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)]
26pub struct WriteBatch<'a> {
27 client: &'a ClientWithMiddleware,
28 base_url: String,
29 writes: Arc<Mutex<Vec<Write>>>,
30}
31
32impl<'a> WriteBatch<'a> {
33 pub(crate) fn new(client: &'a ClientWithMiddleware, base_url: String) -> Self {
34 Self {
35 client,
36 base_url,
37 writes: Arc::new(Mutex::new(Vec::new())),
38 }
39 }
40
41 pub fn set<T: Serialize>(
50 &self,
51 document_path: &str,
52 value: &T,
53 ) -> Result<&Self, FirestoreError> {
54 let fields = convert_serializable_to_fields(value)?;
55 let resource_name = self.extract_resource_name(document_path);
56
57 let write = Write {
58 update_mask: None,
59 update_transforms: None,
60 current_document: None,
61 operation: WriteOperation::Update(Document {
62 name: resource_name,
63 fields,
64 create_time: String::new(), update_time: String::new(), }),
67 };
68
69 self.writes.lock().unwrap().push(write);
70 Ok(self)
71 }
72
73 pub fn update<T: Serialize>(
82 &self,
83 document_path: &str,
84 value: &T,
85 ) -> Result<&Self, FirestoreError> {
86 let fields = convert_serializable_to_fields(value)?;
87 let resource_name = self.extract_resource_name(document_path);
88
89 let field_paths = fields.keys().cloned().collect();
90
91 let write = Write {
92 update_mask: Some(DocumentMask { field_paths }),
93 update_transforms: None,
94 current_document: Some(Precondition {
95 exists: Some(true),
96 update_time: None,
97 }),
98 operation: WriteOperation::Update(Document {
99 name: resource_name,
100 fields,
101 create_time: String::new(),
102 update_time: String::new(),
103 }),
104 };
105
106 self.writes.lock().unwrap().push(write);
107 Ok(self)
108 }
109
110 pub fn delete(&self, document_path: &str) -> Result<&Self, FirestoreError> {
116 let resource_name = self.extract_resource_name(document_path);
117
118 let write = Write {
119 update_mask: None,
120 update_transforms: None,
121 current_document: None,
122 operation: WriteOperation::Delete(resource_name),
123 };
124
125 self.writes.lock().unwrap().push(write);
126 Ok(self)
127 }
128
129 pub fn create<T: Serialize>(
138 &self,
139 document_path: &str,
140 value: &T,
141 ) -> Result<&Self, FirestoreError> {
142 let fields = convert_serializable_to_fields(value)?;
143 let resource_name = self.extract_resource_name(document_path);
144
145 let write = Write {
146 update_mask: None,
147 update_transforms: None,
148 current_document: Some(Precondition {
149 exists: Some(false),
150 update_time: None,
151 }),
152 operation: WriteOperation::Update(Document {
153 name: resource_name,
154 fields,
155 create_time: String::new(),
156 update_time: String::new(),
157 }),
158 };
159
160 self.writes.lock().unwrap().push(write);
161 Ok(self)
162 }
163
164 fn extract_resource_name(&self, document_path: &str) -> String {
165 let prefix = "https://firestore.googleapis.com/v1/";
166 let base_path = self.base_url.strip_prefix(prefix).unwrap_or(&self.base_url);
167 format!("{}/{}", base_path, document_path)
168 }
169
170 pub async fn commit(&self) -> Result<Vec<WriteResult>, FirestoreError> {
172 let writes = {
173 let mut guard = self.writes.lock().unwrap();
174 let w = guard.clone();
175 guard.clear();
176 w
177 };
178
179 if writes.is_empty() {
180 return Ok(Vec::new());
181 }
182
183 let url = format!("{}:commit", self.base_url.split("/documents").next().unwrap());
184
185 let request = CommitRequest {
186 transaction: None,
187 writes,
188 };
189
190 let response = self
191 .client
192 .post(&url)
193 .header(header::CONTENT_TYPE, "application/json")
194 .body(serde_json::to_vec(&request)?)
195 .send()
196 .await?;
197
198 if !response.status().is_success() {
199 let status = response.status();
200 let text = response.text().await.unwrap_or_default();
201 return Err(FirestoreError::ApiError(format!(
202 "Commit batch failed {}: {}",
203 status, text
204 )));
205 }
206
207 let result: CommitResponse = response.json().await?;
208 Ok(result.write_results)
209 }
210}