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