hedl_core/
document.rs

1// Dweve HEDL - Hierarchical Entity Data Language
2//
3// Copyright (c) 2025 Dweve IP B.V. and individual contributors.
4//
5// SPDX-License-Identifier: Apache-2.0
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the LICENSE file at the
10// root of this repository or at: http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Document structure for parsed HEDL.
19//!
20//! # Memory Optimizations
21//!
22//! This module includes several memory optimizations:
23//! - SmallVec for inline field storage (avoids allocations for ≤4 fields)
24//! - Lazy BTreeMap allocation for children (None until first child added)
25//! - Compact child_count representation (u16 instead of Option<usize>)
26//! - Type names can be interned during parsing to reduce duplication
27
28use crate::Value;
29use smallvec::SmallVec;
30use std::collections::BTreeMap;
31
32/// A node in a matrix list.
33///
34/// # Memory Layout Optimizations
35///
36/// Size reduced from ~120 bytes to ~80 bytes per node:
37/// - `fields`: Uses SmallVec to inline up to 4 values (typical case)
38/// - `children`: Lazy allocation - None until first child added (saves 24 bytes for leaf nodes)
39/// - `child_count`: Uses u16 (2 bytes) with 0 = no hint (vs 16 bytes for Option<usize>)
40///
41/// For 10,000 nodes: ~400KB saved in struct overhead alone.
42#[derive(Debug, Clone, PartialEq)]
43pub struct Node {
44    /// The type name (from schema).
45    /// During parsing, this can be interned to share memory across all nodes of same type.
46    pub type_name: String,
47    /// The node's ID (first column value).
48    pub id: String,
49    /// Field values (aligned with schema columns).
50    /// SmallVec avoids heap allocation for ≤4 fields (common case: 90%+ of nodes).
51    pub fields: SmallVec<[Value; 4]>,
52    /// Child nodes grouped by type (from NEST relationships).
53    /// Lazy allocation - None until first child added. ~70% of nodes are leaves.
54    pub children: Option<Box<BTreeMap<String, Vec<Node>>>>,
55    /// Count of direct children (for LLM comprehension hints).
56    /// 0 = no hint provided. Max 65,535 children (sufficient for all practical cases).
57    pub child_count: u16,
58}
59
60impl Default for Node {
61    fn default() -> Self {
62        Self {
63            type_name: String::new(),
64            id: String::new(),
65            fields: SmallVec::new(),
66            children: None,
67            child_count: 0,
68        }
69    }
70}
71
72impl Node {
73    /// Create a new node.
74    pub fn new(type_name: impl Into<String>, id: impl Into<String>, fields: Vec<Value>) -> Self {
75        Self {
76            type_name: type_name.into(),
77            id: id.into(),
78            fields: SmallVec::from_vec(fields),
79            children: None,
80            child_count: 0,
81        }
82    }
83
84    /// Get a field value by column index.
85    pub fn get_field(&self, index: usize) -> Option<&Value> {
86        self.fields.get(index)
87    }
88
89    /// Add a child node.
90    /// Uses lazy allocation - children BTreeMap is only created on first add.
91    pub fn add_child(&mut self, child_type: impl Into<String>, child: Node) {
92        let children = self
93            .children
94            .get_or_insert_with(|| Box::new(BTreeMap::new()));
95        children.entry(child_type.into()).or_default().push(child);
96    }
97
98    /// Set the child count hint (for LLM comprehension).
99    /// Saturates at u16::MAX (65,535) if count exceeds.
100    pub fn set_child_count(&mut self, count: usize) {
101        self.child_count = count.min(u16::MAX as usize) as u16;
102    }
103
104    /// Get the child count hint, if provided.
105    pub fn get_child_count(&self) -> Option<usize> {
106        if self.child_count > 0 {
107            Some(self.child_count as usize)
108        } else {
109            None
110        }
111    }
112
113    /// Create a new node with a child count hint.
114    ///
115    /// # Arguments
116    /// - `type_name`: The type name from the schema
117    /// - `fields`: Field values where fields[0] MUST be the node ID (a string)
118    /// - `child_count`: Expected number of children (for LLM hints)
119    pub fn with_child_count(
120        type_name: impl Into<String>,
121        id: impl Into<String>,
122        fields: Vec<Value>,
123        child_count: usize,
124    ) -> Self {
125        Self {
126            type_name: type_name.into(),
127            id: id.into(),
128            fields: SmallVec::from_vec(fields),
129            children: None,
130            child_count: child_count.min(u16::MAX as usize) as u16,
131        }
132    }
133
134    /// Get the children map, if any children have been added.
135    pub fn children(&self) -> Option<&BTreeMap<String, Vec<Node>>> {
136        self.children.as_deref()
137    }
138
139    /// Get mutable access to children map, if it exists.
140    pub fn children_mut(&mut self) -> Option<&mut BTreeMap<String, Vec<Node>>> {
141        self.children.as_deref_mut()
142    }
143}
144
145/// A typed matrix list with schema.
146#[derive(Debug, Clone, PartialEq)]
147pub struct MatrixList {
148    /// The type name.
149    pub type_name: String,
150    /// Column names (schema).
151    pub schema: Vec<String>,
152    /// Row data as nodes.
153    pub rows: Vec<Node>,
154    /// Optional count hint for LLM comprehension (e.g., `teams(3): @Team`).
155    pub count_hint: Option<usize>,
156}
157
158impl MatrixList {
159    /// Create a new matrix list.
160    pub fn new(type_name: impl Into<String>, schema: Vec<String>) -> Self {
161        Self {
162            type_name: type_name.into(),
163            schema,
164            rows: Vec::new(),
165            count_hint: None,
166        }
167    }
168
169    /// Create a new matrix list with rows.
170    pub fn with_rows(type_name: impl Into<String>, schema: Vec<String>, rows: Vec<Node>) -> Self {
171        Self {
172            type_name: type_name.into(),
173            schema,
174            rows,
175            count_hint: None,
176        }
177    }
178
179    /// Create a new matrix list with a count hint.
180    pub fn with_count_hint(
181        type_name: impl Into<String>,
182        schema: Vec<String>,
183        count_hint: usize,
184    ) -> Self {
185        Self {
186            type_name: type_name.into(),
187            schema,
188            rows: Vec::new(),
189            count_hint: Some(count_hint),
190        }
191    }
192
193    /// Add a row/node to the list.
194    pub fn add_row(&mut self, node: Node) {
195        self.rows.push(node);
196    }
197
198    /// Get the number of columns.
199    pub fn column_count(&self) -> usize {
200        self.schema.len()
201    }
202}
203
204/// An item in the document body.
205#[derive(Debug, Clone, PartialEq)]
206pub enum Item {
207    /// A scalar value.
208    Scalar(Value),
209    /// A nested object.
210    Object(BTreeMap<String, Item>),
211    /// A matrix list.
212    List(MatrixList),
213}
214
215impl Item {
216    /// Try to get as a scalar value.
217    pub fn as_scalar(&self) -> Option<&Value> {
218        match self {
219            Self::Scalar(v) => Some(v),
220            _ => None,
221        }
222    }
223
224    /// Try to get as an object.
225    pub fn as_object(&self) -> Option<&BTreeMap<String, Item>> {
226        match self {
227            Self::Object(o) => Some(o),
228            _ => None,
229        }
230    }
231
232    /// Try to get as a matrix list.
233    pub fn as_list(&self) -> Option<&MatrixList> {
234        match self {
235            Self::List(l) => Some(l),
236            _ => None,
237        }
238    }
239}
240
241/// A parsed HEDL document.
242#[derive(Debug, Clone, PartialEq)]
243pub struct Document {
244    /// Version (major, minor).
245    pub version: (u32, u32),
246    /// Schema versions for types used in this document (for schema evolution).
247    /// Empty map means no schema versioning is used (legacy behavior).
248    pub schema_versions: BTreeMap<String, crate::schema_version::SchemaVersion>,
249    /// Alias definitions.
250    pub aliases: BTreeMap<String, String>,
251    /// Struct definitions (type -> columns).
252    pub structs: BTreeMap<String, Vec<String>>,
253    /// Nest relationships (parent -> child).
254    pub nests: BTreeMap<String, String>,
255    /// Root body content.
256    pub root: BTreeMap<String, Item>,
257}
258
259impl Document {
260    /// Create a new empty document.
261    pub fn new(version: (u32, u32)) -> Self {
262        Self {
263            version,
264            schema_versions: BTreeMap::new(),
265            aliases: BTreeMap::new(),
266            structs: BTreeMap::new(),
267            nests: BTreeMap::new(),
268            root: BTreeMap::new(),
269        }
270    }
271
272    /// Get schema version for a type.
273    pub fn get_schema_version(
274        &self,
275        type_name: &str,
276    ) -> Option<crate::schema_version::SchemaVersion> {
277        self.schema_versions.get(type_name).copied()
278    }
279
280    /// Set schema version for a type.
281    pub fn set_schema_version(
282        &mut self,
283        type_name: String,
284        version: crate::schema_version::SchemaVersion,
285    ) {
286        self.schema_versions.insert(type_name, version);
287    }
288
289    /// Get an item from the root by key.
290    pub fn get(&self, key: &str) -> Option<&Item> {
291        self.root.get(key)
292    }
293
294    /// Get a struct schema by type name.
295    pub fn get_schema(&self, type_name: &str) -> Option<&Vec<String>> {
296        self.structs.get(type_name)
297    }
298
299    /// Get the child type for a parent type (from NEST).
300    pub fn get_child_type(&self, parent_type: &str) -> Option<&String> {
301        self.nests.get(parent_type)
302    }
303
304    /// Expand an alias key to its value.
305    pub fn expand_alias(&self, key: &str) -> Option<&String> {
306        self.aliases.get(key)
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    // ==================== Node tests ====================
315
316    #[test]
317    fn test_node_new() {
318        let node = Node::new("User", "user-1", vec![Value::Int(42)]);
319        assert_eq!(node.type_name, "User");
320        assert_eq!(node.id, "user-1");
321        assert_eq!(node.fields.len(), 1);
322        assert!(node.children.is_none());
323    }
324
325    #[test]
326    fn test_node_get_field() {
327        let node = Node::new(
328            "User",
329            "1",
330            vec![Value::Int(1), Value::String("name".to_string().into())],
331        );
332        assert_eq!(node.get_field(0), Some(&Value::Int(1)));
333        assert_eq!(
334            node.get_field(1),
335            Some(&Value::String("name".to_string().into()))
336        );
337        assert_eq!(node.get_field(2), None);
338    }
339
340    #[test]
341    fn test_node_add_child() {
342        let mut parent = Node::new("User", "1", vec![]);
343        let child = Node::new("Post", "p1", vec![]);
344        parent.add_child("Post", child);
345
346        let children = parent.children().unwrap();
347        assert!(children.contains_key("Post"));
348        assert_eq!(children["Post"].len(), 1);
349    }
350
351    #[test]
352    fn test_node_add_multiple_children_same_type() {
353        let mut parent = Node::new("User", "1", vec![]);
354        parent.add_child("Post", Node::new("Post", "p1", vec![]));
355        parent.add_child("Post", Node::new("Post", "p2", vec![]));
356
357        let children = parent.children().unwrap();
358        assert_eq!(children["Post"].len(), 2);
359    }
360
361    #[test]
362    fn test_node_add_children_different_types() {
363        let mut parent = Node::new("User", "1", vec![]);
364        parent.add_child("Post", Node::new("Post", "p1", vec![]));
365        parent.add_child("Comment", Node::new("Comment", "c1", vec![]));
366
367        let children = parent.children().unwrap();
368        assert_eq!(children.len(), 2);
369        assert!(children.contains_key("Post"));
370        assert!(children.contains_key("Comment"));
371    }
372
373    #[test]
374    fn test_node_equality() {
375        let a = Node::new("User", "1", vec![Value::Int(42)]);
376        let b = Node::new("User", "1", vec![Value::Int(42)]);
377        assert_eq!(a, b);
378    }
379
380    #[test]
381    fn test_node_clone() {
382        let mut original = Node::new("User", "1", vec![Value::Int(42)]);
383        original.add_child("Post", Node::new("Post", "p1", vec![]));
384        let cloned = original.clone();
385        assert_eq!(original, cloned);
386    }
387
388    #[test]
389    fn test_node_debug() {
390        let node = Node::new("User", "1", vec![]);
391        let debug = format!("{:?}", node);
392        assert!(debug.contains("User"));
393        assert!(debug.contains("type_name"));
394    }
395
396    #[test]
397    fn test_node_child_count() {
398        let node = Node::with_child_count("User", "1", vec![], 5);
399        assert_eq!(node.get_child_count(), Some(5));
400    }
401
402    #[test]
403    fn test_node_no_child_count() {
404        let node = Node::new("User", "1", vec![]);
405        assert_eq!(node.get_child_count(), None);
406    }
407
408    #[test]
409    fn test_node_set_child_count() {
410        let mut node = Node::new("User", "1", vec![]);
411        node.set_child_count(10);
412        assert_eq!(node.get_child_count(), Some(10));
413    }
414
415    #[test]
416    fn test_node_child_count_saturation() {
417        let node = Node::with_child_count("User", "1", vec![], 100_000);
418        assert_eq!(node.get_child_count(), Some(65535)); // u16::MAX
419    }
420
421    #[test]
422    fn test_node_lazy_children() {
423        let node = Node::new("User", "1", vec![]);
424        assert!(node.children.is_none()); // Not allocated until first child
425    }
426
427    #[test]
428    fn test_node_children_accessor() {
429        let mut node = Node::new("User", "1", vec![]);
430        assert!(node.children().is_none());
431
432        node.add_child("Post", Node::new("Post", "p1", vec![]));
433        assert!(node.children().is_some());
434        assert!(node.children().unwrap().contains_key("Post"));
435    }
436
437    #[test]
438    fn test_node_children_mut_accessor() {
439        let mut node = Node::new("User", "1", vec![]);
440        node.add_child("Post", Node::new("Post", "p1", vec![]));
441
442        if let Some(children) = node.children_mut() {
443            children.insert("Comment".to_string(), vec![]);
444        }
445
446        assert!(node.children().unwrap().contains_key("Comment"));
447    }
448
449    // ==================== MatrixList tests ====================
450
451    #[test]
452    fn test_matrix_list_new() {
453        let list = MatrixList::new("User", vec!["id".to_string(), "name".to_string()]);
454        assert_eq!(list.type_name, "User");
455        assert_eq!(list.schema, vec!["id", "name"]);
456        assert!(list.rows.is_empty());
457    }
458
459    #[test]
460    fn test_matrix_list_add_row() {
461        let mut list = MatrixList::new("User", vec!["id".to_string()]);
462        list.add_row(Node::new("User", "1", vec![]));
463        assert_eq!(list.rows.len(), 1);
464    }
465
466    #[test]
467    fn test_matrix_list_column_count() {
468        let list = MatrixList::new(
469            "User",
470            vec!["a".to_string(), "b".to_string(), "c".to_string()],
471        );
472        assert_eq!(list.column_count(), 3);
473    }
474
475    #[test]
476    fn test_matrix_list_empty_schema() {
477        let list = MatrixList::new("Empty", vec![]);
478        assert_eq!(list.column_count(), 0);
479    }
480
481    #[test]
482    fn test_matrix_list_equality() {
483        let mut a = MatrixList::new("User", vec!["id".to_string()]);
484        a.add_row(Node::new("User", "1", vec![]));
485        let mut b = MatrixList::new("User", vec!["id".to_string()]);
486        b.add_row(Node::new("User", "1", vec![]));
487        assert_eq!(a, b);
488    }
489
490    #[test]
491    fn test_matrix_list_clone() {
492        let mut list = MatrixList::new("User", vec!["id".to_string()]);
493        list.add_row(Node::new("User", "1", vec![]));
494        let cloned = list.clone();
495        assert_eq!(list, cloned);
496    }
497
498    #[test]
499    fn test_matrix_list_with_rows() {
500        let rows = vec![
501            Node::new("User", "1", vec![]),
502            Node::new("User", "2", vec![]),
503        ];
504        let list = MatrixList::with_rows("User", vec!["id".to_string()], rows);
505        assert_eq!(list.rows.len(), 2);
506    }
507
508    #[test]
509    fn test_matrix_list_with_count_hint() {
510        let list = MatrixList::with_count_hint("User", vec!["id".to_string()], 100);
511        assert_eq!(list.count_hint, Some(100));
512    }
513
514    // ==================== Item tests ====================
515
516    #[test]
517    fn test_item_scalar() {
518        let item = Item::Scalar(Value::Int(42));
519        assert!(item.as_scalar().is_some());
520        assert!(item.as_object().is_none());
521        assert!(item.as_list().is_none());
522    }
523
524    #[test]
525    fn test_item_object() {
526        let mut obj = BTreeMap::new();
527        obj.insert("key".to_string(), Item::Scalar(Value::Int(1)));
528        let item = Item::Object(obj);
529        assert!(item.as_object().is_some());
530        assert!(item.as_scalar().is_none());
531    }
532
533    #[test]
534    fn test_item_list() {
535        let list = MatrixList::new("User", vec!["id".to_string()]);
536        let item = Item::List(list);
537        assert!(item.as_list().is_some());
538        assert!(item.as_scalar().is_none());
539    }
540
541    #[test]
542    fn test_item_as_scalar_returns_value() {
543        let item = Item::Scalar(Value::String("hello".to_string().into()));
544        let value = item.as_scalar().unwrap();
545        assert_eq!(value.as_str(), Some("hello"));
546    }
547
548    #[test]
549    fn test_item_as_object_returns_map() {
550        let mut obj = BTreeMap::new();
551        obj.insert("a".to_string(), Item::Scalar(Value::Int(1)));
552        let item = Item::Object(obj);
553        let map = item.as_object().unwrap();
554        assert!(map.contains_key("a"));
555    }
556
557    #[test]
558    fn test_item_equality() {
559        let a = Item::Scalar(Value::Int(42));
560        let b = Item::Scalar(Value::Int(42));
561        assert_eq!(a, b);
562    }
563
564    #[test]
565    fn test_item_clone() {
566        let item = Item::Scalar(Value::String("test".to_string().into()));
567        let cloned = item.clone();
568        assert_eq!(item, cloned);
569    }
570
571    // ==================== Document tests ====================
572
573    #[test]
574    fn test_document_new() {
575        let doc = Document::new((1, 0));
576        assert_eq!(doc.version, (1, 0));
577        assert!(doc.aliases.is_empty());
578        assert!(doc.structs.is_empty());
579        assert!(doc.nests.is_empty());
580        assert!(doc.root.is_empty());
581    }
582
583    #[test]
584    fn test_document_get() {
585        let mut doc = Document::new((1, 0));
586        doc.root
587            .insert("key".to_string(), Item::Scalar(Value::Int(42)));
588        assert!(doc.get("key").is_some());
589        assert!(doc.get("missing").is_none());
590    }
591
592    #[test]
593    fn test_document_get_schema() {
594        let mut doc = Document::new((1, 0));
595        doc.structs.insert(
596            "User".to_string(),
597            vec!["id".to_string(), "name".to_string()],
598        );
599        let schema = doc.get_schema("User").unwrap();
600        assert_eq!(schema, &vec!["id".to_string(), "name".to_string()]);
601        assert!(doc.get_schema("Missing").is_none());
602    }
603
604    #[test]
605    fn test_document_get_child_type() {
606        let mut doc = Document::new((1, 0));
607        doc.nests.insert("User".to_string(), "Post".to_string());
608        assert_eq!(doc.get_child_type("User"), Some(&"Post".to_string()));
609        assert!(doc.get_child_type("Post").is_none());
610    }
611
612    #[test]
613    fn test_document_expand_alias() {
614        let mut doc = Document::new((1, 0));
615        doc.aliases.insert("active".to_string(), "true".to_string());
616        assert_eq!(doc.expand_alias("active"), Some(&"true".to_string()));
617        assert!(doc.expand_alias("missing").is_none());
618    }
619
620    #[test]
621    fn test_document_equality() {
622        let a = Document::new((1, 0));
623        let b = Document::new((1, 0));
624        assert_eq!(a, b);
625    }
626
627    #[test]
628    fn test_document_clone() {
629        let mut doc = Document::new((1, 0));
630        doc.aliases.insert("key".to_string(), "value".to_string());
631        let cloned = doc.clone();
632        assert_eq!(doc, cloned);
633    }
634
635    #[test]
636    fn test_document_debug() {
637        let doc = Document::new((1, 0));
638        let debug = format!("{:?}", doc);
639        assert!(debug.contains("version"));
640        assert!(debug.contains("aliases"));
641    }
642
643    // ==================== Edge cases ====================
644
645    #[test]
646    fn test_node_empty_fields() {
647        let node = Node::new("Type", "id", vec![]);
648        assert!(node.fields.is_empty());
649        assert!(node.get_field(0).is_none());
650    }
651
652    #[test]
653    fn test_node_unicode_id() {
654        let node = Node::new("User", "日本語", vec![]);
655        assert_eq!(node.id, "日本語");
656    }
657
658    #[test]
659    fn test_document_version_zero() {
660        let doc = Document::new((0, 0));
661        assert_eq!(doc.version, (0, 0));
662    }
663
664    #[test]
665    fn test_document_large_version() {
666        let doc = Document::new((999, 999));
667        assert_eq!(doc.version, (999, 999));
668    }
669
670    #[test]
671    fn test_nested_items() {
672        let mut inner = BTreeMap::new();
673        inner.insert("nested".to_string(), Item::Scalar(Value::Int(42)));
674
675        let mut outer = BTreeMap::new();
676        outer.insert("inner".to_string(), Item::Object(inner));
677
678        let item = Item::Object(outer);
679        let obj = item.as_object().unwrap();
680        let inner_item = obj.get("inner").unwrap();
681        let inner_obj = inner_item.as_object().unwrap();
682        assert!(inner_obj.contains_key("nested"));
683    }
684
685    #[test]
686    fn test_deeply_nested_nodes() {
687        let mut root = Node::new("A", "a", vec![]);
688        let mut child = Node::new("B", "b", vec![]);
689        child.add_child("C", Node::new("C", "c", vec![]));
690        root.add_child("B", child);
691
692        let children = root.children().unwrap();
693        assert_eq!(children["B"].len(), 1);
694        let b_children = children["B"][0].children().unwrap();
695        assert_eq!(b_children["C"].len(), 1);
696    }
697
698    #[test]
699    fn test_node_default() {
700        let node = Node::default();
701        assert_eq!(node.type_name, "");
702        assert!(node.fields.is_empty());
703        assert!(node.children.is_none());
704        assert_eq!(node.child_count, 0);
705    }
706
707    // ==================== Document schema_versions tests ====================
708
709    #[test]
710    fn test_document_schema_versions_empty() {
711        let doc = Document::new((1, 0));
712        assert!(doc.schema_versions.is_empty());
713    }
714
715    #[test]
716    fn test_document_set_schema_version() {
717        use crate::schema_version::SchemaVersion;
718        let mut doc = Document::new((1, 0));
719        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 2, 3));
720        assert_eq!(doc.schema_versions.len(), 1);
721        assert!(doc.schema_versions.contains_key("User"));
722    }
723
724    #[test]
725    fn test_document_get_schema_version() {
726        use crate::schema_version::SchemaVersion;
727        let mut doc = Document::new((1, 0));
728        let version = SchemaVersion::new(2, 0, 0);
729        doc.set_schema_version("Post".to_string(), version);
730        assert_eq!(doc.get_schema_version("Post"), Some(version));
731        assert_eq!(doc.get_schema_version("User"), None);
732    }
733
734    #[test]
735    fn test_document_multiple_schema_versions() {
736        use crate::schema_version::SchemaVersion;
737        let mut doc = Document::new((1, 0));
738        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
739        doc.set_schema_version("Post".to_string(), SchemaVersion::new(2, 1, 0));
740        doc.set_schema_version("Comment".to_string(), SchemaVersion::new(1, 5, 2));
741
742        assert_eq!(doc.schema_versions.len(), 3);
743        assert_eq!(
744            doc.get_schema_version("User"),
745            Some(SchemaVersion::new(1, 0, 0))
746        );
747        assert_eq!(
748            doc.get_schema_version("Post"),
749            Some(SchemaVersion::new(2, 1, 0))
750        );
751        assert_eq!(
752            doc.get_schema_version("Comment"),
753            Some(SchemaVersion::new(1, 5, 2))
754        );
755    }
756
757    #[test]
758    fn test_document_replace_schema_version() {
759        use crate::schema_version::SchemaVersion;
760        let mut doc = Document::new((1, 0));
761        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
762        doc.set_schema_version("User".to_string(), SchemaVersion::new(2, 0, 0));
763
764        assert_eq!(doc.schema_versions.len(), 1);
765        assert_eq!(
766            doc.get_schema_version("User"),
767            Some(SchemaVersion::new(2, 0, 0))
768        );
769    }
770
771    #[test]
772    fn test_document_schema_version_with_clone() {
773        use crate::schema_version::SchemaVersion;
774        let mut doc = Document::new((1, 0));
775        doc.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
776        let cloned = doc.clone();
777
778        assert_eq!(cloned.schema_versions.len(), 1);
779        assert_eq!(
780            cloned.get_schema_version("User"),
781            Some(SchemaVersion::new(1, 0, 0))
782        );
783    }
784
785    #[test]
786    fn test_document_schema_version_equality() {
787        use crate::schema_version::SchemaVersion;
788        let mut a = Document::new((1, 0));
789        a.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
790
791        let mut b = Document::new((1, 0));
792        b.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
793
794        assert_eq!(a, b);
795    }
796
797    #[test]
798    fn test_document_schema_version_inequality() {
799        use crate::schema_version::SchemaVersion;
800        let mut a = Document::new((1, 0));
801        a.set_schema_version("User".to_string(), SchemaVersion::new(1, 0, 0));
802
803        let mut b = Document::new((1, 0));
804        b.set_schema_version("User".to_string(), SchemaVersion::new(2, 0, 0));
805
806        assert_ne!(a, b);
807    }
808}