hydrate_data/
single_object.rs

1use crate::{
2    DataSetError, DataSetResult, HashMap, OrderedSet, SchemaFingerprint, SchemaRecord, Value,
3};
4use crate::{NullOverride, SchemaSet};
5use hydrate_schema::Schema;
6use std::hash::{Hash, Hasher};
7use std::str::FromStr;
8use std::string::ToString;
9use uuid::Uuid;
10
11/// A simplified container of data. Can be used to produce a set of properties and be merged into
12/// a data set later, or be serialized by itself. Still support schema migration.
13#[derive(Clone, Debug)]
14pub struct SingleObject {
15    schema: SchemaRecord,
16    properties: HashMap<String, Value>,
17    property_null_overrides: HashMap<String, NullOverride>,
18    dynamic_collection_entries: HashMap<String, OrderedSet<Uuid>>,
19}
20
21impl Hash for SingleObject {
22    fn hash<H: Hasher>(
23        &self,
24        state: &mut H,
25    ) {
26        let schema = &self.schema;
27
28        schema.fingerprint().hash(state);
29
30        // properties
31        let mut properties_hash = 0;
32        for (key, value) in &self.properties {
33            let mut inner_hasher = siphasher::sip::SipHasher::default();
34            key.hash(&mut inner_hasher);
35            value.hash(&mut inner_hasher);
36            properties_hash = properties_hash ^ inner_hasher.finish();
37        }
38        properties_hash.hash(state);
39
40        // property_null_overrides
41        let mut property_null_overrides_hash = 0;
42        for (key, value) in &self.property_null_overrides {
43            let mut inner_hasher = siphasher::sip::SipHasher::default();
44            key.hash(&mut inner_hasher);
45            value.hash(&mut inner_hasher);
46            property_null_overrides_hash = property_null_overrides_hash ^ inner_hasher.finish();
47        }
48        property_null_overrides_hash.hash(state);
49
50        // dynamic_collection_entries
51        let mut dynamic_collection_entries_hash = 0;
52        for (key, value) in &self.dynamic_collection_entries {
53            let mut inner_hasher = siphasher::sip::SipHasher::default();
54            key.hash(&mut inner_hasher);
55
56            let mut uuid_set_hash = 0;
57            for id in value {
58                let mut inner_hasher2 = siphasher::sip::SipHasher::default();
59                id.hash(&mut inner_hasher2);
60                uuid_set_hash = uuid_set_hash ^ inner_hasher2.finish();
61            }
62            uuid_set_hash.hash(&mut inner_hasher);
63
64            dynamic_collection_entries_hash =
65                dynamic_collection_entries_hash ^ inner_hasher.finish();
66        }
67        dynamic_collection_entries_hash.hash(state);
68    }
69}
70
71impl SingleObject {
72    pub fn new(schema: &SchemaRecord) -> Self {
73        SingleObject {
74            schema: schema.clone(),
75            properties: Default::default(),
76            property_null_overrides: Default::default(),
77            dynamic_collection_entries: Default::default(),
78        }
79    }
80
81    pub fn restore(
82        schema_set: &SchemaSet,
83        schema: SchemaFingerprint,
84        properties: HashMap<String, Value>,
85        property_null_overrides: HashMap<String, NullOverride>,
86        dynamic_collection_entries: HashMap<String, OrderedSet<Uuid>>,
87    ) -> SingleObject {
88        let schema = schema_set.schemas().get(&schema).unwrap();
89        let schema_record = schema.as_record().cloned().unwrap();
90        SingleObject {
91            schema: schema_record,
92            properties,
93            property_null_overrides,
94            dynamic_collection_entries,
95        }
96    }
97
98    pub fn schema(&self) -> &SchemaRecord {
99        &self.schema
100    }
101
102    pub fn properties(&self) -> &HashMap<String, Value> {
103        &self.properties
104    }
105
106    pub fn property_null_overrides(&self) -> &HashMap<String, NullOverride> {
107        &self.property_null_overrides
108    }
109
110    pub fn dynamic_collection_entries(&self) -> &HashMap<String, OrderedSet<Uuid>> {
111        &self.dynamic_collection_entries
112    }
113
114    /// Gets if the property has a null override associated with it An error will be returned if
115    /// the schema doesn't exist or if this field is not nullable
116    pub fn get_null_override(
117        &self,
118        schema_set: &SchemaSet,
119        path: impl AsRef<str>,
120    ) -> DataSetResult<NullOverride> {
121        let property_schema = self
122            .schema
123            .find_property_schema(&path, schema_set.schemas())
124            .ok_or(DataSetError::SchemaNotFound)?;
125
126        if property_schema.is_nullable() {
127            // Not existing in the map implies that it is unset
128            Ok(self
129                .property_null_overrides
130                .get(path.as_ref())
131                .copied()
132                .unwrap_or(NullOverride::Unset))
133        } else {
134            Err(DataSetError::InvalidSchema)?
135        }
136    }
137
138    pub fn set_null_override(
139        &mut self,
140        schema_set: &SchemaSet,
141        path: impl AsRef<str>,
142        null_override: NullOverride,
143    ) -> DataSetResult<()> {
144        let property_schema = self
145            .schema
146            .find_property_schema(&path, schema_set.schemas())
147            .ok_or(DataSetError::SchemaNotFound)?;
148
149        if property_schema.is_nullable() {
150            if null_override != NullOverride::Unset {
151                self.property_null_overrides
152                    .insert(path.as_ref().to_string(), null_override);
153            } else {
154                self.property_null_overrides.remove(path.as_ref());
155            }
156            Ok(())
157        } else {
158            Err(DataSetError::InvalidSchema)?
159        }
160    }
161
162    fn validate_parent_paths(
163        &self,
164        schema_set: &SchemaSet,
165        path: impl AsRef<str>,
166    ) -> DataSetResult<Schema> {
167        // Contains the path segments that we need to check for being null
168        let mut accessed_nullable_keys = vec![];
169        // The containers we access and what keys are used to access them
170        let mut accessed_dynamic_array_keys = vec![];
171        let mut accessed_static_array_keys = vec![];
172        let mut accessed_map_keys = vec![];
173
174        //TODO: Only allow getting values that exist, in particular, dynamic array overrides
175
176        let property_schema = super::property_schema_and_path_ancestors_to_check(
177            &self.schema,
178            &path,
179            schema_set.schemas(),
180            &mut accessed_nullable_keys,
181            &mut accessed_dynamic_array_keys,
182            &mut accessed_static_array_keys,
183            &mut accessed_map_keys,
184        )?;
185
186        // See if this field was contained in any nullables. If any of those were null, return None.
187        for checked_property in &accessed_nullable_keys {
188            if self.resolve_null_override(schema_set, checked_property)? != NullOverride::SetNonNull
189            {
190                return Err(DataSetError::PathParentIsNull)?;
191            }
192        }
193
194        // See if this field was contained in a container. If any of those containers didn't contain
195        // this property path, return None
196        for (path, key) in &accessed_dynamic_array_keys {
197            let dynamic_collection_entries =
198                self.resolve_dynamic_array_entries(schema_set, path)?;
199            if !dynamic_collection_entries
200                .contains(&Uuid::from_str(key).map_err(|_| DataSetError::UuidParseError)?)
201            {
202                return Err(DataSetError::PathDynamicArrayEntryDoesNotExist)?;
203            }
204        }
205
206        Ok(property_schema)
207    }
208
209    // None return means the property can't be resolved, maybe because something higher in
210    // property hierarchy is null or non-existing
211    pub fn resolve_null_override(
212        &self,
213        schema_set: &SchemaSet,
214        path: impl AsRef<str>,
215    ) -> DataSetResult<NullOverride> {
216        let property_schema = self.validate_parent_paths(schema_set, path.as_ref())?;
217
218        // This field is not nullable, return an error
219        if !property_schema.is_nullable() {
220            return Err(DataSetError::InvalidSchema)?;
221        }
222
223        Ok(self
224            .property_null_overrides
225            .get(path.as_ref())
226            .copied()
227            .unwrap_or(NullOverride::Unset))
228    }
229
230    pub fn has_property_override(
231        &self,
232        path: impl AsRef<str>,
233    ) -> bool {
234        self.get_property_override(path).is_some()
235    }
236
237    // Just gets if this object has a property without checking prototype chain for fallback or returning a default
238    // Returning none means it is not overridden
239    pub fn get_property_override(
240        &self,
241        path: impl AsRef<str>,
242    ) -> Option<&Value> {
243        self.properties.get(path.as_ref())
244    }
245
246    // Just sets a property on this object, making it overridden, or replacing the existing override
247    pub fn set_property_override(
248        &mut self,
249        schema_set: &SchemaSet,
250        path: impl AsRef<str>,
251        value: Option<Value>,
252    ) -> DataSetResult<Option<Value>> {
253        let property_schema = self.validate_parent_paths(schema_set, path.as_ref())?;
254
255        if let Some(value) = &value {
256            if !value.matches_schema(&property_schema, schema_set.schemas()) {
257                log::debug!(
258                    "Value {:?} doesn't match schema {:?} on schema {:?} path {:?}",
259                    value,
260                    property_schema,
261                    self.schema.name(),
262                    path.as_ref()
263                );
264                return Err(DataSetError::ValueDoesNotMatchSchema)?;
265            }
266        }
267
268        let old_value = if let Some(value) = value {
269            self.properties.insert(path.as_ref().to_string(), value)
270        } else {
271            self.properties.remove(path.as_ref())
272        };
273        Ok(old_value)
274    }
275
276    pub fn resolve_property<'a>(
277        &'a self,
278        schema_set: &'a SchemaSet,
279        path: impl AsRef<str>,
280    ) -> DataSetResult<&'a Value> {
281        let property_schema = self.validate_parent_paths(schema_set, path.as_ref())?;
282
283        if let Some(value) = self.properties.get(path.as_ref()) {
284            return Ok(value);
285        }
286
287        Ok(Value::default_for_schema(&property_schema, schema_set))
288    }
289
290    fn get_dynamic_collection_entries(
291        &self,
292        path: impl AsRef<str>,
293    ) -> DataSetResult<std::slice::Iter<Uuid>> {
294        if let Some(overrides) = self.dynamic_collection_entries.get(path.as_ref()) {
295            Ok(overrides.iter())
296        } else {
297            Ok(std::slice::Iter::default())
298        }
299    }
300
301    pub fn get_dynamic_array_entries(
302        &self,
303        schema_set: &SchemaSet,
304        path: impl AsRef<str>,
305    ) -> DataSetResult<std::slice::Iter<Uuid>> {
306        let property_schema = self
307            .schema
308            .find_property_schema(&path, schema_set.schemas())
309            .ok_or(DataSetError::SchemaNotFound)?;
310
311        if !property_schema.is_dynamic_array() {
312            return Err(DataSetError::InvalidSchema)?;
313        }
314
315        self.get_dynamic_collection_entries(path)
316    }
317
318    pub fn get_map_entries(
319        &self,
320        schema_set: &SchemaSet,
321        path: impl AsRef<str>,
322    ) -> DataSetResult<std::slice::Iter<Uuid>> {
323        let property_schema = self
324            .schema
325            .find_property_schema(&path, schema_set.schemas())
326            .ok_or(DataSetError::SchemaNotFound)?;
327
328        if !property_schema.is_map() {
329            return Err(DataSetError::InvalidSchema)?;
330        }
331
332        self.get_dynamic_collection_entries(path)
333    }
334
335    fn add_dynamic_collection_entry(
336        &mut self,
337        path: impl AsRef<str>,
338    ) -> DataSetResult<Uuid> {
339        let entry = self
340            .dynamic_collection_entries
341            .entry(path.as_ref().to_string())
342            .or_insert(Default::default());
343        let new_uuid = Uuid::new_v4();
344        let newly_inserted = entry.try_insert_at_end(new_uuid);
345        if !newly_inserted {
346            panic!("Created a new random UUID but it matched an existing UUID");
347        }
348        Ok(new_uuid)
349    }
350
351    pub fn add_dynamic_array_entry(
352        &mut self,
353        schema_set: &SchemaSet,
354        path: impl AsRef<str>,
355    ) -> DataSetResult<Uuid> {
356        let property_schema = self
357            .schema
358            .find_property_schema(&path, schema_set.schemas())
359            .ok_or(DataSetError::SchemaNotFound)?;
360
361        if !property_schema.is_dynamic_array() {
362            return Err(DataSetError::InvalidSchema)?;
363        }
364
365        self.add_dynamic_collection_entry(path)
366    }
367
368    pub fn add_map_entry(
369        &mut self,
370        schema_set: &SchemaSet,
371        path: impl AsRef<str>,
372    ) -> DataSetResult<Uuid> {
373        let property_schema = self
374            .schema
375            .find_property_schema(&path, schema_set.schemas())
376            .ok_or(DataSetError::SchemaNotFound)?;
377
378        if !property_schema.is_map() {
379            return Err(DataSetError::InvalidSchema)?;
380        }
381
382        self.add_dynamic_collection_entry(path)
383    }
384
385    pub fn insert_dynamic_array_entry(
386        &mut self,
387        schema_set: &SchemaSet,
388        path: impl AsRef<str>,
389        index: usize,
390        entry_uuid: Uuid,
391    ) -> DataSetResult<()> {
392        let property_schema = self
393            .schema
394            .find_property_schema(&path, schema_set.schemas())
395            .ok_or(DataSetError::SchemaNotFound)?;
396
397        if !property_schema.is_dynamic_array() {
398            return Err(DataSetError::InvalidSchema)?;
399        }
400
401        if !property_schema.is_dynamic_array() {
402            return Err(DataSetError::InvalidSchema)?;
403        }
404
405        let entry = self
406            .dynamic_collection_entries
407            .entry(path.as_ref().to_string())
408            .or_insert(Default::default());
409        if entry.try_insert_at_position(index, entry_uuid) {
410            Ok(())
411        } else {
412            Err(DataSetError::DuplicateEntryKey)?
413        }
414    }
415
416    fn remove_dynamic_collection_entry(
417        &mut self,
418        path: impl AsRef<str>,
419        element_id: Uuid,
420    ) -> DataSetResult<bool> {
421        if let Some(override_list) = self.dynamic_collection_entries.get_mut(path.as_ref()) {
422            // Return if the override existed or not
423            let was_removed = override_list.remove(&element_id);
424            Ok(was_removed)
425        } else {
426            // The override didn't exist
427            Ok(false)
428        }
429    }
430
431    pub fn remove_dynamic_array_entry(
432        &mut self,
433        schema_set: &SchemaSet,
434        path: impl AsRef<str>,
435        element_id: Uuid,
436    ) -> DataSetResult<bool> {
437        let property_schema = self
438            .schema
439            .find_property_schema(&path, schema_set.schemas())
440            .ok_or(DataSetError::SchemaNotFound)?;
441
442        if !property_schema.is_dynamic_array() {
443            return Err(DataSetError::InvalidSchema)?;
444        }
445
446        self.remove_dynamic_collection_entry(path, element_id)
447    }
448
449    pub fn remove_map_entry(
450        &mut self,
451        schema_set: &SchemaSet,
452        path: impl AsRef<str>,
453        element_id: Uuid,
454    ) -> DataSetResult<bool> {
455        let property_schema = self
456            .schema
457            .find_property_schema(&path, schema_set.schemas())
458            .ok_or(DataSetError::SchemaNotFound)?;
459
460        if !property_schema.is_map() {
461            return Err(DataSetError::InvalidSchema)?;
462        }
463
464        self.remove_dynamic_collection_entry(path, element_id)
465    }
466
467    fn do_resolve_dynamic_collection_entries(
468        &self,
469        path: &str,
470        resolved_entries: &mut Vec<Uuid>,
471    ) {
472        if let Some(entries) = self.dynamic_collection_entries.get(path) {
473            for entry in entries {
474                resolved_entries.push(*entry);
475            }
476        }
477    }
478
479    pub fn resolve_dynamic_array_entries(
480        &self,
481        schema_set: &SchemaSet,
482        path: impl AsRef<str>,
483    ) -> DataSetResult<Box<[Uuid]>> {
484        let property_schema = self.validate_parent_paths(schema_set, path.as_ref())?;
485        if !property_schema.is_dynamic_array() {
486            return Err(DataSetError::InvalidSchema)?;
487        }
488
489        let mut resolved_entries = vec![];
490        self.do_resolve_dynamic_collection_entries(path.as_ref(), &mut resolved_entries);
491        Ok(resolved_entries.into_boxed_slice())
492    }
493
494    pub fn resolve_map_entries(
495        &self,
496        schema_set: &SchemaSet,
497        path: impl AsRef<str>,
498    ) -> DataSetResult<Box<[Uuid]>> {
499        let property_schema = self.validate_parent_paths(schema_set, path.as_ref())?;
500        if !property_schema.is_map() {
501            return Err(DataSetError::InvalidSchema)?;
502        }
503
504        let mut resolved_entries = vec![];
505        self.do_resolve_dynamic_collection_entries(path.as_ref(), &mut resolved_entries);
506        Ok(resolved_entries.into_boxed_slice())
507    }
508}