|
2 | 2 | //!
|
3 | 3 | //! An instance of `Document` is scoped to a specific document and is created
|
4 | 4 | //! via a parent `Collection` struct, for example:
|
5 |
| -//! `client.collection("collection_name").document("document_id")` |
| 5 | +//! `client.collection::<Book>("books").document("123")` |
6 | 6 |
|
7 |
| -use super::{Client, Error}; |
| 7 | +use crate::{Client, Error}; |
| 8 | +use serde::{de::DeserializeOwned, Serialize}; |
8 | 9 | use std::sync::Arc;
|
9 |
| -use typesense_codegen::apis::{configuration, documents_api}; |
| 10 | +use typesense_codegen::{ |
| 11 | + apis::{configuration, documents_api}, |
| 12 | + models, |
| 13 | +}; |
10 | 14 |
|
11 | 15 | /// Provides methods for interacting with a single document within a specific Typesense collection.
|
12 | 16 | ///
|
13 |
| -/// This struct is created by calling a method like `client.collection("collection_name").document("document_id")`. |
14 |
| -pub struct Document<'a> { |
| 17 | +/// This struct is created by calling a method like `client.collection::<T>("collection_name").document("document_id")`. |
| 18 | +/// The generic `T` represents the shape of the document and must implement `Serialize` and `DeserializeOwned`. |
| 19 | +/// If `T` is not specified, it defaults to `serde_json::Value` for schemaless interactions. |
| 20 | +pub struct Document<'a, T = serde_json::Value> |
| 21 | +where |
| 22 | + T: DeserializeOwned + Serialize + Send + Sync, |
| 23 | +{ |
15 | 24 | pub(super) client: &'a Client,
|
16 | 25 | pub(super) collection_name: &'a str,
|
17 | 26 | pub(super) document_id: &'a str,
|
| 27 | + pub(super) _phantom: std::marker::PhantomData<T>, |
18 | 28 | }
|
19 | 29 |
|
20 |
| -impl<'a> Document<'a> { |
| 30 | +impl<'a, T> Document<'a, T> |
| 31 | +where |
| 32 | + T: DeserializeOwned + Serialize + Send + Sync, |
| 33 | +{ |
21 | 34 | /// Creates a new `Document` instance for a specific document ID.
|
22 |
| - /// This is intended for internal use by the parent `Documents` struct. |
| 35 | + /// This is intended for internal use by the parent `Collection` struct. |
23 | 36 | pub(super) fn new(client: &'a Client, collection_name: &'a str, document_id: &'a str) -> Self {
|
24 | 37 | Self {
|
25 | 38 | client,
|
26 | 39 | collection_name,
|
27 | 40 | document_id,
|
| 41 | + _phantom: std::marker::PhantomData, |
28 | 42 | }
|
29 | 43 | }
|
30 | 44 |
|
31 |
| - /// Fetches this individual document from the collection. |
32 |
| - pub async fn retrieve( |
33 |
| - &self, |
34 |
| - ) -> Result<serde_json::Value, Error<documents_api::GetDocumentError>> { |
| 45 | + /// Fetches this individual document from the collection and deserializes it into `T`. |
| 46 | + /// |
| 47 | + /// # Returns |
| 48 | + /// A `Result` containing the strongly-typed document `T` if successful. |
| 49 | + pub async fn retrieve(&self) -> Result<T, Error<documents_api::GetDocumentError>> { |
35 | 50 | let params = documents_api::GetDocumentParams {
|
36 | 51 | collection_name: self.collection_name.to_string(),
|
37 | 52 | document_id: self.document_id.to_string(),
|
38 | 53 | };
|
39 | 54 |
|
40 |
| - self.client |
| 55 | + let result_value = self |
| 56 | + .client |
41 | 57 | .execute(|config: Arc<configuration::Configuration>| {
|
42 | 58 | let params_for_move = params.clone();
|
43 | 59 | async move { documents_api::get_document(&config, params_for_move).await }
|
44 | 60 | })
|
45 |
| - .await |
| 61 | + .await?; |
| 62 | + |
| 63 | + // Deserialize the raw JSON value into the user's type T. |
| 64 | + serde_json::from_value(result_value).map_err(Error::from) |
46 | 65 | }
|
47 | 66 |
|
48 | 67 | /// Updates this individual document. The update can be partial.
|
| 68 | + /// The updated full document is returned. |
49 | 69 | ///
|
50 | 70 | /// # Arguments
|
51 |
| - /// * `document` - A `serde_json::Value` containing the fields to update. |
52 |
| - pub async fn update( |
| 71 | + /// * `partial_document` - A serializable struct or a `serde_json::Value` containing the fields to update. |
| 72 | + /// For example: `serde_json::json!({ "in_stock": false })`. |
| 73 | + /// * `params` - An optional `DocumentIndexParameters` struct to specify additional |
| 74 | + /// parameters, such as `dirty_values` which determines what Typesense should do when the type of a particular field being indexed does not match the previously inferred type for that field, or the one defined in the collection's schema. |
| 75 | + /// |
| 76 | + /// # Returns |
| 77 | + /// A `Result` containing the full, updated document deserialized into `T`. |
| 78 | + /// |
| 79 | + /// # Example |
| 80 | + /// ```no_run |
| 81 | + /// # use serde::{Serialize, Deserialize}; |
| 82 | + /// # use typesense::{Client, MultiNodeConfiguration, models}; |
| 83 | + /// # use reqwest::Url; |
| 84 | + /// # #[derive(Serialize, Deserialize)] |
| 85 | + /// # struct Book { id: String, title: String, pages: i32 } |
| 86 | + /// # |
| 87 | + /// # async fn run() -> Result<(), Box<dyn std::error::Error>> { |
| 88 | + /// # let config = MultiNodeConfiguration { |
| 89 | + /// # nodes: vec![Url::parse("http://localhost:8108")?], |
| 90 | + /// # api_key: "xyz".to_string(), |
| 91 | + /// # ..Default::default() |
| 92 | + /// # }; |
| 93 | + /// # let client = Client::new(config)?; |
| 94 | + /// let book_update = serde_json::json!({ "pages": 654 }); |
| 95 | + /// |
| 96 | + /// // Simple update |
| 97 | + /// let updated_book = client.collection_of::<Book>("books").document("123") |
| 98 | + /// .update(&book_update, None) |
| 99 | + /// .await?; |
| 100 | + /// |
| 101 | + /// // Update with additional parameters |
| 102 | + /// let params = models::DocumentIndexParameters { |
| 103 | + /// dirty_values: Some(models::DirtyValues::CoerceOrReject), |
| 104 | + /// }; |
| 105 | + /// let updated_book_with_params = client.collection_of::<Book>("books").document("124") |
| 106 | + /// .update(&book_update, Some(params)) |
| 107 | + /// .await?; |
| 108 | + /// # |
| 109 | + /// # Ok(()) |
| 110 | + /// # } |
| 111 | + /// ``` |
| 112 | + pub async fn update<U: Serialize>( |
53 | 113 | &self,
|
54 |
| - document: serde_json::Value, |
55 |
| - ) -> Result<serde_json::Value, Error<documents_api::UpdateDocumentError>> { |
| 114 | + partial_document: &U, |
| 115 | + params: Option<models::DocumentIndexParameters>, |
| 116 | + ) -> Result<T, Error<documents_api::UpdateDocumentError>> { |
56 | 117 | let params = documents_api::UpdateDocumentParams {
|
57 | 118 | collection_name: self.collection_name.to_string(),
|
58 | 119 | document_id: self.document_id.to_string(),
|
59 |
| - body: document, |
60 |
| - dirty_values: None, |
| 120 | + body: serde_json::to_value(partial_document)?, |
| 121 | + dirty_values: params.unwrap_or_default().dirty_values, |
61 | 122 | };
|
62 |
| - self.client |
| 123 | + |
| 124 | + let result_value = self |
| 125 | + .client |
63 | 126 | .execute(|config: Arc<configuration::Configuration>| {
|
64 | 127 | let params_for_move = params.clone();
|
65 | 128 | async move { documents_api::update_document(&config, params_for_move).await }
|
66 | 129 | })
|
67 |
| - .await |
| 130 | + .await?; |
| 131 | + |
| 132 | + // Deserialize the raw JSON value of the updated document into T. |
| 133 | + serde_json::from_value(result_value).map_err(Error::from) |
68 | 134 | }
|
69 | 135 |
|
70 | 136 | /// Deletes this individual document from the collection.
|
71 |
| - pub async fn delete( |
72 |
| - &self, |
73 |
| - ) -> Result<serde_json::Value, Error<documents_api::DeleteDocumentError>> { |
| 137 | + /// The deleted document is returned. |
| 138 | + /// |
| 139 | + /// # Returns |
| 140 | + /// A `Result` containing the deleted document deserialized into `T`. |
| 141 | + pub async fn delete(&self) -> Result<T, Error<documents_api::DeleteDocumentError>> { |
74 | 142 | let params = documents_api::DeleteDocumentParams {
|
75 | 143 | collection_name: self.collection_name.to_string(),
|
76 | 144 | document_id: self.document_id.to_string(),
|
77 | 145 | };
|
78 |
| - self.client |
| 146 | + |
| 147 | + let result_value = self |
| 148 | + .client |
79 | 149 | .execute(|config: Arc<configuration::Configuration>| {
|
80 | 150 | let params_for_move = params.clone();
|
81 | 151 | async move { documents_api::delete_document(&config, params_for_move).await }
|
82 | 152 | })
|
83 |
| - .await |
| 153 | + .await?; |
| 154 | + |
| 155 | + // Deserialize the raw JSON value of the deleted document into T. |
| 156 | + serde_json::from_value(result_value).map_err(Error::from) |
84 | 157 | }
|
85 | 158 | }
|
0 commit comments