firebase_admin_sdk/firestore/
transaction.rs1use super::models::{
2 CommitRequest, CommitResponse, Document, DocumentMask, Precondition, Write, WriteOperation,
3 WriteResult,
4};
5use super::reference::{convert_fields_to_serde_value, convert_serializable_to_fields};
6use super::FirestoreError;
7use crate::core::parse_error_response;
8use reqwest::header;
9use reqwest_middleware::ClientWithMiddleware;
10use serde::de::DeserializeOwned;
11use serde::Serialize;
12use std::sync::{Arc, Mutex};
13use url::Url;
14
15#[derive(Clone)]
19pub struct Transaction {
20 client: ClientWithMiddleware,
21 base_url: String,
22 pub transaction_id: String,
23 writes: Arc<Mutex<Vec<Write>>>,
24}
25
26impl Transaction {
27 pub(crate) fn new(
28 client: ClientWithMiddleware,
29 base_url: String,
30 transaction_id: String,
31 ) -> Self {
32 Self {
33 client,
34 base_url,
35 transaction_id,
36 writes: Arc::new(Mutex::new(Vec::new())),
37 }
38 }
39
40 pub async fn get<T: DeserializeOwned>(
48 &self,
49 document_path: &str,
50 ) -> Result<Option<T>, FirestoreError> {
51 let url = format!("{}/{}", self.base_url, document_path);
56 let mut url_obj = Url::parse(&url).map_err(|e| FirestoreError::ApiError(e.to_string()))?;
57 url_obj.query_pairs_mut().append_pair("transaction", &self.transaction_id);
58
59 let response = self
61 .client
62 .get(url_obj)
63 .send()
64 .await?;
65
66 if response.status() == reqwest::StatusCode::NOT_FOUND {
67 return Ok(None);
68 }
69
70 if !response.status().is_success() {
71 return Err(FirestoreError::ApiError(parse_error_response(response, "Get document in transaction failed").await));
72 }
73
74 let doc: Document = response.json().await?;
75 let serde_value = convert_fields_to_serde_value(doc.fields)?;
76 let obj = serde_json::from_value(serde_value)?;
77 Ok(Some(obj))
78 }
79
80 pub fn set<T: Serialize>(
89 &self,
90 document_path: &str,
91 value: &T,
92 ) -> Result<&Self, FirestoreError> {
93 let fields = convert_serializable_to_fields(value)?;
94 let resource_name = self.extract_resource_name(document_path);
95
96 let write = Write {
97 update_mask: None,
98 update_transforms: None,
99 current_document: None,
100 operation: WriteOperation::Update(Document {
101 name: resource_name,
102 fields,
103 create_time: String::new(), update_time: String::new(), }),
106 };
107
108 self.writes.lock().unwrap().push(write);
109 Ok(self)
110 }
111
112 pub fn update<T: Serialize>(
121 &self,
122 document_path: &str,
123 value: &T,
124 ) -> Result<&Self, FirestoreError> {
125 let fields = convert_serializable_to_fields(value)?;
126 let resource_name = self.extract_resource_name(document_path);
127
128 let field_paths = fields.keys().cloned().collect();
135
136 let write = Write {
137 update_mask: Some(DocumentMask { field_paths }),
138 update_transforms: None,
139 current_document: Some(Precondition {
140 exists: Some(true),
141 update_time: None,
142 }),
143 operation: WriteOperation::Update(Document {
144 name: resource_name,
145 fields,
146 create_time: String::new(),
147 update_time: String::new(),
148 }),
149 };
150
151 self.writes.lock().unwrap().push(write);
152 Ok(self)
153 }
154
155 pub fn delete(&self, document_path: &str) -> Result<&Self, FirestoreError> {
161 let resource_name = self.extract_resource_name(document_path);
162
163 let write = Write {
164 update_mask: None,
165 update_transforms: None,
166 current_document: None,
167 operation: WriteOperation::Delete(resource_name),
168 };
169
170 self.writes.lock().unwrap().push(write);
171 Ok(self)
172 }
173
174 fn extract_resource_name(&self, document_path: &str) -> String {
175 let prefix = "https://firestore.googleapis.com/v1/";
180 let base_path = self.base_url.strip_prefix(prefix).unwrap_or(&self.base_url);
181 format!("{}/{}", base_path, document_path)
182 }
183
184 pub(crate) async fn commit(&self) -> Result<Vec<WriteResult>, FirestoreError> {
188 let writes = {
189 let mut guard = self.writes.lock().unwrap();
190 let w = guard.clone();
191 guard.clear();
192 w
193 };
194
195 if writes.is_empty() {
196 return Ok(Vec::new());
197 }
198
199 let url = format!("{}:commit", self.base_url.split("/documents").next().unwrap());
200
201 let request = CommitRequest {
202 transaction: Some(self.transaction_id.clone()),
203 writes,
204 };
205
206 let response = self
207 .client
208 .post(&url)
209 .header(header::CONTENT_TYPE, "application/json")
210 .body(serde_json::to_vec(&request)?)
211 .send()
212 .await?;
213
214 if !response.status().is_success() {
215 return Err(FirestoreError::ApiError(parse_error_response(response, "Commit transaction failed").await));
216 }
217
218 let result: CommitResponse = response.json().await?;
219 Ok(result.write_results)
220 }
221}