Skip to main content

eure_document/
write.rs

1//! IntoEure trait for writing Rust types to Eure documents.
2
3extern crate alloc;
4
5pub mod record;
6pub mod tuple;
7
8pub use record::RecordWriter;
9pub use tuple::TupleWriter;
10
11use crate::document::constructor::DocumentConstructor;
12
13use alloc::borrow::{Cow, ToOwned};
14use alloc::string::String;
15use num_bigint::BigInt;
16
17use indexmap::IndexMap;
18
19use crate::document::InsertError;
20use crate::document::constructor::ScopeError;
21use crate::identifier::IdentifierError;
22use crate::parse::VariantPath;
23use crate::path::PathSegment;
24use crate::prelude_internal::*;
25use crate::text::Text;
26use crate::value::ValueKind;
27use core::any::type_name;
28
29/// Error type for write operations.
30#[derive(Debug, thiserror::Error, Clone)]
31pub enum WriteError {
32    /// Error during document insertion.
33    #[error("insert error: {0}")]
34    Insert(#[from] InsertError),
35
36    /// Error during scope management.
37    #[error("scope error: {0}")]
38    Scope(#[from] ScopeError),
39
40    /// Invalid identifier provided.
41    #[error("invalid identifier: {0}")]
42    InvalidIdentifier(String),
43
44    /// Invalid `$variant` extension type (must be text).
45    #[error("invalid $variant extension type: expected text, got {actual}")]
46    InvalidVariantExtensionType { actual: ValueKind },
47
48    /// Invalid `$variant` path syntax.
49    #[error("invalid $variant path: {source}")]
50    InvalidVariantPath { source: IdentifierError },
51
52    /// Unknown variant when writing a non-exhaustive proxy enum.
53    #[error("non-exhaustive enum variant for {type_name}")]
54    NonExhaustiveVariant { type_name: &'static str },
55
56    /// Flatten target cannot be written as record fields.
57    #[error("flatten target is not record-like: {type_name}")]
58    FlattenTargetNotRecordLike { type_name: &'static str },
59}
60
61/// Trait for writing Rust types to Eure documents.
62///
63/// Types implementing this trait can be serialized into [`EureDocument`]
64/// via [`DocumentConstructor`].
65///
66/// The generic parameter `T` defaults to `Self`, allowing remote type support
67/// via marker types:
68/// - `IntoEure` (same as `IntoEure<Self>`) - standard implementation
69/// - `IntoEure<RemoteType>` - marker type implements writing for a remote type
70///
71/// # Examples
72///
73/// Standard implementation:
74/// ```ignore
75/// impl IntoEure for User {
76///     type Error = WriteError;
77///
78///     fn write(value: User, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
79///         c.record(|rec| {
80///             rec.field("name", value.name)?;
81///             rec.field_optional("age", value.age)?;
82///             Ok::<(), WriteError>(())
83///         })
84///     }
85/// }
86/// ```
87///
88/// Remote type support via marker:
89/// ```ignore
90/// impl IntoEure<std::time::Duration> for DurationDef {
91///     type Error = WriteError;
92///
93///     fn write(value: std::time::Duration, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
94///         c.record(|rec| {
95///             rec.field("secs", value.as_secs())?;
96///             rec.field("nanos", value.subsec_nanos())?;
97///             Ok::<(), WriteError>(())
98///         })
99///     }
100/// }
101/// ```
102pub trait IntoEure<T = Self>: Sized {
103    /// The error type returned when writing.
104    ///
105    /// This must be able to represent `WriteError` so document-constructor
106    /// failures can be propagated through custom user errors.
107    type Error: From<WriteError>;
108
109    /// Write a value to the current node in the document constructor.
110    fn write(value: T, c: &mut DocumentConstructor) -> Result<(), Self::Error>;
111
112    /// Write a value as flattened record fields.
113    ///
114    /// The default implementation returns a runtime error. Types that are
115    /// record-like (e.g. named structs, map-like containers) should override
116    /// this to emit fields into `rec`.
117    fn write_flatten(value: T, rec: &mut RecordWriter<'_>) -> Result<(), Self::Error> {
118        let _ = value;
119        let _ = rec;
120        Err(WriteError::FlattenTargetNotRecordLike {
121            type_name: type_name::<T>(),
122        }
123        .into())
124    }
125}
126
127impl IntoEure for EureDocument {
128    type Error = WriteError;
129
130    fn write(value: Self, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
131        c.write_subtree(&value, value.get_root_id())
132    }
133}
134
135fn write_subtree_node(
136    src: &EureDocument,
137    node_id: NodeId,
138    c: &mut DocumentConstructor,
139) -> Result<(), WriteError> {
140    let node = src.node(node_id);
141
142    match &node.content {
143        NodeValue::Hole(label) => {
144            c.bind_hole(label.clone())?;
145        }
146        NodeValue::Primitive(prim) => {
147            c.bind_primitive(prim.clone())?;
148        }
149        NodeValue::Array(array) => {
150            c.bind_empty_array()?;
151            for &child_id in array.iter() {
152                let scope = c.begin_scope();
153                c.navigate(PathSegment::ArrayIndex(None))?;
154                write_subtree_node(src, child_id, c)?;
155                c.end_scope(scope)?;
156            }
157        }
158        NodeValue::Tuple(tuple) => {
159            c.bind_empty_tuple()?;
160            for (index, &child_id) in tuple.iter().enumerate() {
161                let scope = c.begin_scope();
162                c.navigate(PathSegment::TupleIndex(index as u8))?;
163                write_subtree_node(src, child_id, c)?;
164                c.end_scope(scope)?;
165            }
166        }
167        NodeValue::Map(map) => {
168            c.bind_empty_map()?;
169            for (key, &child_id) in map.iter() {
170                let scope = c.begin_scope();
171                c.navigate(PathSegment::Value(key.clone()))?;
172                write_subtree_node(src, child_id, c)?;
173                c.end_scope(scope)?;
174            }
175        }
176        NodeValue::PartialMap(map) => {
177            c.bind_empty_partial_map()?;
178            for (key, &child_id) in map.iter() {
179                let scope = c.begin_scope();
180                c.navigate_partial_map_entry(key.clone())?;
181                write_subtree_node(src, child_id, c)?;
182                c.end_scope(scope)?;
183            }
184        }
185    }
186
187    for (ident, &ext_node_id) in node.extensions.iter() {
188        let scope = c.begin_scope();
189        c.navigate(PathSegment::Extension(ident.clone()))?;
190        write_subtree_node(src, ext_node_id, c)?;
191        c.end_scope(scope)?;
192    }
193
194    Ok(())
195}
196
197// ============================================================================
198// Primitive implementations
199// ============================================================================
200
201impl IntoEure for bool {
202    type Error = WriteError;
203
204    fn write(value: bool, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
205        c.bind_primitive(PrimitiveValue::Bool(value))
206            .map_err(WriteError::from)?;
207        Ok(())
208    }
209}
210
211macro_rules! impl_into_eure_int {
212    ($($ty:ty),*) => {
213        $(
214            impl IntoEure for $ty {
215                type Error = WriteError;
216
217                fn write(value: $ty, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
218                    c.bind_primitive(PrimitiveValue::Integer(BigInt::from(value)))
219                        .map_err(WriteError::from)?;
220                    Ok(())
221                }
222            }
223        )*
224    };
225}
226
227impl_into_eure_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
228
229impl IntoEure for f32 {
230    type Error = WriteError;
231
232    fn write(value: f32, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
233        c.bind_primitive(PrimitiveValue::F32(value))
234            .map_err(WriteError::from)?;
235        Ok(())
236    }
237}
238
239impl IntoEure for f64 {
240    type Error = WriteError;
241
242    fn write(value: f64, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
243        c.bind_primitive(PrimitiveValue::F64(value))
244            .map_err(WriteError::from)?;
245        Ok(())
246    }
247}
248
249impl IntoEure for BigInt {
250    type Error = WriteError;
251
252    fn write(value: BigInt, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
253        c.bind_primitive(PrimitiveValue::Integer(value))
254            .map_err(WriteError::from)?;
255        Ok(())
256    }
257}
258
259impl IntoEure for String {
260    type Error = WriteError;
261
262    fn write(value: String, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
263        c.bind_primitive(PrimitiveValue::Text(Text::plaintext(value)))
264            .map_err(WriteError::from)?;
265        Ok(())
266    }
267}
268
269impl<'a> IntoEure for &'a str {
270    type Error = WriteError;
271
272    fn write(value: &'a str, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
273        c.bind_primitive(PrimitiveValue::Text(Text::plaintext(value)))
274            .map_err(WriteError::from)?;
275        Ok(())
276    }
277}
278
279impl<'a, T> IntoEure<Cow<'a, T>> for Cow<'a, T>
280where
281    T: ToOwned + ?Sized,
282    T::Owned: IntoEure,
283{
284    type Error = <T::Owned as IntoEure>::Error;
285
286    fn write(value: Cow<'a, T>, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
287        <T::Owned as IntoEure>::write(value.into_owned(), c)
288    }
289}
290
291impl IntoEure for Text {
292    type Error = WriteError;
293
294    fn write(value: Text, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
295        c.bind_primitive(PrimitiveValue::Text(value))
296            .map_err(WriteError::from)?;
297        Ok(())
298    }
299}
300
301impl IntoEure for PrimitiveValue {
302    type Error = WriteError;
303
304    fn write(value: PrimitiveValue, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
305        c.bind_primitive(value).map_err(WriteError::from)?;
306        Ok(())
307    }
308}
309
310impl IntoEure for Identifier {
311    type Error = WriteError;
312
313    fn write(value: Identifier, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
314        c.bind_primitive(PrimitiveValue::Text(Text::plaintext(value.into_string())))
315            .map_err(WriteError::from)?;
316        Ok(())
317    }
318}
319
320// ============================================================================
321// Collection implementations
322// ============================================================================
323
324impl<M, T> IntoEure<Vec<T>> for Vec<M>
325where
326    M: IntoEure<T>,
327{
328    type Error = M::Error;
329
330    fn write(value: Vec<T>, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
331        c.bind_empty_array().map_err(WriteError::from)?;
332        for item in value {
333            let scope = c.begin_scope();
334            c.navigate(PathSegment::ArrayIndex(None))
335                .map_err(WriteError::from)?;
336            M::write(item, c)?;
337            c.end_scope(scope).map_err(WriteError::from)?;
338        }
339        Ok(())
340    }
341}
342
343impl<M, T, const N: usize> IntoEure<[T; N]> for [M; N]
344where
345    M: IntoEure<T>,
346{
347    type Error = M::Error;
348
349    fn write(value: [T; N], c: &mut DocumentConstructor) -> Result<(), Self::Error> {
350        c.bind_empty_array().map_err(WriteError::from)?;
351        for item in value {
352            let scope = c.begin_scope();
353            c.navigate(PathSegment::ArrayIndex(None))
354                .map_err(WriteError::from)?;
355            M::write(item, c)?;
356            c.end_scope(scope).map_err(WriteError::from)?;
357        }
358        Ok(())
359    }
360}
361
362impl<M, K, V> IntoEure<Map<K, V>> for Map<K, M>
363where
364    M: IntoEure<V>,
365    K: Into<ObjectKey>,
366{
367    type Error = M::Error;
368
369    fn write(value: Map<K, V>, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
370        c.bind_empty_map().map_err(WriteError::from)?;
371        for (key, v) in value {
372            let scope = c.begin_scope();
373            c.navigate(PathSegment::Value(key.into()))
374                .map_err(WriteError::from)?;
375            M::write(v, c)?;
376            c.end_scope(scope).map_err(WriteError::from)?;
377        }
378        Ok(())
379    }
380
381    fn write_flatten(value: Map<K, V>, rec: &mut RecordWriter<'_>) -> Result<(), Self::Error> {
382        for (key, v) in value {
383            let key = match key.into() {
384                ObjectKey::String(name) => name,
385                _ => {
386                    return Err(WriteError::FlattenTargetNotRecordLike {
387                        type_name: type_name::<Map<K, V>>(),
388                    }
389                    .into());
390                }
391            };
392            rec.field_via::<M, _>(&key, v)?;
393        }
394        Ok(())
395    }
396}
397
398impl<M, K, V> IntoEure<IndexMap<K, V>> for IndexMap<K, M>
399where
400    M: IntoEure<V>,
401    K: Into<ObjectKey> + Eq + std::hash::Hash,
402{
403    type Error = M::Error;
404
405    fn write(value: IndexMap<K, V>, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
406        c.bind_empty_map().map_err(WriteError::from)?;
407        for (key, v) in value {
408            let scope = c.begin_scope();
409            c.navigate(PathSegment::Value(key.into()))
410                .map_err(WriteError::from)?;
411            M::write(v, c)?;
412            c.end_scope(scope).map_err(WriteError::from)?;
413        }
414        Ok(())
415    }
416
417    fn write_flatten(value: IndexMap<K, V>, rec: &mut RecordWriter<'_>) -> Result<(), Self::Error> {
418        for (key, v) in value {
419            let key = match key.into() {
420                ObjectKey::String(name) => name,
421                _ => {
422                    return Err(WriteError::FlattenTargetNotRecordLike {
423                        type_name: type_name::<IndexMap<K, V>>(),
424                    }
425                    .into());
426                }
427            };
428            rec.field_via::<M, _>(&key, v)?;
429        }
430        Ok(())
431    }
432}
433
434impl<M, T> IntoEure<Option<T>> for Option<M>
435where
436    M: IntoEure<T>,
437{
438    type Error = M::Error;
439
440    fn write(value: Option<T>, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
441        match value {
442            Some(v) => M::write(v, c),
443            None => {
444                c.bind_primitive(PrimitiveValue::Null)
445                    .map_err(WriteError::from)?;
446                Ok(())
447            }
448        }
449    }
450}
451
452// ============================================================================
453// Tuple implementations
454// ============================================================================
455
456macro_rules! impl_into_document_tuple {
457    ($n:expr, $($idx:tt: $marker:ident : $ty:ident),+) => {
458        impl<$($marker, $ty),+, __E> IntoEure<($($ty,)+)> for ($($marker,)+)
459        where
460            $($marker: IntoEure<$ty, Error = __E>,)+
461            __E: From<WriteError>,
462        {
463            type Error = __E;
464
465            fn write(value: ($($ty,)+), c: &mut DocumentConstructor) -> Result<(), Self::Error> {
466                c.bind_empty_tuple().map_err(WriteError::from)?;
467                $(
468                    let scope = c.begin_scope();
469                    c.navigate(PathSegment::TupleIndex($idx)).map_err(WriteError::from)?;
470                    $marker::write(value.$idx, c)?;
471                    c.end_scope(scope).map_err(WriteError::from)?;
472                )+
473                Ok(())
474            }
475        }
476    };
477}
478
479impl_into_document_tuple!(1, 0: MA: A);
480impl_into_document_tuple!(2, 0: MA: A, 1: MB: B);
481impl_into_document_tuple!(3, 0: MA: A, 1: MB: B, 2: MC: C);
482impl_into_document_tuple!(4, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D);
483impl_into_document_tuple!(5, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E);
484impl_into_document_tuple!(6, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F);
485impl_into_document_tuple!(7, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G);
486impl_into_document_tuple!(8, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H);
487impl_into_document_tuple!(9, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I);
488impl_into_document_tuple!(10, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J);
489impl_into_document_tuple!(11, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J, 10: MK: K);
490impl_into_document_tuple!(12, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J, 10: MK: K, 11: ML: L);
491impl_into_document_tuple!(13, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J, 10: MK: K, 11: ML: L, 12: MM: M);
492impl_into_document_tuple!(14, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J, 10: MK: K, 11: ML: L, 12: MM: M, 13: MN: N);
493impl_into_document_tuple!(15, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J, 10: MK: K, 11: ML: L, 12: MM: M, 13: MN: N, 14: MO: O);
494impl_into_document_tuple!(16, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F, 6: MG: G, 7: MH: H, 8: MI: I, 9: MJ: J, 10: MK: K, 11: ML: L, 12: MM: M, 13: MN: N, 14: MO: O, 15: MP: P);
495
496// ============================================================================
497// DocumentConstructor extensions
498// ============================================================================
499
500impl DocumentConstructor {
501    /// Write a record (map with string keys) using a closure.
502    ///
503    /// # Example
504    ///
505    /// ```ignore
506    /// c.record(|rec| {
507    ///     rec.field("name", "Alice")?;
508    ///     rec.field("age", 30)?;
509    ///     Ok(())
510    /// })?;
511    /// ```
512    pub fn record<F, T, E>(&mut self, f: F) -> Result<T, E>
513    where
514        F: FnOnce(&mut RecordWriter<'_>) -> Result<T, E>,
515        E: From<WriteError>,
516    {
517        self.bind_empty_map().map_err(WriteError::from)?;
518        let mut writer = RecordWriter::new(self);
519        f(&mut writer)
520    }
521
522    /// Write a tuple using a closure.
523    ///
524    /// # Example
525    ///
526    /// ```ignore
527    /// c.tuple(|t| {
528    ///     t.next("first")?;
529    ///     t.next(42)?;
530    ///     t.next(true)?;
531    ///     Ok(())
532    /// })?;
533    /// ```
534    pub fn tuple<F, T, E>(&mut self, f: F) -> Result<T, E>
535    where
536        F: FnOnce(&mut TupleWriter<'_>) -> Result<T, E>,
537        E: From<WriteError>,
538    {
539        self.bind_empty_tuple().map_err(WriteError::from)?;
540        let mut writer = TupleWriter::new(self);
541        f(&mut writer)
542    }
543
544    /// Set an extension value on the current node.
545    ///
546    /// # Example
547    ///
548    /// ```ignore
549    /// c.set_extension("optional", true)?;
550    /// ```
551    pub fn set_extension<T: IntoEure>(&mut self, name: &str, value: T) -> Result<(), T::Error> {
552        let ident: Identifier = name
553            .parse()
554            .map_err(|_| WriteError::InvalidIdentifier(name.into()))?;
555        let scope = self.begin_scope();
556        self.navigate(PathSegment::Extension(ident))
557            .map_err(WriteError::from)?;
558        T::write(value, self)?;
559        self.end_scope(scope).map_err(WriteError::from)?;
560        Ok(())
561    }
562
563    /// Set an optional extension value on the current node.
564    /// Does nothing if the value is `None`.
565    ///
566    /// # Example
567    ///
568    /// ```ignore
569    /// c.set_extension_optional("default", self.default)?;
570    /// ```
571    pub fn set_extension_optional<T: IntoEure>(
572        &mut self,
573        name: &str,
574        value: Option<T>,
575    ) -> Result<(), T::Error> {
576        if let Some(v) = value {
577            self.set_extension(name, v)?;
578        }
579        Ok(())
580    }
581
582    /// Set the `$variant` extension for union types.
583    ///
584    /// If called multiple times on the same node (nested unions), the variant
585    /// path is appended using `.` (e.g., `outer.inner.leaf`).
586    ///
587    /// # Example
588    ///
589    /// ```ignore
590    /// match self {
591    ///     MyEnum::Foo(inner) => {
592    ///         c.set_variant("foo")?;
593    ///         c.write(inner)?;
594    ///     }
595    /// }
596    /// ```
597    pub fn set_variant(&mut self, variant: &str) -> Result<(), WriteError> {
598        VariantPath::parse(variant)
599            .map_err(|err| WriteError::InvalidVariantPath { source: err })?;
600        let current_id = self.current_node_id();
601        if let Some(variant_node_id) = self
602            .document()
603            .node(current_id)
604            .get_extension(&Identifier::VARIANT)
605        {
606            let node = self.document().node(variant_node_id);
607            let existing = match node.as_primitive().and_then(|value| value.as_str()) {
608                Some(existing) => existing,
609                None => {
610                    let actual = node.content.value_kind();
611                    return Err(WriteError::InvalidVariantExtensionType { actual });
612                }
613            };
614            VariantPath::parse(existing)
615                .map_err(|err| WriteError::InvalidVariantPath { source: err })?;
616            let mut combined = String::with_capacity(existing.len() + 1 + variant.len());
617            combined.push_str(existing);
618            combined.push('.');
619            combined.push_str(variant);
620            self.document_mut().node_mut(variant_node_id).content =
621                NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(combined)));
622            Ok(())
623        } else {
624            self.set_extension("variant", variant)
625        }
626    }
627
628    /// Copy a subtree from another `EureDocument` into the current position.
629    ///
630    /// This recursively copies the node at `node_id` from `src`, including all
631    /// children and extensions, into the current node of this constructor.
632    pub fn write_subtree(&mut self, src: &EureDocument, node_id: NodeId) -> Result<(), WriteError> {
633        write_subtree_node(src, node_id, self)
634    }
635
636    /// Write a value implementing `IntoEure` to the current node.
637    ///
638    /// # Example
639    ///
640    /// ```ignore
641    /// c.write(my_value)?;
642    /// ```
643    pub fn write<T: IntoEure>(&mut self, value: T) -> Result<(), T::Error> {
644        T::write(value, self)
645    }
646
647    /// Write a remote type using a marker type.
648    ///
649    /// This enables writing types from external crates that can't implement
650    /// `IntoEure` directly due to Rust's orphan rule.
651    ///
652    /// # Example
653    ///
654    /// ```ignore
655    /// // DurationDef implements IntoEure<std::time::Duration>
656    /// c.write_via::<DurationDef, _>(duration)?;
657    /// ```
658    pub fn write_via<M, T>(&mut self, value: T) -> Result<(), M::Error>
659    where
660        M: IntoEure<T>,
661    {
662        M::write(value, self)
663    }
664}
665
666#[cfg(test)]
667mod tests {
668    use super::*;
669
670    #[test]
671    fn test_primitive_bool() {
672        let mut c = DocumentConstructor::new();
673        c.write(true).unwrap();
674        let doc = c.finish();
675        assert_eq!(
676            doc.root().content,
677            NodeValue::Primitive(PrimitiveValue::Bool(true))
678        );
679    }
680
681    #[test]
682    fn test_primitive_string() {
683        let mut c = DocumentConstructor::new();
684        c.write("hello").unwrap();
685        let doc = c.finish();
686        assert_eq!(
687            doc.root().content,
688            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
689        );
690    }
691
692    #[test]
693    fn test_vec() {
694        let mut c = DocumentConstructor::new();
695        c.write(vec![1i32, 2, 3]).unwrap();
696        let doc = c.finish();
697        let arr = doc.root().as_array().unwrap();
698        assert_eq!(arr.len(), 3);
699    }
700
701    #[test]
702    fn test_tuple() {
703        let mut c = DocumentConstructor::new();
704        c.write((1i32, "two", true)).unwrap();
705        let doc = c.finish();
706        let tuple = doc.root().as_tuple().unwrap();
707        assert_eq!(tuple.len(), 3);
708    }
709
710    #[test]
711    fn test_tuple_roundtrip() {
712        let original = (42i32, "hello".to_string(), true);
713
714        // Write
715        let mut c = DocumentConstructor::new();
716        c.write(original.clone()).unwrap();
717        let doc = c.finish();
718
719        assert_eq!(eure!({=(42i32, "hello", true)}), doc);
720
721        // Parse back
722        let root_id = doc.get_root_id();
723        let parsed: (i32, String, bool) = doc.parse(root_id).unwrap();
724
725        assert_eq!(parsed, original);
726    }
727
728    #[test]
729    fn test_record() {
730        let mut c = DocumentConstructor::new();
731        c.record(|rec| {
732            rec.field("name", "Alice")?;
733            rec.field("age", 30i32)?;
734            Ok::<(), WriteError>(())
735        })
736        .unwrap();
737        let doc = c.finish();
738        let map = doc.root().as_map().unwrap();
739        assert_eq!(map.len(), 2);
740    }
741
742    #[test]
743    fn test_set_extension() {
744        let mut c = DocumentConstructor::new();
745        c.record(|rec| {
746            rec.field("type", "string")?;
747            Ok::<(), WriteError>(())
748        })
749        .unwrap();
750        c.set_extension("optional", true).unwrap();
751        let doc = c.finish();
752
753        let root = doc.root();
754        assert!(
755            root.extensions
756                .contains_key(&"optional".parse::<Identifier>().unwrap())
757        );
758    }
759
760    #[test]
761    fn test_set_variant() {
762        let mut c = DocumentConstructor::new();
763        c.set_variant("foo").unwrap();
764        c.record(|rec| {
765            rec.field("value", 42i32)?;
766            Ok::<(), WriteError>(())
767        })
768        .unwrap();
769        let doc = c.finish();
770
771        let root = doc.root();
772        assert!(
773            root.extensions
774                .contains_key(&"variant".parse::<Identifier>().unwrap())
775        );
776    }
777
778    #[test]
779    fn test_set_variant_invalid_extension_type() {
780        let mut c = DocumentConstructor::new();
781        c.set_extension("variant", 1i32).unwrap();
782        let err = c.set_variant("foo").unwrap_err();
783        assert!(matches!(
784            err,
785            WriteError::InvalidVariantExtensionType {
786                actual: ValueKind::Integer
787            }
788        ));
789    }
790
791    #[test]
792    fn test_nested_record() {
793        let mut c = DocumentConstructor::new();
794        c.record(|rec| {
795            rec.field("name", "Alice")?;
796            rec.field_with("address", |c| {
797                c.record(|rec| {
798                    rec.field("city", "Tokyo")?;
799                    rec.field("zip", "100-0001")?;
800                    Ok::<(), WriteError>(())
801                })
802            })?;
803            Ok::<(), WriteError>(())
804        })
805        .unwrap();
806        let doc = c.finish();
807        let map = doc.root().as_map().unwrap();
808        assert_eq!(map.len(), 2);
809    }
810
811    #[test]
812    fn test_write_via() {
813        // Marker type for Duration-like struct
814        struct DurationMarker;
815        struct DurationLike {
816            secs: u64,
817            nanos: u32,
818        }
819
820        impl IntoEure<DurationLike> for DurationMarker {
821            type Error = WriteError;
822
823            fn write(value: DurationLike, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
824                c.record(|rec| {
825                    rec.field("secs", value.secs)?;
826                    rec.field("nanos", value.nanos)?;
827                    Ok::<(), WriteError>(())
828                })
829            }
830        }
831
832        let mut c = DocumentConstructor::new();
833        c.write_via::<DurationMarker, _>(DurationLike {
834            secs: 60,
835            nanos: 123,
836        })
837        .unwrap();
838        let doc = c.finish();
839        let map = doc.root().as_map().unwrap();
840        assert_eq!(map.len(), 2);
841    }
842
843    #[test]
844    fn test_array_write() {
845        let mut c = DocumentConstructor::new();
846        c.write([1i32, 2, 3]).unwrap();
847        let doc = c.finish();
848        let arr = doc.root().as_array().unwrap();
849        assert_eq!(arr.len(), 3);
850    }
851
852    #[test]
853    fn test_array_empty_write() {
854        let mut c = DocumentConstructor::new();
855        let empty: [i32; 0] = [];
856        c.write(empty).unwrap();
857        let doc = c.finish();
858        let arr = doc.root().as_array().unwrap();
859        assert_eq!(arr.len(), 0);
860    }
861
862    #[test]
863    fn test_array_roundtrip() {
864        let original: [i32; 3] = [10, 20, 30];
865
866        // Write
867        let mut c = DocumentConstructor::new();
868        c.write(original).unwrap();
869        let doc = c.finish();
870
871        // Parse back
872        let root_id = doc.get_root_id();
873        let parsed: [i32; 3] = doc.parse(root_id).unwrap();
874
875        assert_eq!(parsed, original);
876    }
877
878    // =========================================================================
879    // Regex tests
880    // =========================================================================
881
882    #[test]
883    fn test_regex_roundtrip() {
884        let original = regex::Regex::new(r"^[a-z]+\d{2,4}$").unwrap();
885
886        // Write
887        let mut c = DocumentConstructor::new();
888        c.write(original.clone()).unwrap();
889        let doc = c.finish();
890
891        // Parse back
892        let root_id = doc.get_root_id();
893        let parsed: regex::Regex = doc.parse(root_id).unwrap();
894
895        assert_eq!(parsed.as_str(), original.as_str());
896    }
897
898    // =========================================================================
899    // Cow tests
900    // =========================================================================
901
902    #[test]
903    fn test_cow_borrowed_str() {
904        let mut c = DocumentConstructor::new();
905        let value: Cow<'_, str> = Cow::Borrowed("hello");
906        c.write(value).unwrap();
907        let doc = c.finish();
908        assert_eq!(
909            doc.root().content,
910            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
911        );
912    }
913
914    #[test]
915    fn test_cow_owned_str() {
916        let mut c = DocumentConstructor::new();
917        let value: Cow<'static, str> = Cow::Owned("hello".to_string());
918        c.write(value).unwrap();
919        let doc = c.finish();
920        assert_eq!(
921            doc.root().content,
922            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
923        );
924    }
925
926    // =========================================================================
927    // IntoEure for EureDocument tests
928    // =========================================================================
929
930    #[test]
931    fn test_document_write_primitive() {
932        let src = eure!({ = "hello" });
933        let mut c = DocumentConstructor::new();
934        c.write(src.clone()).unwrap();
935        let result = c.finish();
936        assert_eq!(result, src);
937    }
938
939    #[test]
940    fn test_document_write_map() {
941        let src = eure!({
942            name = "Alice"
943            age = 30
944        });
945        let mut c = DocumentConstructor::new();
946        c.write(src.clone()).unwrap();
947        let result = c.finish();
948        assert_eq!(result, src);
949    }
950
951    #[test]
952    fn test_document_write_array() {
953        let src = eure!({ = [1, 2, 3] });
954        let mut c = DocumentConstructor::new();
955        c.write(src.clone()).unwrap();
956        let result = c.finish();
957        assert_eq!(result, src);
958    }
959
960    #[test]
961    fn test_document_write_nested() {
962        let src = eure!({
963            name = "Alice"
964            address {
965                city = "Tokyo"
966                zip = "100-0001"
967            }
968            tags = ["a", "b"]
969        });
970        let mut c = DocumentConstructor::new();
971        c.write(src.clone()).unwrap();
972        let result = c.finish();
973        assert_eq!(result, src);
974    }
975
976    #[test]
977    fn test_document_write_as_field() {
978        let inner = eure!({ city = "Tokyo" });
979        let mut c = DocumentConstructor::new();
980        c.record(|rec| {
981            rec.field("name", "Alice")?;
982            rec.field("address", inner)?;
983            Ok::<(), WriteError>(())
984        })
985        .unwrap();
986        let result = c.finish();
987        let expected = eure!({
988            name = "Alice"
989            address { city = "Tokyo" }
990        });
991        assert_eq!(result, expected);
992    }
993
994    #[test]
995    fn test_document_write_subtree() {
996        let src = eure!({
997            name = "Alice"
998            active = true
999        });
1000        let mut c = DocumentConstructor::new();
1001        c.write_subtree(&src, src.get_root_id()).unwrap();
1002        let result = c.finish();
1003        assert_eq!(result, src);
1004    }
1005}