Skip to main content

eure_document/write/
tuple.rs

1//! TupleWriter for writing tuple types to Eure documents.
2
3extern crate alloc;
4
5use crate::document::constructor::DocumentConstructor;
6use crate::path::PathSegment;
7
8use super::{IntoEure, WriteError};
9
10/// Helper for writing tuple types to Eure documents.
11///
12/// Used within the closure passed to [`DocumentConstructor::tuple`].
13/// Automatically tracks position, no manual index management needed.
14///
15/// # Example
16///
17/// ```ignore
18/// c.tuple(|t| {
19///     t.next("first")?;
20///     t.next(42)?;
21///     t.next(true)?;
22///     Ok(())
23/// })?;
24/// ```
25pub struct TupleWriter<'a> {
26    constructor: &'a mut DocumentConstructor,
27    position: u8,
28}
29
30impl<'a> TupleWriter<'a> {
31    /// Create a new TupleWriter.
32    pub(crate) fn new(constructor: &'a mut DocumentConstructor) -> Self {
33        Self {
34            constructor,
35            position: 0,
36        }
37    }
38
39    /// Write the next element, advancing position automatically.
40    ///
41    /// # Example
42    ///
43    /// ```ignore
44    /// t.next("value")?;
45    /// t.next(123)?;
46    /// ```
47    pub fn next<T: IntoEure>(&mut self, value: T) -> Result<(), T::Error> {
48        let scope = self.constructor.begin_scope();
49        self.constructor
50            .navigate(PathSegment::TupleIndex(self.position))
51            .map_err(WriteError::from)?;
52        T::write(value, self.constructor)?;
53        self.constructor
54            .end_scope(scope)
55            .map_err(WriteError::from)?;
56        self.position += 1;
57        Ok(())
58    }
59
60    /// Write the next element using a marker type, advancing position automatically.
61    ///
62    /// This enables writing types from external crates that can't implement
63    /// `IntoEure` directly due to Rust's orphan rule.
64    ///
65    /// # Example
66    ///
67    /// ```ignore
68    /// // DurationDef implements IntoEure<std::time::Duration>
69    /// t.next_via::<DurationDef, _>(duration)?;
70    /// ```
71    pub fn next_via<M, T>(&mut self, value: T) -> Result<(), M::Error>
72    where
73        M: IntoEure<T>,
74    {
75        let scope = self.constructor.begin_scope();
76        self.constructor
77            .navigate(PathSegment::TupleIndex(self.position))
78            .map_err(WriteError::from)?;
79        M::write(value, self.constructor)?;
80        self.constructor
81            .end_scope(scope)
82            .map_err(WriteError::from)?;
83        self.position += 1;
84        Ok(())
85    }
86
87    /// Write the next element using a custom writer closure.
88    ///
89    /// Useful for nested structures that need custom handling.
90    ///
91    /// # Example
92    ///
93    /// ```ignore
94    /// t.next_with(|c| {
95    ///     c.record(|rec| {
96    ///         rec.field("inner", "value")?;
97    ///         Ok(())
98    ///     })
99    /// })?;
100    /// ```
101    pub fn next_with<F, R>(&mut self, f: F) -> Result<R, WriteError>
102    where
103        F: FnOnce(&mut DocumentConstructor) -> Result<R, WriteError>,
104    {
105        let scope = self.constructor.begin_scope();
106        self.constructor
107            .navigate(PathSegment::TupleIndex(self.position))
108            .map_err(WriteError::from)?;
109        let result = f(self.constructor)?;
110        self.constructor
111            .end_scope(scope)
112            .map_err(WriteError::from)?;
113        self.position += 1;
114        Ok(result)
115    }
116
117    /// Get the current position (number of elements written).
118    pub fn position(&self) -> u8 {
119        self.position
120    }
121
122    /// Get a mutable reference to the underlying DocumentConstructor.
123    ///
124    /// Useful for advanced use cases that need direct access.
125    pub fn constructor(&mut self) -> &mut DocumentConstructor {
126        self.constructor
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use alloc::string::ToString;
133
134    use super::*;
135    use crate::document::node::NodeValue;
136    use crate::text::Text;
137    use crate::value::{ObjectKey, PrimitiveValue};
138
139    #[test]
140    fn test_next_sequential() {
141        let mut c = DocumentConstructor::new();
142        c.tuple(|t| {
143            t.next(1i32)?;
144            t.next("two")?;
145            t.next(true)
146        })
147        .unwrap();
148        let doc = c.finish();
149        let tuple = doc.root().as_tuple().unwrap();
150        assert_eq!(tuple.len(), 3);
151    }
152
153    #[test]
154    fn test_next_with_nested() {
155        let mut c = DocumentConstructor::new();
156        c.tuple(|t| {
157            t.next("first")?;
158            t.next_with(|c| {
159                c.record(|rec| {
160                    rec.field("inner", "value")?;
161                    Ok::<(), WriteError>(())
162                })
163            })
164        })
165        .unwrap();
166        let doc = c.finish();
167        let tuple = doc.root().as_tuple().unwrap();
168        assert_eq!(tuple.len(), 2);
169
170        // Check nested record
171        let nested_id = tuple.get(1).unwrap();
172        let nested = doc.node(nested_id).as_map().unwrap();
173        assert!(
174            nested
175                .get(&ObjectKey::String("inner".to_string()))
176                .is_some()
177        );
178    }
179
180    #[test]
181    fn test_position_tracking() {
182        let mut c = DocumentConstructor::new();
183        c.tuple(|t| {
184            assert_eq!(t.position(), 0);
185            t.next(1i32)?;
186            assert_eq!(t.position(), 1);
187            t.next(2i32)?;
188            assert_eq!(t.position(), 2);
189            Ok::<(), WriteError>(())
190        })
191        .unwrap();
192    }
193
194    #[test]
195    fn test_empty_tuple() {
196        let mut c = DocumentConstructor::new();
197        c.tuple(|_t| Ok::<(), WriteError>(())).unwrap();
198        let doc = c.finish();
199        let tuple = doc.root().as_tuple().unwrap();
200        assert!(tuple.is_empty());
201    }
202
203    #[test]
204    fn test_values_written_correctly() {
205        let mut c = DocumentConstructor::new();
206        c.tuple(|t| {
207            t.next(42i32)?;
208            t.next("hello")?;
209            Ok::<(), WriteError>(())
210        })
211        .unwrap();
212        let doc = c.finish();
213        let tuple = doc.root().as_tuple().unwrap();
214
215        // Check first element
216        let first_id = tuple.get(0).unwrap();
217        assert_eq!(
218            doc.node(first_id).content,
219            NodeValue::Primitive(PrimitiveValue::Integer(42.into()))
220        );
221
222        // Check second element
223        let second_id = tuple.get(1).unwrap();
224        assert_eq!(
225            doc.node(second_id).content,
226            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
227        );
228    }
229}