firebase_admin_sdk/firestore/
batch.rs1use super::models::{
2 CommitRequest, CommitResponse, Document, DocumentMask, Precondition, Write, WriteOperation,
3 WriteResult,
4};
5use super::reference::convert_serializable_to_fields;
6use super::FirestoreError;
7use reqwest::header;
8use reqwest_middleware::ClientWithMiddleware;
9use serde::Serialize;
10use std::sync::{Arc, Mutex};
11
12#[derive(Clone)]
35pub struct WriteBatch<'a> {
36 client: &'a ClientWithMiddleware,
37 base_url: String,
38 writes: Arc<Mutex<Vec<Write>>>,
39}
40
41impl<'a> WriteBatch<'a> {
42 pub(crate) fn new(client: &'a ClientWithMiddleware, base_url: String) -> Self {
43 Self {
44 client,
45 base_url,
46 writes: Arc::new(Mutex::new(Vec::new())),
47 }
48 }
49
50 pub fn set<T: Serialize>(
59 &self,
60 document_path: &str,
61 value: &T,
62 ) -> Result<&Self, FirestoreError> {
63 let fields = convert_serializable_to_fields(value)?;
64 let resource_name = self.extract_resource_name(document_path);
65
66 let write = Write {
67 update_mask: None,
68 update_transforms: None,
69 current_document: None,
70 operation: WriteOperation::Update(Document {
71 name: resource_name,
72 fields,
73 create_time: String::new(), update_time: String::new(), }),
76 };
77
78 self.writes.lock().unwrap().push(write);
79 Ok(self)
80 }
81
82 pub fn update<T: Serialize>(
91 &self,
92 document_path: &str,
93 value: &T,
94 ) -> Result<&Self, FirestoreError> {
95 let fields = convert_serializable_to_fields(value)?;
96 let resource_name = self.extract_resource_name(document_path);
97
98 let field_paths = fields.keys().cloned().collect();
99
100 let write = Write {
101 update_mask: Some(DocumentMask { field_paths }),
102 update_transforms: None,
103 current_document: Some(Precondition {
104 exists: Some(true),
105 update_time: None,
106 }),
107 operation: WriteOperation::Update(Document {
108 name: resource_name,
109 fields,
110 create_time: String::new(),
111 update_time: String::new(),
112 }),
113 };
114
115 self.writes.lock().unwrap().push(write);
116 Ok(self)
117 }
118
119 pub fn delete(&self, document_path: &str) -> Result<&Self, FirestoreError> {
125 let resource_name = self.extract_resource_name(document_path);
126
127 let write = Write {
128 update_mask: None,
129 update_transforms: None,
130 current_document: None,
131 operation: WriteOperation::Delete(resource_name),
132 };
133
134 self.writes.lock().unwrap().push(write);
135 Ok(self)
136 }
137
138 pub fn create<T: Serialize>(
147 &self,
148 document_path: &str,
149 value: &T,
150 ) -> Result<&Self, FirestoreError> {
151 let fields = convert_serializable_to_fields(value)?;
152 let resource_name = self.extract_resource_name(document_path);
153
154 let write = Write {
155 update_mask: None,
156 update_transforms: None,
157 current_document: Some(Precondition {
158 exists: Some(false),
159 update_time: None,
160 }),
161 operation: WriteOperation::Update(Document {
162 name: resource_name,
163 fields,
164 create_time: String::new(),
165 update_time: String::new(),
166 }),
167 };
168
169 self.writes.lock().unwrap().push(write);
170 Ok(self)
171 }
172
173 fn extract_resource_name(&self, document_path: &str) -> String {
174 let prefix = "https://firestore.googleapis.com/v1/";
175 let base_path = self.base_url.strip_prefix(prefix).unwrap_or(&self.base_url);
176 format!("{}/{}", base_path, document_path)
177 }
178
179 pub async fn commit(&self) -> Result<Vec<WriteResult>, FirestoreError> {
181 let writes = {
182 let mut guard = self.writes.lock().unwrap();
183 let w = guard.clone();
184 guard.clear();
185 w
186 };
187
188 if writes.is_empty() {
189 return Ok(Vec::new());
190 }
191
192 let url = format!("{}:commit", self.base_url.split("/documents").next().unwrap());
193
194 let request = CommitRequest {
195 transaction: None,
196 writes,
197 };
198
199 let response = self
200 .client
201 .post(&url)
202 .header(header::CONTENT_TYPE, "application/json")
203 .body(serde_json::to_vec(&request)?)
204 .send()
205 .await?;
206
207 if !response.status().is_success() {
208 let status = response.status();
209 let text = response.text().await.unwrap_or_default();
210 return Err(FirestoreError::ApiError(format!(
211 "Commit batch failed {}: {}",
212 status, text
213 )));
214 }
215
216 let result: CommitResponse = response.json().await?;
217 Ok(result.write_results)
218 }
219}