1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
use super::*; /// This is returned by the write() method in a successful case. /// /// This structure contains the document id of the written document. #[derive(Serialize, Deserialize)] pub struct WriteResult { /// pub create_time: Option<chrono::DateTime<chrono::Utc>>, pub update_time: Option<chrono::DateTime<chrono::Utc>>, pub document_id: String, } /// Write options. The default will overwrite a target document and not merge fields. #[derive(Default)] pub struct WriteOptions { /// If this is set instead of overwriting all fields of a target document, only the given fields will be merged. /// This only works if your document type has Option fields. /// The write will fail, if no document_id is given or the target document does not exist yet. pub merge: bool, } /// /// Write a document to a given collection. /// /// If no document_id is given, Firestore will generate an ID. Check the [`WriteResult`] return value. /// /// If a document_id is given, the document will be created if it does not yet exist. /// Except if the "merge" option (see [`WriteOptions::merge`]) is set. /// /// Example: ///```rust ///use firestore_db_and_auth::{Credentials, ServiceSession, documents, errors::Result, FirebaseAuthBearer}; ///use serde::{Serialize,Deserialize}; /// /// #[derive(Serialize, Deserialize)] /// struct DemoDTO { /// a_string: String, /// an_int: u32, /// another_int: u32, /// } /// #[derive(Serialize, Deserialize)] /// struct DemoPartialDTO { /// #[serde(skip_serializing_if = "Option::is_none")] /// a_string: Option<String>, /// an_int: u32, /// } /// /// fn write<'a>(session: &'a impl FirebaseAuthBearer<'a>) -> Result<()> { /// let obj = DemoDTO { a_string: "abcd".to_owned(), an_int: 14, another_int: 10 }; /// let result = documents::write(session, "tests", Some("service_test"), &obj, documents::WriteOptions::default())?; /// println!("id: {}, created: {}, updated: {}", result.document_id, result.create_time.unwrap(), result.update_time.unwrap()); /// Ok(()) /// } /// /// Only write some fields and do not overwrite the entire document. /// /// Either via Option<> or by not having the fields in the structure, see DemoPartialDTO. /// fn write_partial<'a>(session: &'a impl FirebaseAuthBearer<'a>) -> Result<()> { /// let obj = DemoPartialDTO { a_string: None, an_int: 16 }; /// let result = documents::write(session, "tests", Some("service_test"), &obj, documents::WriteOptions{merge:true})?; /// println!("id: {}, created: {}, updated: {}", result.document_id, result.create_time.unwrap(), result.update_time.unwrap()); /// Ok(()) /// } /// /// # fn main() -> Result<()> { /// # let cred = Credentials::from_file("firebase-service-account.json")?; /// # let session = ServiceSession::new(cred)?; /// # write(&session)?; /// # write_partial(&session)?; /// # /// # Ok(()) /// # } ///``` /// /// ## Arguments /// * 'auth' The authentication token /// * 'path' The document path / collection; For example "my_collection" or "a/nested/collection" /// * 'document_id' The document id. Make sure that you do not include the document id in the path argument. /// * 'document' The document /// * 'options' Write options pub fn write<T>( auth: &impl FirebaseAuthBearer, path: &str, document_id: Option<impl AsRef<str>>, document: &T, options: WriteOptions, ) -> Result<WriteResult> where T: Serialize, { let mut url = match document_id.as_ref() { Some(document_id) => firebase_url_extended(auth.project_id(), path, document_id.as_ref()), None => firebase_url(auth.project_id(), path), }; let firebase_document = pod_to_document(&document)?; if options.merge && firebase_document.fields.is_some() { let fields = firebase_document.fields.as_ref().unwrap().keys().join(","); url = format!("{}?currentDocument.exists=true&updateMask.fieldPaths={}", url, fields); } let builder = if document_id.is_some() { auth.client().patch(&url) } else { auth.client().post(&url) }; let mut resp = builder .bearer_auth(auth.access_token().to_owned()) .json(&firebase_document) .send()?; extract_google_api_error(&mut resp, || { document_id .as_ref() .and_then(|f| Some(f.as_ref().to_owned())) .or(Some(String::new())) .unwrap() })?; let result_document: dto::Document = resp.json()?; let document_id = Path::new(&result_document.name) .file_name() .ok_or_else(|| FirebaseError::Generic("Resulting documents 'name' field is not a valid path"))? .to_str() .ok_or_else(|| FirebaseError::Generic("No valid unicode in 'name' field"))? .to_owned(); let create_time = match result_document.create_time { Some(f) => Some( chrono::DateTime::parse_from_rfc3339(&f) .map_err(|_| FirebaseError::Generic("Failed to parse rfc3339 date from 'create_time' field"))? .with_timezone(&chrono::Utc), ), None => None, }; let update_time = match result_document.update_time { Some(f) => Some( chrono::DateTime::parse_from_rfc3339(&f) .map_err(|_| FirebaseError::Generic("Failed to parse rfc3339 date from 'update_time' field"))? .with_timezone(&chrono::Utc), ), None => None, }; Ok(WriteResult { document_id, create_time, update_time, }) }