eure_document/write/
record.rs

1//! RecordWriter for writing record types to Eure documents.
2
3extern crate alloc;
4
5use alloc::string::ToString;
6
7use crate::document::constructor::DocumentConstructor;
8use crate::path::PathSegment;
9use crate::value::ObjectKey;
10
11use super::{IntoDocument, WriteError};
12
13/// Helper for writing record (map with string keys) to Eure documents.
14///
15/// Used within the closure passed to [`DocumentConstructor::record`].
16///
17/// # Example
18///
19/// ```ignore
20/// c.record(|rec| {
21///     rec.field("name", "Alice")?;
22///     rec.field_optional("age", Some(30))?;
23///     Ok(())
24/// })?;
25/// ```
26pub struct RecordWriter<'a> {
27    constructor: &'a mut DocumentConstructor,
28}
29
30impl<'a> RecordWriter<'a> {
31    /// Create a new RecordWriter.
32    pub(crate) fn new(constructor: &'a mut DocumentConstructor) -> Self {
33        Self { constructor }
34    }
35
36    /// Write a required field.
37    ///
38    /// # Example
39    ///
40    /// ```ignore
41    /// rec.field("name", "Alice")?;
42    /// ```
43    pub fn field<T: IntoDocument>(&mut self, name: &str, value: T) -> Result<(), WriteError> {
44        let scope = self.constructor.begin_scope();
45        self.constructor
46            .navigate(PathSegment::Value(ObjectKey::String(name.to_string())))?;
47        value.write_to(self.constructor)?;
48        self.constructor.end_scope(scope)?;
49        Ok(())
50    }
51
52    /// Write an optional field.
53    /// Does nothing if the value is `None`.
54    ///
55    /// # Example
56    ///
57    /// ```ignore
58    /// rec.field_optional("age", self.age)?;
59    /// ```
60    pub fn field_optional<T: IntoDocument>(
61        &mut self,
62        name: &str,
63        value: Option<T>,
64    ) -> Result<(), WriteError> {
65        if let Some(v) = value {
66            self.field(name, v)?;
67        }
68        Ok(())
69    }
70
71    /// Write a field using a custom writer closure.
72    ///
73    /// Useful for nested structures that need custom handling.
74    ///
75    /// # Example
76    ///
77    /// ```ignore
78    /// rec.field_with("address", |c| {
79    ///     c.record(|rec| {
80    ///         rec.field("city", "Tokyo")?;
81    ///         Ok(())
82    ///     })
83    /// })?;
84    /// ```
85    pub fn field_with<F, T>(&mut self, name: &str, f: F) -> Result<T, WriteError>
86    where
87        F: FnOnce(&mut DocumentConstructor) -> Result<T, WriteError>,
88    {
89        let scope = self.constructor.begin_scope();
90        self.constructor
91            .navigate(PathSegment::Value(ObjectKey::String(name.to_string())))?;
92        let result = f(self.constructor)?;
93        self.constructor.end_scope(scope)?;
94        Ok(result)
95    }
96
97    /// Write an optional field using a custom writer closure.
98    /// Does nothing if the value is `None`.
99    ///
100    /// # Example
101    ///
102    /// ```ignore
103    /// rec.field_with_optional("metadata", self.metadata.as_ref(), |c, meta| {
104    ///     meta.write_to(c)
105    /// })?;
106    /// ```
107    pub fn field_with_optional<T, F, R>(
108        &mut self,
109        name: &str,
110        value: Option<T>,
111        f: F,
112    ) -> Result<Option<R>, WriteError>
113    where
114        F: FnOnce(&mut DocumentConstructor, T) -> Result<R, WriteError>,
115    {
116        if let Some(v) = value {
117            let result = self.field_with(name, |c| f(c, v))?;
118            Ok(Some(result))
119        } else {
120            Ok(None)
121        }
122    }
123
124    /// Get a mutable reference to the underlying DocumentConstructor.
125    ///
126    /// Useful for advanced use cases that need direct access.
127    pub fn constructor(&mut self) -> &mut DocumentConstructor {
128        self.constructor
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::document::node::NodeValue;
136    use crate::text::Text;
137    use crate::value::PrimitiveValue;
138
139    #[test]
140    fn test_field() {
141        let mut c = DocumentConstructor::new();
142        c.record(|rec| {
143            rec.field("name", "Alice")?;
144            Ok(())
145        })
146        .unwrap();
147        let doc = c.finish();
148        let map = doc.root().as_map().unwrap();
149        let name_id = map.get(&ObjectKey::String("name".to_string())).unwrap();
150        let node = doc.node(*name_id);
151        assert_eq!(
152            node.content,
153            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
154        );
155    }
156
157    #[test]
158    fn test_field_optional_some() {
159        let mut c = DocumentConstructor::new();
160        c.record(|rec| {
161            rec.field_optional("age", Some(30i32))?;
162            Ok(())
163        })
164        .unwrap();
165        let doc = c.finish();
166        let map = doc.root().as_map().unwrap();
167        assert!(map.get(&ObjectKey::String("age".to_string())).is_some());
168    }
169
170    #[test]
171    fn test_field_optional_none() {
172        let mut c = DocumentConstructor::new();
173        c.record(|rec| {
174            rec.field_optional::<i32>("age", None)?;
175            Ok(())
176        })
177        .unwrap();
178        let doc = c.finish();
179        let map = doc.root().as_map().unwrap();
180        assert!(map.get(&ObjectKey::String("age".to_string())).is_none());
181    }
182
183    #[test]
184    fn test_field_with() {
185        let mut c = DocumentConstructor::new();
186        c.record(|rec| {
187            rec.field_with("nested", |c| {
188                c.record(|rec| {
189                    rec.field("inner", "value")?;
190                    Ok(())
191                })
192            })?;
193            Ok(())
194        })
195        .unwrap();
196        let doc = c.finish();
197        let map = doc.root().as_map().unwrap();
198        let nested_id = map.get(&ObjectKey::String("nested".to_string())).unwrap();
199        let nested = doc.node(*nested_id).as_map().unwrap();
200        assert!(
201            nested
202                .get(&ObjectKey::String("inner".to_string()))
203                .is_some()
204        );
205    }
206
207    #[test]
208    fn test_multiple_fields() {
209        let mut c = DocumentConstructor::new();
210        c.record(|rec| {
211            rec.field("name", "Bob")?;
212            rec.field("age", 25i32)?;
213            rec.field("active", true)?;
214            Ok(())
215        })
216        .unwrap();
217        let doc = c.finish();
218        let map = doc.root().as_map().unwrap();
219        assert_eq!(map.len(), 3);
220    }
221}