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 alloc::borrow::{Cow, ToOwned};
12use alloc::string::String;
13use num_bigint::BigInt;
14
15use crate::document::InsertError;
16use crate::document::constructor::{DocumentConstructor, ScopeError};
17use crate::identifier::IdentifierError;
18use crate::parse::VariantPath;
19use crate::path::PathSegment;
20use crate::prelude_internal::*;
21use crate::text::Text;
22use crate::value::ValueKind;
23
24/// Error type for write operations.
25#[derive(Debug, thiserror::Error, Clone)]
26pub enum WriteError {
27    /// Error during document insertion.
28    #[error("insert error: {0}")]
29    Insert(#[from] InsertError),
30
31    /// Error during scope management.
32    #[error("scope error: {0}")]
33    Scope(#[from] ScopeError),
34
35    /// Invalid identifier provided.
36    #[error("invalid identifier: {0}")]
37    InvalidIdentifier(String),
38
39    /// Invalid `$variant` extension type (must be text).
40    #[error("invalid $variant extension type: expected text, got {actual}")]
41    InvalidVariantExtensionType { actual: ValueKind },
42
43    /// Invalid `$variant` path syntax.
44    #[error("invalid $variant path: {source}")]
45    InvalidVariantPath { source: IdentifierError },
46
47    /// Unknown variant when writing a non-exhaustive proxy enum.
48    #[error("non-exhaustive enum variant for {type_name}")]
49    NonExhaustiveVariant { type_name: &'static str },
50}
51
52/// Trait for writing Rust types to Eure documents.
53///
54/// Types implementing this trait can be serialized into [`EureDocument`]
55/// via [`DocumentConstructor`].
56///
57/// The generic parameter `T` defaults to `Self`, allowing remote type support
58/// via marker types:
59/// - `IntoEure` (same as `IntoEure<Self>`) - standard implementation
60/// - `IntoEure<RemoteType>` - marker type implements writing for a remote type
61///
62/// # Examples
63///
64/// Standard implementation:
65/// ```ignore
66/// impl IntoEure for User {
67///     fn write(value: User, c: &mut DocumentConstructor) -> Result<(), WriteError> {
68///         c.record(|rec| {
69///             rec.field("name", value.name)?;
70///             rec.field_optional("age", value.age)?;
71///             Ok(())
72///         })
73///     }
74/// }
75/// ```
76///
77/// Remote type support via marker:
78/// ```ignore
79/// impl IntoEure<std::time::Duration> for DurationDef {
80///     fn write(value: std::time::Duration, c: &mut DocumentConstructor) -> Result<(), WriteError> {
81///         c.record(|rec| {
82///             rec.field("secs", value.as_secs())?;
83///             rec.field("nanos", value.subsec_nanos())?;
84///             Ok(())
85///         })
86///     }
87/// }
88/// ```
89pub trait IntoEure<T = Self>: Sized {
90    /// Write a value to the current node in the document constructor.
91    fn write(value: T, c: &mut DocumentConstructor) -> Result<(), WriteError>;
92}
93
94// ============================================================================
95// Primitive implementations
96// ============================================================================
97
98impl IntoEure for bool {
99    fn write(value: bool, c: &mut DocumentConstructor) -> Result<(), WriteError> {
100        c.bind_primitive(PrimitiveValue::Bool(value))?;
101        Ok(())
102    }
103}
104
105macro_rules! impl_into_eure_int {
106    ($($ty:ty),*) => {
107        $(
108            impl IntoEure for $ty {
109                fn write(value: $ty, c: &mut DocumentConstructor) -> Result<(), WriteError> {
110                    c.bind_primitive(PrimitiveValue::Integer(BigInt::from(value)))?;
111                    Ok(())
112                }
113            }
114        )*
115    };
116}
117
118impl_into_eure_int!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
119
120impl IntoEure for f32 {
121    fn write(value: f32, c: &mut DocumentConstructor) -> Result<(), WriteError> {
122        c.bind_primitive(PrimitiveValue::F32(value))?;
123        Ok(())
124    }
125}
126
127impl IntoEure for f64 {
128    fn write(value: f64, c: &mut DocumentConstructor) -> Result<(), WriteError> {
129        c.bind_primitive(PrimitiveValue::F64(value))?;
130        Ok(())
131    }
132}
133
134impl IntoEure for BigInt {
135    fn write(value: BigInt, c: &mut DocumentConstructor) -> Result<(), WriteError> {
136        c.bind_primitive(PrimitiveValue::Integer(value))?;
137        Ok(())
138    }
139}
140
141impl IntoEure for String {
142    fn write(value: String, c: &mut DocumentConstructor) -> Result<(), WriteError> {
143        c.bind_primitive(PrimitiveValue::Text(Text::plaintext(value)))?;
144        Ok(())
145    }
146}
147
148impl<'a> IntoEure for &'a str {
149    fn write(value: &'a str, c: &mut DocumentConstructor) -> Result<(), WriteError> {
150        c.bind_primitive(PrimitiveValue::Text(Text::plaintext(value)))?;
151        Ok(())
152    }
153}
154
155impl<'a, T> IntoEure<Cow<'a, T>> for Cow<'a, T>
156where
157    T: ToOwned + ?Sized,
158    T::Owned: IntoEure,
159{
160    fn write(value: Cow<'a, T>, c: &mut DocumentConstructor) -> Result<(), WriteError> {
161        <T::Owned as IntoEure>::write(value.into_owned(), c)
162    }
163}
164
165impl IntoEure for Text {
166    fn write(value: Text, c: &mut DocumentConstructor) -> Result<(), WriteError> {
167        c.bind_primitive(PrimitiveValue::Text(value))?;
168        Ok(())
169    }
170}
171
172impl IntoEure for PrimitiveValue {
173    fn write(value: PrimitiveValue, c: &mut DocumentConstructor) -> Result<(), WriteError> {
174        c.bind_primitive(value)?;
175        Ok(())
176    }
177}
178
179impl IntoEure for Identifier {
180    fn write(value: Identifier, c: &mut DocumentConstructor) -> Result<(), WriteError> {
181        c.bind_primitive(PrimitiveValue::Text(Text::plaintext(value.into_string())))?;
182        Ok(())
183    }
184}
185
186// ============================================================================
187// Collection implementations
188// ============================================================================
189
190impl<M, T> IntoEure<Vec<T>> for Vec<M>
191where
192    M: IntoEure<T>,
193{
194    fn write(value: Vec<T>, c: &mut DocumentConstructor) -> Result<(), WriteError> {
195        c.bind_empty_array()?;
196        for item in value {
197            let scope = c.begin_scope();
198            c.navigate(PathSegment::ArrayIndex(None))?;
199            M::write(item, c)?;
200            c.end_scope(scope)?;
201        }
202        Ok(())
203    }
204}
205
206impl<M, T, const N: usize> IntoEure<[T; N]> for [M; N]
207where
208    M: IntoEure<T>,
209{
210    fn write(value: [T; N], c: &mut DocumentConstructor) -> Result<(), WriteError> {
211        c.bind_empty_array()?;
212        for item in value {
213            let scope = c.begin_scope();
214            c.navigate(PathSegment::ArrayIndex(None))?;
215            M::write(item, c)?;
216            c.end_scope(scope)?;
217        }
218        Ok(())
219    }
220}
221
222impl<M, K, V> IntoEure<Map<K, V>> for Map<K, M>
223where
224    M: IntoEure<V>,
225    K: Into<ObjectKey>,
226{
227    fn write(value: Map<K, V>, c: &mut DocumentConstructor) -> Result<(), WriteError> {
228        c.bind_empty_map()?;
229        for (key, v) in value {
230            let scope = c.begin_scope();
231            c.navigate(PathSegment::Value(key.into()))?;
232            M::write(v, c)?;
233            c.end_scope(scope)?;
234        }
235        Ok(())
236    }
237}
238
239impl<M, T> IntoEure<Option<T>> for Option<M>
240where
241    M: IntoEure<T>,
242{
243    fn write(value: Option<T>, c: &mut DocumentConstructor) -> Result<(), WriteError> {
244        match value {
245            Some(v) => M::write(v, c),
246            None => {
247                c.bind_primitive(PrimitiveValue::Null)?;
248                Ok(())
249            }
250        }
251    }
252}
253
254// ============================================================================
255// Tuple implementations
256// ============================================================================
257
258macro_rules! impl_into_document_tuple {
259    ($n:expr, $($idx:tt: $marker:ident : $ty:ident),+) => {
260        impl<$($marker, $ty),+> IntoEure<($($ty,)+)> for ($($marker,)+)
261        where
262            $($marker: IntoEure<$ty>),+
263        {
264            fn write(value: ($($ty,)+), c: &mut DocumentConstructor) -> Result<(), WriteError> {
265                c.bind_empty_tuple()?;
266                $(
267                    let scope = c.begin_scope();
268                    c.navigate(PathSegment::TupleIndex($idx))?;
269                    $marker::write(value.$idx, c)?;
270                    c.end_scope(scope)?;
271                )+
272                Ok(())
273            }
274        }
275    };
276}
277
278impl_into_document_tuple!(1, 0: MA: A);
279impl_into_document_tuple!(2, 0: MA: A, 1: MB: B);
280impl_into_document_tuple!(3, 0: MA: A, 1: MB: B, 2: MC: C);
281impl_into_document_tuple!(4, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D);
282impl_into_document_tuple!(5, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E);
283impl_into_document_tuple!(6, 0: MA: A, 1: MB: B, 2: MC: C, 3: MD: D, 4: ME: E, 5: MF: F);
284impl_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);
285impl_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);
286impl_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);
287impl_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);
288impl_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);
289impl_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);
290impl_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);
291impl_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);
292impl_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);
293impl_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);
294
295// ============================================================================
296// DocumentConstructor extensions
297// ============================================================================
298
299impl DocumentConstructor {
300    /// Write a record (map with string keys) using a closure.
301    ///
302    /// # Example
303    ///
304    /// ```ignore
305    /// c.record(|rec| {
306    ///     rec.field("name", "Alice")?;
307    ///     rec.field("age", 30)?;
308    ///     Ok(())
309    /// })?;
310    /// ```
311    pub fn record<F, T>(&mut self, f: F) -> Result<T, WriteError>
312    where
313        F: FnOnce(&mut RecordWriter<'_>) -> Result<T, WriteError>,
314    {
315        self.bind_empty_map()?;
316        let mut writer = RecordWriter::new(self);
317        f(&mut writer)
318    }
319
320    /// Write a tuple using a closure.
321    ///
322    /// # Example
323    ///
324    /// ```ignore
325    /// c.tuple(|t| {
326    ///     t.next("first")?;
327    ///     t.next(42)?;
328    ///     t.next(true)?;
329    ///     Ok(())
330    /// })?;
331    /// ```
332    pub fn tuple<F, T>(&mut self, f: F) -> Result<T, WriteError>
333    where
334        F: FnOnce(&mut TupleWriter<'_>) -> Result<T, WriteError>,
335    {
336        self.bind_empty_tuple()?;
337        let mut writer = TupleWriter::new(self);
338        f(&mut writer)
339    }
340
341    /// Set an extension value on the current node.
342    ///
343    /// # Example
344    ///
345    /// ```ignore
346    /// c.set_extension("optional", true)?;
347    /// ```
348    pub fn set_extension<T: IntoEure>(&mut self, name: &str, value: T) -> Result<(), WriteError> {
349        let ident: Identifier = name
350            .parse()
351            .map_err(|_| WriteError::InvalidIdentifier(name.into()))?;
352        let scope = self.begin_scope();
353        self.navigate(PathSegment::Extension(ident))?;
354        T::write(value, self)?;
355        self.end_scope(scope)?;
356        Ok(())
357    }
358
359    /// Set an optional extension value on the current node.
360    /// Does nothing if the value is `None`.
361    ///
362    /// # Example
363    ///
364    /// ```ignore
365    /// c.set_extension_optional("default", self.default)?;
366    /// ```
367    pub fn set_extension_optional<T: IntoEure>(
368        &mut self,
369        name: &str,
370        value: Option<T>,
371    ) -> Result<(), WriteError> {
372        if let Some(v) = value {
373            self.set_extension(name, v)?;
374        }
375        Ok(())
376    }
377
378    /// Set the `$variant` extension for union types.
379    ///
380    /// If called multiple times on the same node (nested unions), the variant
381    /// path is appended using `.` (e.g., `outer.inner.leaf`).
382    ///
383    /// # Example
384    ///
385    /// ```ignore
386    /// match self {
387    ///     MyEnum::Foo(inner) => {
388    ///         c.set_variant("foo")?;
389    ///         c.write(inner)?;
390    ///     }
391    /// }
392    /// ```
393    pub fn set_variant(&mut self, variant: &str) -> Result<(), WriteError> {
394        VariantPath::parse(variant)
395            .map_err(|err| WriteError::InvalidVariantPath { source: err })?;
396        let current_id = self.current_node_id();
397        if let Some(variant_node_id) = self
398            .document()
399            .node(current_id)
400            .get_extension(&Identifier::VARIANT)
401        {
402            let node = self.document().node(variant_node_id);
403            let existing = match node.as_primitive().and_then(|value| value.as_str()) {
404                Some(existing) => existing,
405                None => {
406                    let actual = node.content.value_kind();
407                    return Err(WriteError::InvalidVariantExtensionType { actual });
408                }
409            };
410            VariantPath::parse(existing)
411                .map_err(|err| WriteError::InvalidVariantPath { source: err })?;
412            let mut combined = String::with_capacity(existing.len() + 1 + variant.len());
413            combined.push_str(existing);
414            combined.push('.');
415            combined.push_str(variant);
416            self.document_mut().node_mut(variant_node_id).content =
417                NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(combined)));
418            Ok(())
419        } else {
420            self.set_extension("variant", variant)
421        }
422    }
423
424    /// Write a value implementing `IntoEure` to the current node.
425    ///
426    /// # Example
427    ///
428    /// ```ignore
429    /// c.write(my_value)?;
430    /// ```
431    pub fn write<T: IntoEure>(&mut self, value: T) -> Result<(), WriteError> {
432        T::write(value, self)
433    }
434
435    /// Write a remote type using a marker type.
436    ///
437    /// This enables writing types from external crates that can't implement
438    /// `IntoEure` directly due to Rust's orphan rule.
439    ///
440    /// # Example
441    ///
442    /// ```ignore
443    /// // DurationDef implements IntoEure<std::time::Duration>
444    /// c.write_via::<DurationDef, _>(duration)?;
445    /// ```
446    pub fn write_via<M, T>(&mut self, value: T) -> Result<(), WriteError>
447    where
448        M: IntoEure<T>,
449    {
450        M::write(value, self)
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457
458    #[test]
459    fn test_primitive_bool() {
460        let mut c = DocumentConstructor::new();
461        c.write(true).unwrap();
462        let doc = c.finish();
463        assert_eq!(
464            doc.root().content,
465            NodeValue::Primitive(PrimitiveValue::Bool(true))
466        );
467    }
468
469    #[test]
470    fn test_primitive_string() {
471        let mut c = DocumentConstructor::new();
472        c.write("hello").unwrap();
473        let doc = c.finish();
474        assert_eq!(
475            doc.root().content,
476            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
477        );
478    }
479
480    #[test]
481    fn test_vec() {
482        let mut c = DocumentConstructor::new();
483        c.write(vec![1i32, 2, 3]).unwrap();
484        let doc = c.finish();
485        let arr = doc.root().as_array().unwrap();
486        assert_eq!(arr.len(), 3);
487    }
488
489    #[test]
490    fn test_tuple() {
491        let mut c = DocumentConstructor::new();
492        c.write((1i32, "two", true)).unwrap();
493        let doc = c.finish();
494        let tuple = doc.root().as_tuple().unwrap();
495        assert_eq!(tuple.len(), 3);
496    }
497
498    #[test]
499    fn test_record() {
500        let mut c = DocumentConstructor::new();
501        c.record(|rec| {
502            rec.field("name", "Alice")?;
503            rec.field("age", 30i32)?;
504            Ok(())
505        })
506        .unwrap();
507        let doc = c.finish();
508        let map = doc.root().as_map().unwrap();
509        assert_eq!(map.len(), 2);
510    }
511
512    #[test]
513    fn test_set_extension() {
514        let mut c = DocumentConstructor::new();
515        c.record(|rec| {
516            rec.field("type", "string")?;
517            Ok(())
518        })
519        .unwrap();
520        c.set_extension("optional", true).unwrap();
521        let doc = c.finish();
522
523        let root = doc.root();
524        assert!(
525            root.extensions
526                .contains_key(&"optional".parse::<Identifier>().unwrap())
527        );
528    }
529
530    #[test]
531    fn test_set_variant() {
532        let mut c = DocumentConstructor::new();
533        c.set_variant("foo").unwrap();
534        c.record(|rec| {
535            rec.field("value", 42i32)?;
536            Ok(())
537        })
538        .unwrap();
539        let doc = c.finish();
540
541        let root = doc.root();
542        assert!(
543            root.extensions
544                .contains_key(&"variant".parse::<Identifier>().unwrap())
545        );
546    }
547
548    #[test]
549    fn test_set_variant_invalid_extension_type() {
550        let mut c = DocumentConstructor::new();
551        c.set_extension("variant", 1i32).unwrap();
552        let err = c.set_variant("foo").unwrap_err();
553        assert!(matches!(
554            err,
555            WriteError::InvalidVariantExtensionType {
556                actual: ValueKind::Integer
557            }
558        ));
559    }
560
561    #[test]
562    fn test_nested_record() {
563        let mut c = DocumentConstructor::new();
564        c.record(|rec| {
565            rec.field("name", "Alice")?;
566            rec.field_with("address", |c| {
567                c.record(|rec| {
568                    rec.field("city", "Tokyo")?;
569                    rec.field("zip", "100-0001")?;
570                    Ok(())
571                })
572            })?;
573            Ok(())
574        })
575        .unwrap();
576        let doc = c.finish();
577        let map = doc.root().as_map().unwrap();
578        assert_eq!(map.len(), 2);
579    }
580
581    #[test]
582    fn test_write_via() {
583        // Marker type for Duration-like struct
584        struct DurationMarker;
585        struct DurationLike {
586            secs: u64,
587            nanos: u32,
588        }
589
590        impl IntoEure<DurationLike> for DurationMarker {
591            fn write(value: DurationLike, c: &mut DocumentConstructor) -> Result<(), WriteError> {
592                c.record(|rec| {
593                    rec.field("secs", value.secs)?;
594                    rec.field("nanos", value.nanos)?;
595                    Ok(())
596                })
597            }
598        }
599
600        let mut c = DocumentConstructor::new();
601        c.write_via::<DurationMarker, _>(DurationLike {
602            secs: 60,
603            nanos: 123,
604        })
605        .unwrap();
606        let doc = c.finish();
607        let map = doc.root().as_map().unwrap();
608        assert_eq!(map.len(), 2);
609    }
610
611    #[test]
612    fn test_array_write() {
613        let mut c = DocumentConstructor::new();
614        c.write([1i32, 2, 3]).unwrap();
615        let doc = c.finish();
616        let arr = doc.root().as_array().unwrap();
617        assert_eq!(arr.len(), 3);
618    }
619
620    #[test]
621    fn test_array_empty_write() {
622        let mut c = DocumentConstructor::new();
623        let empty: [i32; 0] = [];
624        c.write(empty).unwrap();
625        let doc = c.finish();
626        let arr = doc.root().as_array().unwrap();
627        assert_eq!(arr.len(), 0);
628    }
629
630    #[test]
631    fn test_array_roundtrip() {
632        let original: [i32; 3] = [10, 20, 30];
633
634        // Write
635        let mut c = DocumentConstructor::new();
636        c.write(original).unwrap();
637        let doc = c.finish();
638
639        // Parse back
640        let root_id = doc.get_root_id();
641        let parsed: [i32; 3] = doc.parse(root_id).unwrap();
642
643        assert_eq!(parsed, original);
644    }
645
646    // =========================================================================
647    // Cow tests
648    // =========================================================================
649
650    #[test]
651    fn test_cow_borrowed_str() {
652        let mut c = DocumentConstructor::new();
653        let value: Cow<'_, str> = Cow::Borrowed("hello");
654        c.write(value).unwrap();
655        let doc = c.finish();
656        assert_eq!(
657            doc.root().content,
658            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
659        );
660    }
661
662    #[test]
663    fn test_cow_owned_str() {
664        let mut c = DocumentConstructor::new();
665        let value: Cow<'static, str> = Cow::Owned("hello".to_string());
666        c.write(value).unwrap();
667        let doc = c.finish();
668        assert_eq!(
669            doc.root().content,
670            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("hello")))
671        );
672    }
673}