firebase_rs_sdk/firestore/api/
snapshot.rs1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use crate::firestore::error::FirestoreResult;
5use crate::firestore::model::DocumentKey;
6use crate::firestore::value::{FirestoreValue, MapValue};
7
8use super::reference::DocumentReference;
9use super::Firestore;
10use super::FirestoreDataConverter;
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub struct SnapshotMetadata {
15 from_cache: bool,
16 has_pending_writes: bool,
17}
18
19impl SnapshotMetadata {
20 pub fn new(from_cache: bool, has_pending_writes: bool) -> Self {
22 Self {
23 from_cache,
24 has_pending_writes,
25 }
26 }
27
28 pub fn from_cache(&self) -> bool {
30 self.from_cache
31 }
32
33 pub fn has_pending_writes(&self) -> bool {
35 self.has_pending_writes
36 }
37}
38
39#[derive(Clone, Debug)]
41pub struct DocumentSnapshot {
42 key: DocumentKey,
43 data: Option<MapValue>,
44 metadata: SnapshotMetadata,
45}
46
47impl DocumentSnapshot {
48 pub fn new(key: DocumentKey, data: Option<MapValue>, metadata: SnapshotMetadata) -> Self {
49 Self {
50 key,
51 data,
52 metadata,
53 }
54 }
55
56 pub fn exists(&self) -> bool {
58 self.data.is_some()
59 }
60
61 pub fn data(&self) -> Option<&BTreeMap<String, FirestoreValue>> {
66 self.data.as_ref().map(|map| map.fields())
67 }
68
69 pub fn metadata(&self) -> &SnapshotMetadata {
71 &self.metadata
72 }
73
74 pub fn map_value(&self) -> Option<&MapValue> {
76 self.data.as_ref()
77 }
78
79 pub fn from_cache(&self) -> bool {
81 self.metadata.from_cache()
82 }
83
84 pub fn has_pending_writes(&self) -> bool {
86 self.metadata.has_pending_writes()
87 }
88
89 pub fn id(&self) -> &str {
91 self.key.id()
92 }
93
94 pub(crate) fn document_key(&self) -> &DocumentKey {
95 &self.key
96 }
97
98 pub fn reference(&self, firestore: Firestore) -> FirestoreResult<DocumentReference> {
100 DocumentReference::new(firestore, self.key.path().clone())
101 }
102 pub fn into_typed<C>(self, converter: Arc<C>) -> TypedDocumentSnapshot<C>
104 where
105 C: FirestoreDataConverter,
106 {
107 TypedDocumentSnapshot::new(self, converter)
108 }
109
110 pub fn to_typed<C>(&self, converter: Arc<C>) -> TypedDocumentSnapshot<C>
112 where
113 C: FirestoreDataConverter,
114 {
115 self.clone().into_typed(converter)
116 }
117}
118
119#[derive(Clone)]
121pub struct TypedDocumentSnapshot<C>
122where
123 C: FirestoreDataConverter,
124{
125 base: DocumentSnapshot,
126 converter: Arc<C>,
127}
128
129impl<C> TypedDocumentSnapshot<C>
130where
131 C: FirestoreDataConverter,
132{
133 pub fn new(base: DocumentSnapshot, converter: Arc<C>) -> Self {
134 Self { base, converter }
135 }
136
137 pub fn exists(&self) -> bool {
138 self.base.exists()
139 }
140
141 pub fn id(&self) -> &str {
142 self.base.id()
143 }
144
145 pub fn metadata(&self) -> &SnapshotMetadata {
146 self.base.metadata()
147 }
148
149 pub fn from_cache(&self) -> bool {
150 self.base.from_cache()
151 }
152
153 pub fn has_pending_writes(&self) -> bool {
154 self.base.has_pending_writes()
155 }
156
157 pub fn reference(&self, firestore: Firestore) -> FirestoreResult<DocumentReference> {
158 self.base.reference(firestore)
159 }
160
161 pub fn raw(&self) -> &DocumentSnapshot {
162 &self.base
163 }
164
165 pub fn into_raw(self) -> DocumentSnapshot {
166 self.base
167 }
168
169 pub fn data(&self) -> FirestoreResult<Option<C::Model>> {
171 match self.base.map_value() {
172 Some(map) => self.converter.from_map(map).map(Some),
173 None => Ok(None),
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::firestore::api::converter::{FirestoreDataConverter, PassthroughConverter};
182 use crate::firestore::model::DocumentKey;
183 use crate::firestore::value::ValueKind;
184 use std::collections::BTreeMap;
185
186 #[test]
187 fn metadata_flags() {
188 let meta = SnapshotMetadata::new(true, false);
189 assert!(meta.from_cache());
190 assert!(!meta.has_pending_writes());
191 }
192
193 #[test]
194 fn snapshot_reports_existence() {
195 let key = DocumentKey::from_string("cities/sf").unwrap();
196 let snapshot = DocumentSnapshot::new(key, None, SnapshotMetadata::default());
197 assert!(!snapshot.exists());
198 }
199
200 #[derive(Clone)]
201 struct NameConverter;
202
203 impl FirestoreDataConverter for NameConverter {
204 type Model = String;
205
206 fn to_map(&self, value: &Self::Model) -> FirestoreResult<BTreeMap<String, FirestoreValue>> {
207 let mut map = BTreeMap::new();
208 map.insert("name".to_string(), FirestoreValue::from_string(value));
209 Ok(map)
210 }
211
212 fn from_map(&self, value: &MapValue) -> FirestoreResult<Self::Model> {
213 match value.fields().get("name").and_then(|val| match val.kind() {
214 ValueKind::String(s) => Some(s.clone()),
215 _ => None,
216 }) {
217 Some(name) => Ok(name),
218 None => Err(crate::firestore::error::invalid_argument(
219 "missing name field",
220 )),
221 }
222 }
223 }
224
225 #[test]
226 fn typed_snapshot_uses_converter() {
227 let key = DocumentKey::from_string("cities/sf").unwrap();
228 let mut map = BTreeMap::new();
229 map.insert(
230 "name".to_string(),
231 FirestoreValue::from_string("San Francisco"),
232 );
233 let snapshot = DocumentSnapshot::new(
234 key,
235 Some(MapValue::new(map)),
236 SnapshotMetadata::new(false, false),
237 );
238
239 let typed = snapshot.into_typed(Arc::new(NameConverter));
240 let name = typed.data().unwrap();
241 assert_eq!(name.as_deref(), Some("San Francisco"));
242 }
243
244 #[test]
245 fn passthrough_converter_roundtrip() {
246 let key = DocumentKey::from_string("cities/sf").unwrap();
247 let mut map = BTreeMap::new();
248 map.insert("name".to_string(), FirestoreValue::from_string("SF"));
249 let snapshot = DocumentSnapshot::new(
250 key,
251 Some(MapValue::new(map.clone())),
252 SnapshotMetadata::default(),
253 );
254
255 let typed = snapshot.into_typed(Arc::new(PassthroughConverter::default()));
256 let raw = typed.data().unwrap().unwrap();
257 assert_eq!(raw.get("name"), map.get("name"));
258 }
259}