Skip to main content

tealeaf/
builder.rs

1//! Builder for constructing TeaLeaf documents from multiple DTOs.
2
3use indexmap::IndexMap;
4
5use crate::convert::ToTeaLeaf;
6use crate::{Schema, Union, TeaLeaf, Value};
7
8/// Builder for constructing TeaLeaf documents from multiple DTOs.
9///
10/// # Example
11///
12/// ```ignore
13/// use tealeaf::TeaLeafBuilder;
14///
15/// let doc = TeaLeafBuilder::new()
16///     .add("config", &config)
17///     .add_vec("users", &users)
18///     .build();
19/// ```
20pub struct TeaLeafBuilder {
21    schemas: IndexMap<String, Schema>,
22    unions: IndexMap<String, Union>,
23    data: IndexMap<String, Value>,
24    is_root_array: bool,
25}
26
27impl TeaLeafBuilder {
28    /// Create a new empty builder.
29    pub fn new() -> Self {
30        Self {
31            schemas: IndexMap::new(),
32            unions: IndexMap::new(),
33            data: IndexMap::new(),
34            is_root_array: false,
35        }
36    }
37
38    /// Add a single DTO under the given key.
39    pub fn add<T: ToTeaLeaf>(mut self, key: &str, dto: &T) -> Self {
40        self.schemas.extend(T::collect_schemas());
41        self.unions.extend(T::collect_unions());
42        self.data
43            .insert(key.to_string(), dto.to_tealeaf_value());
44        self
45    }
46
47    /// Add a raw Value under the given key (no schema collection).
48    pub fn add_value(mut self, key: &str, value: Value) -> Self {
49        self.data.insert(key.to_string(), value);
50        self
51    }
52
53    /// Add a schema definition.
54    pub fn add_schema(mut self, schema: Schema) -> Self {
55        self.schemas.insert(schema.name.clone(), schema);
56        self
57    }
58
59    /// Add a union definition.
60    pub fn add_union(mut self, union: Union) -> Self {
61        self.unions.insert(union.name.clone(), union);
62        self
63    }
64
65    /// Add a Vec of DTOs as an array under the given key.
66    pub fn add_vec<T: ToTeaLeaf>(mut self, key: &str, items: &[T]) -> Self {
67        self.schemas.extend(T::collect_schemas());
68        self.unions.extend(T::collect_unions());
69        let arr = Value::Array(items.iter().map(|i| i.to_tealeaf_value()).collect());
70        self.data.insert(key.to_string(), arr);
71        self
72    }
73
74    /// Mark the document as a root array (for JSON round-trip fidelity).
75    pub fn root_array(mut self) -> Self {
76        self.is_root_array = true;
77        self
78    }
79
80    /// Build the TeaLeaf document.
81    pub fn build(self) -> TeaLeaf {
82        let mut doc = TeaLeaf::new(self.schemas, self.data);
83        doc.unions = self.unions;
84        doc.set_root_array(self.is_root_array);
85        doc
86    }
87}
88
89impl Default for TeaLeafBuilder {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::FieldType;
99    #[allow(unused_imports)]
100    use crate::types::ObjectMap;
101
102    #[test]
103    fn test_builder_basic() {
104        let doc = TeaLeafBuilder::new()
105            .add_value("name", Value::String("test".into()))
106            .add_value("count", Value::Int(42))
107            .build();
108
109        assert_eq!(
110            doc.get("name"),
111            Some(&Value::String("test".into()))
112        );
113        assert_eq!(doc.get("count"), Some(&Value::Int(42)));
114    }
115
116    #[test]
117    fn test_builder_with_schema() {
118        let schema = crate::Schema::new("point")
119            .field("x", FieldType::new("float"))
120            .field("y", FieldType::new("float"));
121
122        let doc = TeaLeafBuilder::new()
123            .add_schema(schema)
124            .add_value("origin", Value::Object({
125                let mut m = ObjectMap::new();
126                m.insert("x".to_string(), Value::Float(0.0));
127                m.insert("y".to_string(), Value::Float(0.0));
128                m
129            }))
130            .build();
131
132        assert!(doc.schema("point").is_some());
133        assert!(doc.get("origin").is_some());
134    }
135
136    #[test]
137    fn test_builder_with_union() {
138        use crate::Variant;
139
140        let union = Union::new("Shape")
141            .variant(Variant::new("circle").field("radius", FieldType::new("float")))
142            .variant(Variant::new("rect").field("w", FieldType::new("float")).field("h", FieldType::new("float")));
143
144        let doc = TeaLeafBuilder::new()
145            .add_union(union)
146            .add_value("shape", Value::Tagged("circle".into(), Box::new(Value::Float(5.0))))
147            .build();
148
149        assert!(doc.union("Shape").is_some());
150        assert_eq!(doc.union("Shape").unwrap().variants.len(), 2);
151    }
152
153    #[test]
154    fn test_builder_default() {
155        let doc = TeaLeafBuilder::default().build();
156        assert!(doc.get("anything").is_none());
157    }
158}