Skip to main content

eure_document/
parse.rs

1//! FromEure trait for parsing Rust types from Eure documents.
2
3extern crate alloc;
4
5pub mod object_key;
6pub mod record;
7pub mod tuple;
8pub mod union;
9pub mod variant_path;
10
11use alloc::borrow::{Cow, ToOwned};
12use indexmap::{IndexMap, IndexSet};
13pub use object_key::ParseObjectKey;
14pub use record::RecordParser;
15pub use tuple::TupleParser;
16pub use union::UnionParser;
17pub use variant_path::VariantPath;
18// UnionTagMode is defined in this module and exported automatically
19
20use alloc::format;
21use alloc::rc::Rc;
22use core::cell::RefCell;
23use num_bigint::BigInt;
24
25use core::marker::PhantomData;
26use std::collections::{BTreeMap, HashMap, HashSet};
27
28use crate::{
29    data_model::VariantRepr,
30    document::node::{Node, NodeArray},
31    identifier::IdentifierError,
32    prelude_internal::*,
33    value::ValueKind,
34};
35
36// =============================================================================
37// UnionTagMode
38// =============================================================================
39
40/// Mode for union tag resolution.
41///
42/// This determines how variant tags are resolved during union parsing:
43/// - `Eure`: Use `$variant` extension and untagged matching (for native Eure documents)
44/// - `Repr`: Use only `VariantRepr` patterns (for JSON/YAML imports)
45///
46/// These modes are mutually exclusive to avoid false positives.
47#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
48pub enum UnionTagMode {
49    /// Eure mode: Use `$variant` extension or untagged matching.
50    ///
51    /// This is the default mode for native Eure documents.
52    /// - If `$variant` extension is present, use it to determine the variant
53    /// - Otherwise, use untagged matching (try all variants)
54    /// - `VariantRepr` is ignored in this mode
55    #[default]
56    Eure,
57
58    /// Repr mode: Use only `VariantRepr` patterns.
59    ///
60    /// This mode is for documents imported from JSON/YAML.
61    /// - Extract variant tag using `VariantRepr` (External, Internal, Adjacent)
62    /// - `$variant` extension is ignored in this mode
63    /// - If repr doesn't extract a tag, error (no untagged fallback)
64    Repr,
65}
66
67// =============================================================================
68// AccessedSet
69// =============================================================================
70
71/// Snapshot of accessed state (fields, extensions).
72pub type AccessedSnapshot = (HashSet<String>, HashSet<Identifier>);
73
74/// Tracks accessed fields and extensions with snapshot/rollback support for union parsing.
75///
76/// The internal state is a stack where the last item is always the current working state.
77/// Items before it are snapshots (save points) for rollback.
78///
79/// Invariant: stack is never empty.
80///
81/// # Stack visualization
82/// ```text
83/// Initial:        [current]
84/// push_snapshot:  [snapshot, current]  (snapshot = copy of current)
85/// modify:         [snapshot, current'] (current' has changes)
86/// restore:        [snapshot, snapshot] (current reset to snapshot)
87/// pop_restore:    [snapshot]           (current removed, snapshot is new current)
88/// pop_no_restore: [current']           (snapshot removed, keep modified current)
89/// ```
90#[derive(Debug, Clone)]
91pub struct AccessedSet(Rc<RefCell<Vec<AccessedSnapshot>>>);
92
93impl AccessedSet {
94    /// Create a new empty set.
95    pub fn new() -> Self {
96        // Start with one empty state (the current working state)
97        Self(Rc::new(RefCell::new(vec![(
98            HashSet::new(),
99            HashSet::new(),
100        )])))
101    }
102
103    /// Add a field to the accessed set.
104    pub fn add_field(&self, field: impl Into<String>) {
105        self.0
106            .borrow_mut()
107            .last_mut()
108            .unwrap()
109            .0
110            .insert(field.into());
111    }
112
113    /// Add an extension to the accessed set.
114    pub fn add_ext(&self, ext: Identifier) {
115        self.0.borrow_mut().last_mut().unwrap().1.insert(ext);
116    }
117
118    /// Check if a field has been accessed.
119    pub fn has_field(&self, field: &str) -> bool {
120        self.0.borrow().last().unwrap().0.contains(field)
121    }
122
123    /// Check if an extension has been accessed.
124    pub fn has_ext(&self, ext: &Identifier) -> bool {
125        self.0.borrow().last().unwrap().1.contains(ext)
126    }
127
128    /// Get all accessed extensions.
129    pub fn get_accessed_exts(&self) -> HashSet<Identifier> {
130        self.0.borrow().last().unwrap().1.clone()
131    }
132
133    /// Push a snapshot (call at start of union parsing).
134    /// Inserts a copy of current BEFORE current, so current can be modified.
135    pub fn push_snapshot(&self) {
136        let mut stack = self.0.borrow_mut();
137        let snapshot = stack.last().unwrap().clone();
138        let len = stack.len();
139        stack.insert(len - 1, snapshot);
140        // Stack: [..., current] → [..., snapshot, current]
141    }
142
143    /// Restore current to the snapshot (call when variant fails).
144    /// Resets current (last) to match the snapshot (second-to-last).
145    pub fn restore_to_current_snapshot(&self) {
146        let mut stack = self.0.borrow_mut();
147        if stack.len() >= 2 {
148            let snapshot = stack[stack.len() - 2].clone();
149            *stack.last_mut().unwrap() = snapshot;
150        }
151    }
152
153    /// Capture the current state (for non-priority variants).
154    pub fn capture_current_state(&self) -> AccessedSnapshot {
155        self.0.borrow().last().unwrap().clone()
156    }
157
158    /// Restore to a previously captured state.
159    pub fn restore_to_state(&self, state: AccessedSnapshot) {
160        *self.0.borrow_mut().last_mut().unwrap() = state;
161    }
162
163    /// Pop and restore (call when union fails/ambiguous).
164    /// Removes current, snapshot becomes new current.
165    pub fn pop_and_restore(&self) {
166        let mut stack = self.0.borrow_mut();
167        if stack.len() >= 2 {
168            stack.pop(); // Remove current, snapshot is now current
169        }
170    }
171
172    /// Pop without restore (call when union succeeds).
173    /// Removes the snapshot, keeps current.
174    pub fn pop_without_restore(&self) {
175        let mut stack = self.0.borrow_mut();
176        if stack.len() >= 2 {
177            let snapshot_idx = stack.len() - 2;
178            stack.remove(snapshot_idx); // Remove snapshot, keep current
179        }
180    }
181}
182
183impl Default for AccessedSet {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189// =============================================================================
190// ParserScope
191// =============================================================================
192
193/// Scope for flatten parsing - indicates whether we're in record or extension mode.
194///
195/// This determines what `parse_record_or_ext()` iterates over for catch-all types like HashMap.
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum ParserScope {
198    /// Record scope - iterates record fields (from `rec.flatten()`)
199    Record,
200    /// Extension scope - iterates extensions (from `ext.flatten_ext()`)
201    Extension,
202}
203
204// =============================================================================
205// FlattenContext
206// =============================================================================
207
208/// Context for flatten parsing - wraps AccessedSet with snapshot/rollback support.
209///
210/// When parsing flattened types, all levels share a single `AccessedSet` owned
211/// by the root parser. Child parsers add to this shared set, and only the root
212/// parser actually validates with `deny_unknown_fields()`.
213///
214/// # Example
215///
216/// ```ignore
217/// // Root parser owns the accessed set
218/// let mut rec1 = ctx.parse_record()?;  // accessed = {} (owned)
219/// rec1.field("a");  // accessed = {a}
220///
221/// // Child parser shares and adds to the same set
222/// let ctx2 = rec1.flatten();
223/// let mut rec2 = ctx2.parse_record()?;  // shares accessed via Rc
224/// rec2.field("b");  // accessed = {a, b}
225/// rec2.deny_unknown_fields()?;  // NO-OP (child)
226///
227/// rec1.field("c");  // accessed = {a, b, c}
228/// rec1.deny_unknown_fields()?;  // VALIDATES (root)
229/// ```
230#[derive(Debug, Clone)]
231pub struct FlattenContext {
232    accessed: AccessedSet,
233    scope: ParserScope,
234}
235
236impl FlattenContext {
237    /// Create a FlattenContext from an existing AccessedSet with the given scope.
238    pub fn new(accessed: AccessedSet, scope: ParserScope) -> Self {
239        Self { accessed, scope }
240    }
241
242    /// Get the parser scope.
243    pub fn scope(&self) -> ParserScope {
244        self.scope
245    }
246
247    /// Get the underlying AccessedSet (for sharing with RecordParser).
248    pub fn accessed_set(&self) -> &AccessedSet {
249        &self.accessed
250    }
251
252    /// Add a field to the accessed set.
253    pub fn add_field(&self, field: impl Into<String>) {
254        self.accessed.add_field(field);
255    }
256
257    /// Add an extension to the accessed set.
258    pub fn add_ext(&self, ext: Identifier) {
259        self.accessed.add_ext(ext);
260    }
261
262    /// Check if a field has been accessed.
263    pub fn has_field(&self, field: &str) -> bool {
264        self.accessed.has_field(field)
265    }
266
267    /// Check if an extension has been accessed.
268    pub fn has_ext(&self, ext: &Identifier) -> bool {
269        self.accessed.has_ext(ext)
270    }
271
272    /// Push snapshot (at start of union parsing).
273    pub fn push_snapshot(&self) {
274        self.accessed.push_snapshot();
275    }
276
277    /// Restore to current snapshot (when variant fails).
278    pub fn restore_to_current_snapshot(&self) {
279        self.accessed.restore_to_current_snapshot();
280    }
281
282    /// Capture current state (for non-priority variants).
283    pub fn capture_current_state(&self) -> AccessedSnapshot {
284        self.accessed.capture_current_state()
285    }
286
287    /// Restore to a captured state (when selecting a non-priority variant).
288    pub fn restore_to_state(&self, state: AccessedSnapshot) {
289        self.accessed.restore_to_state(state);
290    }
291
292    /// Pop and restore (when union fails/ambiguous).
293    pub fn pop_and_restore(&self) {
294        self.accessed.pop_and_restore();
295    }
296
297    /// Pop without restore (when union succeeds).
298    pub fn pop_without_restore(&self) {
299        self.accessed.pop_without_restore();
300    }
301}
302
303// =============================================================================
304// ParseContext
305// =============================================================================
306
307/// Context for parsing Eure documents.
308///
309/// Encapsulates the document, current node, and variant path state.
310/// Similar to `DocumentConstructor` for writing, but for reading.
311#[derive(Clone, Debug)]
312pub struct ParseContext<'doc> {
313    doc: &'doc EureDocument,
314    node_id: NodeId,
315    variant_path: Option<VariantPath>,
316    /// Flatten context for tracking shared accessed fields/extensions.
317    /// If Some, this context is a flattened child - deny_unknown_* is no-op.
318    /// If None, this is a root context.
319    flatten_ctx: Option<FlattenContext>,
320    /// Mode for union tag resolution.
321    union_tag_mode: UnionTagMode,
322    /// Tracks accessed fields and extensions.
323    accessed: AccessedSet,
324}
325
326impl<'doc> ParseContext<'doc> {
327    /// Create a new parse context at the given node.
328    pub fn new(doc: &'doc EureDocument, node_id: NodeId) -> Self {
329        Self {
330            doc,
331            node_id,
332            variant_path: None,
333            flatten_ctx: None,
334            union_tag_mode: UnionTagMode::default(),
335            accessed: AccessedSet::new(),
336        }
337    }
338
339    /// Create a new parse context with the specified union tag mode.
340    pub fn with_union_tag_mode(
341        doc: &'doc EureDocument,
342        node_id: NodeId,
343        mode: UnionTagMode,
344    ) -> Self {
345        Self {
346            doc,
347            node_id,
348            variant_path: None,
349            flatten_ctx: None,
350            union_tag_mode: mode,
351            accessed: AccessedSet::new(),
352        }
353    }
354
355    /// Create a context with a flatten context (for flatten parsing).
356    ///
357    /// When a flatten context is present:
358    /// - `deny_unknown_fields()` and `deny_unknown_extensions()` become no-ops
359    /// - All field/extension accesses are recorded in the shared `FlattenContext`
360    pub fn with_flatten_ctx(
361        doc: &'doc EureDocument,
362        node_id: NodeId,
363        flatten_ctx: FlattenContext,
364        mode: UnionTagMode,
365    ) -> Self {
366        // Share accessed set from flatten context
367        let accessed = flatten_ctx.accessed_set().clone();
368        Self {
369            doc,
370            node_id,
371            variant_path: None,
372            flatten_ctx: Some(flatten_ctx),
373            union_tag_mode: mode,
374            accessed,
375        }
376    }
377
378    /// Get the flatten context, if present.
379    pub fn flatten_ctx(&self) -> Option<&FlattenContext> {
380        self.flatten_ctx.as_ref()
381    }
382
383    /// Check if this context is flattened (has a flatten context).
384    ///
385    /// When flattened, `deny_unknown_fields()` and `deny_unknown_extensions()` are no-ops.
386    pub fn is_flattened(&self) -> bool {
387        self.flatten_ctx.is_some()
388    }
389
390    /// Get the parser scope, if in a flatten context.
391    ///
392    /// Returns `Some(ParserScope::Record)` if from `rec.flatten()`,
393    /// `Some(ParserScope::Extension)` if from `ext.flatten_ext()`,
394    /// `None` if not in a flatten context.
395    pub fn parser_scope(&self) -> Option<ParserScope> {
396        self.flatten_ctx.as_ref().map(|fc| fc.scope())
397    }
398
399    /// Get the current node ID.
400    pub fn node_id(&self) -> NodeId {
401        self.node_id
402    }
403
404    /// Get the document reference (internal use only).
405    pub(crate) fn doc(&self) -> &'doc EureDocument {
406        self.doc
407    }
408
409    /// Get the current node.
410    pub fn node(&self) -> &'doc Node {
411        self.doc.node(self.node_id)
412    }
413
414    /// Create a new context at a different node (clears variant path and flatten context).
415    pub(crate) fn at(&self, node_id: NodeId) -> Self {
416        Self {
417            doc: self.doc,
418            node_id,
419            variant_path: None,
420            flatten_ctx: None,
421            union_tag_mode: self.union_tag_mode,
422            accessed: AccessedSet::new(),
423        }
424    }
425
426    /// Create a flattened version of this context for shared access tracking.
427    ///
428    /// When you need both record parsing and extension parsing to share
429    /// access tracking (so deny_unknown_* works correctly), use this method
430    /// to create a shared context first.
431    ///
432    /// # Scope
433    ///
434    /// This method always sets `ParserScope::Record`. When alternating between
435    /// `flatten()` and `flatten_ext()`, the scope is updated each time:
436    ///
437    /// ```ignore
438    /// ctx.flatten()       // scope = Record
439    ///    .flatten_ext()   // scope = Extension
440    ///    .flatten()       // scope = Record (updated, not inherited)
441    /// ```
442    ///
443    /// # AccessedSet Sharing
444    ///
445    /// The `AccessedSet` is shared across all contexts in the flatten chain
446    /// (via `Rc`). This ensures that field/extension accesses are tracked
447    /// regardless of which scope they were accessed from, and the root parser
448    /// can validate everything with `deny_unknown_*`.
449    ///
450    /// # Example
451    ///
452    /// ```ignore
453    /// // Both record and extension parsers share the same AccessedSet
454    /// let ctx = ctx.flatten();
455    /// // ... parse extensions with ctx.parse_ext(...) ...
456    /// let mut rec = ctx.parse_record()?;
457    /// // ... parse fields ...
458    /// rec.deny_unknown_fields()?;  // Validates both fields and extensions
459    /// ```
460    pub fn flatten(&self) -> Self {
461        // Always create a NEW FlattenContext with Record scope.
462        // We cannot just clone the existing FlattenContext because that would
463        // preserve the old scope. Instead, we create a new one with the correct
464        // scope while sharing the AccessedSet (via Rc clone).
465        let flatten_ctx = match &self.flatten_ctx {
466            Some(fc) => FlattenContext::new(fc.accessed_set().clone(), ParserScope::Record),
467            None => FlattenContext::new(self.accessed.clone(), ParserScope::Record),
468        };
469        Self {
470            doc: self.doc,
471            node_id: self.node_id,
472            variant_path: self.variant_path.clone(),
473            flatten_ctx: Some(flatten_ctx),
474            union_tag_mode: self.union_tag_mode,
475            accessed: self.accessed.clone(),
476        }
477    }
478
479    /// Get the union tag mode.
480    pub fn union_tag_mode(&self) -> UnionTagMode {
481        self.union_tag_mode
482    }
483
484    /// Parse the current node as type T.
485    pub fn parse<T: FromEure<'doc, T>>(&self) -> Result<T, T::Error> {
486        T::parse(self)
487    }
488
489    /// Parse the current node as type T using a marker/strategy type M.
490    ///
491    /// This is used for parsing remote types where `M` implements
492    /// `FromEure<'doc, T>` but `T` doesn't implement `FromEure` itself.
493    pub fn parse_via<M, T>(&self) -> Result<T, M::Error>
494    where
495        M: FromEure<'doc, T>,
496    {
497        M::parse(self)
498    }
499
500    pub fn parse_with<T: DocumentParser<'doc>>(
501        &self,
502        mut parser: T,
503    ) -> Result<T::Output, T::Error> {
504        parser.parse(self)
505    }
506
507    /// Get a union parser for the current node with the specified variant representation.
508    ///
509    /// Returns error if `$variant` extension has invalid type or syntax.
510    ///
511    /// # Arguments
512    ///
513    /// * `repr` - The variant representation to use. Use `VariantRepr::default()` for Untagged.
514    pub fn parse_union<T, E>(&self, repr: VariantRepr) -> Result<UnionParser<'doc, '_, T, E>, E>
515    where
516        E: From<ParseError>,
517    {
518        UnionParser::new(self, repr).map_err(Into::into)
519    }
520
521    /// Parse the current node as a record.
522    ///
523    /// Returns error if variant path is not empty.
524    pub fn parse_record(&self) -> Result<RecordParser<'doc>, ParseError> {
525        self.ensure_no_variant_path()?;
526        RecordParser::new(self)
527    }
528
529    /// Parse the current node as a tuple.
530    ///
531    /// Returns error if variant path is not empty.
532    pub fn parse_tuple(&self) -> Result<TupleParser<'doc>, ParseError> {
533        self.ensure_no_variant_path()?;
534        TupleParser::new(self)
535    }
536
537    /// Parse the current node as a primitive value.
538    ///
539    /// Returns `NotPrimitive` error if the node is not a primitive.
540    /// Returns `UnexpectedVariantPath` error if variant path is not empty.
541    pub fn parse_primitive(&self) -> Result<&'doc PrimitiveValue, ParseError> {
542        self.ensure_no_variant_path()?;
543        match &self.node().content {
544            NodeValue::Primitive(p) => Ok(p),
545            _ => Err(ParseError {
546                node_id: self.node_id(),
547                kind: ParseErrorKind::NotPrimitive {
548                    actual: self.node().content.value_kind(),
549                },
550            }),
551        }
552    }
553
554    // =========================================================================
555    // Extension parsing methods
556    // =========================================================================
557
558    /// Get the AccessedSet for this context.
559    pub(crate) fn accessed(&self) -> &AccessedSet {
560        &self.accessed
561    }
562
563    /// Create a standalone document from the current node's subtree,
564    /// excluding extensions that have been marked as accessed.
565    ///
566    /// This is useful for literal comparison in schema validation, where
567    /// extensions like `$variant` have been consumed by union resolution
568    /// and should not be part of the literal value.
569    pub fn node_subtree_to_document_excluding_accessed(&self) -> EureDocument {
570        let mut doc = self.doc.node_subtree_to_document(self.node_id);
571        let root_id = doc.get_root_id();
572        let accessed_exts = self.accessed.get_accessed_exts();
573        for ext in accessed_exts {
574            doc.node_mut(root_id).extensions.remove_fast(&ext);
575        }
576        doc
577    }
578
579    /// Mark an extension as accessed.
580    fn mark_ext_accessed(&self, ident: Identifier) {
581        self.accessed.add_ext(ident);
582    }
583
584    /// Get a required extension field.
585    ///
586    /// Returns `ParseErrorKind::MissingExtension` if the extension is not present.
587    pub fn parse_ext<T>(&self, name: &str) -> Result<T, T::Error>
588    where
589        T: FromEure<'doc>,
590        T::Error: From<ParseError>,
591    {
592        self.parse_ext_with(name, T::parse)
593    }
594
595    /// Get a required extension field with a custom parser.
596    pub fn parse_ext_with<T>(&self, name: &str, mut parser: T) -> Result<T::Output, T::Error>
597    where
598        T: DocumentParser<'doc>,
599        T::Error: From<ParseError>,
600    {
601        let ident: Identifier = name.parse().map_err(|e| ParseError {
602            node_id: self.node_id,
603            kind: ParseErrorKind::InvalidIdentifier(e),
604        })?;
605        self.mark_ext_accessed(ident.clone());
606        let ext_node_id = self
607            .node()
608            .extensions
609            .get(&ident)
610            .ok_or_else(|| ParseError {
611                node_id: self.node_id,
612                kind: ParseErrorKind::MissingExtension(name.to_string()),
613            })?;
614        let ctx = ParseContext::with_union_tag_mode(self.doc, *ext_node_id, self.union_tag_mode);
615        parser.parse(&ctx)
616    }
617
618    /// Get an optional extension field.
619    ///
620    /// Returns `Ok(None)` if the extension is not present.
621    pub fn parse_ext_optional<T>(&self, name: &str) -> Result<Option<T>, T::Error>
622    where
623        T: FromEure<'doc>,
624        T::Error: From<ParseError>,
625    {
626        self.parse_ext_optional_with(name, T::parse)
627    }
628
629    /// Get an optional extension field with a custom parser.
630    ///
631    /// Returns `Ok(None)` if the extension is not present.
632    pub fn parse_ext_optional_with<T>(
633        &self,
634        name: &str,
635        mut parser: T,
636    ) -> Result<Option<T::Output>, T::Error>
637    where
638        T: DocumentParser<'doc>,
639        T::Error: From<ParseError>,
640    {
641        let ident: Identifier = name.parse().map_err(|e| ParseError {
642            node_id: self.node_id,
643            kind: ParseErrorKind::InvalidIdentifier(e),
644        })?;
645        self.mark_ext_accessed(ident.clone());
646        match self.node().extensions.get(&ident) {
647            Some(ext_node_id) => {
648                let ctx =
649                    ParseContext::with_union_tag_mode(self.doc, *ext_node_id, self.union_tag_mode);
650                Ok(Some(parser.parse(&ctx)?))
651            }
652            None => Ok(None),
653        }
654    }
655
656    /// Get the parse context for an extension field without parsing it.
657    ///
658    /// Use this when you need access to the extension's NodeId or want to defer parsing.
659    /// Returns `ParseErrorKind::MissingExtension` if the extension is not present.
660    pub fn ext(&self, name: &str) -> Result<ParseContext<'doc>, ParseError> {
661        let ident: Identifier = name.parse().map_err(|e| ParseError {
662            node_id: self.node_id,
663            kind: ParseErrorKind::InvalidIdentifier(e),
664        })?;
665        self.mark_ext_accessed(ident.clone());
666        let ext_node_id =
667            self.node()
668                .extensions
669                .get(&ident)
670                .copied()
671                .ok_or_else(|| ParseError {
672                    node_id: self.node_id,
673                    kind: ParseErrorKind::MissingExtension(name.to_string()),
674                })?;
675        Ok(ParseContext::with_union_tag_mode(
676            self.doc,
677            ext_node_id,
678            self.union_tag_mode,
679        ))
680    }
681
682    /// Get the parse context for an optional extension field without parsing it.
683    ///
684    /// Use this when you need access to the extension's NodeId or want to defer parsing.
685    /// Returns `None` if the extension is not present.
686    pub fn ext_optional(&self, name: &str) -> Option<ParseContext<'doc>> {
687        let ident: Identifier = name.parse().ok()?;
688        self.mark_ext_accessed(ident.clone());
689        self.node().extensions.get(&ident).map(|&node_id| {
690            ParseContext::with_union_tag_mode(self.doc, node_id, self.union_tag_mode)
691        })
692    }
693
694    /// Finish parsing with Deny policy (error if unknown extensions exist).
695    ///
696    /// **Flatten behavior**: If this context is in a flatten chain (has flatten_ctx),
697    /// this is a no-op. Only root parsers validate.
698    pub fn deny_unknown_extensions(&self) -> Result<(), ParseError> {
699        // If child (in any flatten context), no-op - parent will validate
700        if self.flatten_ctx.is_some() {
701            return Ok(());
702        }
703
704        // Root parser - validate using accessed set
705        for (ident, _) in self.node().extensions.iter() {
706            if !self.accessed.has_ext(ident) {
707                return Err(ParseError {
708                    node_id: self.node_id,
709                    kind: ParseErrorKind::UnknownExtension(ident.clone()),
710                });
711            }
712        }
713        Ok(())
714    }
715
716    /// Get an iterator over unknown extensions (for custom handling).
717    ///
718    /// Returns (identifier, context) pairs for extensions that haven't been accessed.
719    pub fn unknown_extensions(
720        &self,
721    ) -> impl Iterator<Item = (&'doc Identifier, ParseContext<'doc>)> + '_ {
722        let doc = self.doc;
723        let mode = self.union_tag_mode;
724        // Clone the accessed set for filtering - we need the current state
725        let accessed = self.accessed.clone();
726        self.node()
727            .extensions
728            .iter()
729            .filter_map(move |(ident, &node_id)| {
730                if !accessed.has_ext(ident) {
731                    Some((ident, ParseContext::with_union_tag_mode(doc, node_id, mode)))
732                } else {
733                    None
734                }
735            })
736    }
737
738    /// Create a flatten context for child parsers in Extension scope.
739    ///
740    /// This creates a FlattenContext initialized with the current accessed extensions,
741    /// and returns a ParseContext that children can use. Children created from this
742    /// context will:
743    /// - Add their accessed extensions to the shared FlattenContext
744    /// - Have deny_unknown_extensions() be a no-op
745    ///
746    /// The root parser should call deny_unknown_extensions() after all children are done.
747    ///
748    /// # Scope
749    ///
750    /// This method always sets `ParserScope::Extension`. When alternating between
751    /// `flatten()` and `flatten_ext()`, the scope is updated each time:
752    ///
753    /// ```ignore
754    /// ctx.flatten()       // scope = Record
755    ///    .flatten_ext()   // scope = Extension (updated, not inherited)
756    ///    .flatten()       // scope = Record
757    /// ```
758    ///
759    /// # AccessedSet Sharing
760    ///
761    /// The `AccessedSet` is shared across all contexts in the flatten chain
762    /// (via `Rc`). See [`flatten()`](Self::flatten) for details.
763    pub fn flatten_ext(&self) -> ParseContext<'doc> {
764        // Always create a NEW FlattenContext with Extension scope.
765        // We cannot just clone the existing FlattenContext because that would
766        // preserve the old scope. Instead, we create a new one with the correct
767        // scope while sharing the AccessedSet (via Rc clone).
768        let flatten_ctx = match &self.flatten_ctx {
769            Some(fc) => FlattenContext::new(fc.accessed_set().clone(), ParserScope::Extension),
770            None => FlattenContext::new(self.accessed.clone(), ParserScope::Extension),
771        };
772
773        ParseContext::with_flatten_ctx(self.doc, self.node_id, flatten_ctx, self.union_tag_mode)
774    }
775
776    /// Check if the current node is null.
777    pub fn is_null(&self) -> bool {
778        matches!(
779            &self.node().content,
780            NodeValue::Primitive(PrimitiveValue::Null)
781        )
782    }
783
784    /// Create a child context with the remaining variant path.
785    pub(crate) fn with_variant_rest(&self, rest: Option<VariantPath>) -> Self {
786        Self {
787            doc: self.doc,
788            node_id: self.node_id,
789            variant_path: rest,
790            flatten_ctx: self.flatten_ctx.clone(),
791            union_tag_mode: self.union_tag_mode,
792            accessed: self.accessed.clone(),
793        }
794    }
795
796    /// Get the current variant path.
797    pub(crate) fn variant_path(&self) -> Option<&VariantPath> {
798        self.variant_path.as_ref()
799    }
800
801    /// Check that no variant path remains, error otherwise.
802    fn ensure_no_variant_path(&self) -> Result<(), ParseError> {
803        if let Some(vp) = &self.variant_path
804            && !vp.is_empty()
805        {
806            return Err(ParseError {
807                node_id: self.node_id,
808                kind: ParseErrorKind::UnexpectedVariantPath(vp.clone()),
809            });
810        }
811        Ok(())
812    }
813
814    fn unexpected_kind(&self, expected: ValueKind) -> ParseError {
815        ParseError {
816            node_id: self.node_id(),
817            kind: ParseErrorKind::TypeMismatch {
818                expected,
819                actual: self.node().content.value_kind(),
820            },
821        }
822    }
823}
824
825// =============================================================================
826// FromEure trait
827// =============================================================================
828
829/// Trait for parsing Rust types from Eure documents.
830///
831/// Types implementing this trait can be constructed from [`EureDocument`]
832/// via [`ParseContext`].
833///
834/// # Type Parameters
835///
836/// - `'doc`: The document lifetime, allowing zero-copy parsing for references
837/// - `T`: The target type to parse (defaults to `Self`)
838///
839/// When `T = Self` (the default), this is standard parsing.
840/// When `T != Self`, `Self` acts as a "strategy" type for parsing remote types.
841/// This follows the same pattern as `PartialEq<Rhs = Self>`.
842///
843/// # Remote Type Support
844///
845/// The `T` parameter enables parsing external crate types that can't implement
846/// `FromEure` directly (due to Rust's orphan rule). Define a marker type and
847/// implement `FromEure<'doc, RemoteType>` for it:
848///
849/// ```ignore
850/// struct DurationDef;
851///
852/// impl<'doc> FromEure<'doc, std::time::Duration> for DurationDef {
853///     type Error = ParseError;
854///     fn parse(ctx: &ParseContext<'doc>) -> Result<std::time::Duration, Self::Error> {
855///         let rec = ctx.parse_record()?;
856///         let secs: u64 = rec.parse_field("secs")?;
857///         let nanos: u32 = rec.parse_field("nanos")?;
858///         rec.deny_unknown_fields()?;
859///         Ok(std::time::Duration::new(secs, nanos))
860///     }
861/// }
862/// ```
863///
864/// Container types (`Option<M>`, `Vec<M>`, etc.) automatically support remote types:
865/// if `M: FromEure<'doc, T>`, then `Option<M>: FromEure<'doc, Option<T>>`.
866///
867/// # Examples
868///
869/// ```ignore
870/// // Reference type - borrows from document
871/// impl<'doc> FromEure<'doc> for &'doc str { ... }
872///
873/// // Owned type - no lifetime dependency
874/// impl FromEure<'_> for String { ... }
875/// ```
876#[diagnostic::on_unimplemented(
877    message = "`{Self}` cannot be parsed from Eure document",
878    label = "this type does not implement `FromEure`",
879    note = "consider adding `#[derive(FromEure)]` to `{Self}`"
880)]
881pub trait FromEure<'doc, T = Self>: Sized {
882    /// The error type returned by parsing.
883    type Error;
884
885    /// Parse a value of type T from the given parse context.
886    fn parse(ctx: &ParseContext<'doc>) -> Result<T, Self::Error>;
887}
888
889#[derive(Debug, thiserror::Error, Clone, PartialEq)]
890#[error("parse error: {kind}")]
891pub struct ParseError {
892    pub node_id: NodeId,
893    pub kind: ParseErrorKind,
894}
895
896/// Error type for parsing failures.
897#[derive(Debug, thiserror::Error, Clone, PartialEq)]
898pub enum ParseErrorKind {
899    /// Unexpected uninitialized value.
900    #[error("unexpected uninitialized value")]
901    UnexpectedHole,
902
903    /// Type mismatch between expected and actual value.
904    #[error("type mismatch: expected {expected}, got {actual}")]
905    TypeMismatch {
906        expected: ValueKind,
907        actual: ValueKind,
908    },
909
910    /// Required field is missing.
911    #[error("missing field: {0}")]
912    MissingField(String),
913
914    /// Required extension is missing.
915    #[error("missing extension: ${0}")]
916    MissingExtension(String),
917
918    /// Unknown variant in a union type.
919    #[error("unknown variant: {0}")]
920    UnknownVariant(String),
921
922    /// Value is out of valid range.
923    #[error("value out of range: {0}")]
924    OutOfRange(String),
925
926    /// Invalid value pattern or format.
927    ///
928    /// Used for validation errors in types like regex, URL, UUID, etc.
929    /// - `kind`: Type of validation (e.g., "regex", "url", "uuid", "pattern: <expected>")
930    /// - `reason`: Human-readable error message explaining the failure
931    #[error("invalid {kind}: {reason}")]
932    InvalidPattern { kind: String, reason: String },
933
934    /// Nested parse error with path context.
935    #[error("at {path}: {source}")]
936    Nested {
937        path: String,
938        #[source]
939        source: Box<ParseErrorKind>,
940    },
941
942    /// Invalid identifier.
943    #[error("invalid identifier: {0}")]
944    InvalidIdentifier(#[from] IdentifierError),
945
946    /// Unexpected tuple length.
947    #[error("unexpected tuple length: expected {expected}, got {actual}")]
948    UnexpectedTupleLength { expected: usize, actual: usize },
949
950    /// Unknown field in record.
951    #[error("unknown field: {0}")]
952    UnknownField(String),
953
954    /// Unknown extension on node.
955    #[error("unknown extension: ${0}")]
956    UnknownExtension(Identifier),
957
958    /// Invalid key type in record (expected string).
959    #[error("invalid key type in record: expected string key, got {0:?}")]
960    InvalidKeyType(crate::value::ObjectKey),
961
962    /// No variant matched in union type.
963    #[error("no matching variant{}", variant.as_ref().map(|v| format!(" (variant: {})", v)).unwrap_or_default())]
964    NoMatchingVariant {
965        /// Variant name extracted (if any).
966        variant: Option<String>,
967    },
968
969    /// Conflicting variant tags: $variant and repr extracted different variant names.
970    #[error("conflicting variant tags: $variant = {explicit}, repr = {repr}")]
971    ConflictingVariantTags { explicit: String, repr: String },
972
973    /// Multiple variants matched with no priority to resolve.
974    #[error("ambiguous union: {0:?}")]
975    AmbiguousUnion(Vec<String>),
976
977    /// Literal value mismatch.
978    #[error("literal value mismatch: expected {expected}, got {actual}")]
979    // FIXME: Use EureDocument instead of String?
980    LiteralMismatch { expected: String, actual: String },
981
982    /// Variant path provided but type is not a union.
983    #[error("unexpected variant path: {0}")]
984    UnexpectedVariantPath(VariantPath),
985
986    /// $variant extension has invalid type (not a string).
987    #[error("$variant must be a string, got {0}")]
988    InvalidVariantType(ValueKind),
989
990    /// $variant extension has invalid path syntax.
991    #[error("invalid $variant path syntax: {0}")]
992    InvalidVariantPath(String),
993
994    /// Tried to parse record fields while in extension flatten scope.
995    /// This happens when using #[eure(flatten_ext)] with a type that calls parse_record().
996    #[error(
997        "cannot parse record in extension scope: use #[eure(flatten)] instead of #[eure(flatten_ext)]"
998    )]
999    RecordInExtensionScope,
1000
1001    /// Unexpected array length.
1002    #[error("unexpected array length: expected {expected}, got {actual}")]
1003    UnexpectedArrayLength { expected: usize, actual: usize },
1004
1005    /// Expected a primitive value but got a non-primitive node.
1006    #[error("expected primitive value, got {actual}")]
1007    NotPrimitive { actual: ValueKind },
1008}
1009
1010impl ParseErrorKind {
1011    /// Wrap this error with path context.
1012    pub fn at(self, path: impl Into<String>) -> Self {
1013        ParseErrorKind::Nested {
1014            path: path.into(),
1015            source: Box::new(self),
1016        }
1017    }
1018}
1019
1020impl<'doc> EureDocument {
1021    /// Parse a value of type T from the given node.
1022    pub fn parse<T: FromEure<'doc, T>>(&'doc self, node_id: NodeId) -> Result<T, T::Error> {
1023        self.parse_with(node_id, T::parse)
1024    }
1025
1026    /// Parse a value of type T from the given node using a marker/strategy type M.
1027    ///
1028    /// This is used for parsing remote types where `M` implements
1029    /// `FromEure<'doc, T>` but `T` doesn't implement `FromEure` itself.
1030    pub fn parse_via<M, T>(&'doc self, node_id: NodeId) -> Result<T, M::Error>
1031    where
1032        M: FromEure<'doc, T>,
1033    {
1034        let ctx = self.parse_context(node_id);
1035        M::parse(&ctx)
1036    }
1037
1038    pub fn parse_with<T: DocumentParser<'doc>>(
1039        &'doc self,
1040        node_id: NodeId,
1041        mut parser: T,
1042    ) -> Result<T::Output, T::Error> {
1043        parser.parse(&self.parse_context(node_id))
1044    }
1045
1046    /// Create a parse context at the given node.
1047    pub fn parse_context(&'doc self, node_id: NodeId) -> ParseContext<'doc> {
1048        ParseContext::new(self, node_id)
1049    }
1050
1051    /// Parse a node as a record.
1052    ///
1053    /// Convenience method equivalent to `doc.parse_context(node_id).parse_record()`.
1054    pub fn parse_record(&'doc self, node_id: NodeId) -> Result<RecordParser<'doc>, ParseError> {
1055        RecordParser::from_doc_and_node(self, node_id)
1056    }
1057
1058    /// Create a parse context for extension parsing.
1059    ///
1060    /// Convenience method equivalent to `doc.parse_context(node_id)`.
1061    /// Use the returned context's `parse_ext()`, `ext()`, etc. methods.
1062    pub fn parse_extension_context(&'doc self, node_id: NodeId) -> ParseContext<'doc> {
1063        ParseContext::new(self, node_id)
1064    }
1065
1066    /// Parse a node as a tuple.
1067    ///
1068    /// Convenience method equivalent to `doc.parse_context(node_id).parse_tuple()`.
1069    pub fn parse_tuple(&'doc self, node_id: NodeId) -> Result<TupleParser<'doc>, ParseError> {
1070        TupleParser::from_doc_and_node(self, node_id)
1071    }
1072}
1073
1074impl<'doc> FromEure<'doc> for EureDocument {
1075    type Error = ParseError;
1076
1077    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1078        Ok(ctx.doc().node_subtree_to_document(ctx.node_id()))
1079    }
1080}
1081
1082impl<'doc> FromEure<'doc> for &'doc str {
1083    type Error = ParseError;
1084
1085    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1086        if let PrimitiveValue::Text(text) = ctx.parse_primitive()? {
1087            return Ok(text.as_str());
1088        }
1089        Err(ctx.unexpected_kind(ValueKind::Text))
1090    }
1091}
1092
1093impl FromEure<'_> for String {
1094    type Error = ParseError;
1095
1096    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1097        ctx.parse::<&str>().map(String::from)
1098    }
1099}
1100
1101#[diagnostic::do_not_recommend]
1102impl<'doc, T> FromEure<'doc> for Cow<'static, T>
1103where
1104    T: ToOwned + ?Sized,
1105    T::Owned: FromEure<'doc>,
1106{
1107    type Error = <T::Owned as FromEure<'doc>>::Error;
1108
1109    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1110        <T::Owned as FromEure<'doc>>::parse(ctx).map(Cow::Owned)
1111    }
1112}
1113
1114impl FromEure<'_> for Text {
1115    type Error = ParseError;
1116
1117    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1118        if let PrimitiveValue::Text(text) = ctx.parse_primitive()? {
1119            return Ok(text.clone());
1120        }
1121        Err(ctx.unexpected_kind(ValueKind::Text))
1122    }
1123}
1124
1125impl FromEure<'_> for bool {
1126    type Error = ParseError;
1127
1128    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1129        if let PrimitiveValue::Bool(b) = ctx.parse_primitive()? {
1130            return Ok(*b);
1131        }
1132        Err(ctx.unexpected_kind(ValueKind::Bool))
1133    }
1134}
1135
1136impl FromEure<'_> for BigInt {
1137    type Error = ParseError;
1138
1139    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1140        if let PrimitiveValue::Integer(i) = ctx.parse_primitive()? {
1141            return Ok(i.clone());
1142        }
1143        Err(ctx.unexpected_kind(ValueKind::Integer))
1144    }
1145}
1146
1147impl FromEure<'_> for f32 {
1148    type Error = ParseError;
1149
1150    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1151        if let PrimitiveValue::F32(f) = ctx.parse_primitive()? {
1152            return Ok(*f);
1153        }
1154        Err(ctx.unexpected_kind(ValueKind::F32))
1155    }
1156}
1157
1158impl FromEure<'_> for f64 {
1159    type Error = ParseError;
1160
1161    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1162        match ctx.parse_primitive()? {
1163            PrimitiveValue::F32(f) => Ok(*f as f64),
1164            PrimitiveValue::F64(f) => Ok(*f),
1165            _ => Err(ctx.unexpected_kind(ValueKind::F64)),
1166        }
1167    }
1168}
1169
1170macro_rules! impl_from_eure_int {
1171    ($($ty:ty),*) => {
1172        $(
1173            impl FromEure<'_> for $ty {
1174                type Error = ParseError;
1175
1176                fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1177                    let value: BigInt = ctx.parse()?;
1178                    <$ty>::try_from(&value).map_err(|_| ParseError {
1179                        node_id: ctx.node_id(),
1180                        kind: ParseErrorKind::OutOfRange(
1181                            format!("value {} out of {} range", value, stringify!($ty)),
1182                        ),
1183                    })
1184                }
1185            }
1186        )*
1187    };
1188}
1189
1190impl_from_eure_int!(
1191    u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
1192);
1193
1194impl<'doc> FromEure<'doc> for &'doc PrimitiveValue {
1195    type Error = ParseError;
1196
1197    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1198        ctx.parse_primitive()
1199    }
1200}
1201
1202impl FromEure<'_> for PrimitiveValue {
1203    type Error = ParseError;
1204
1205    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1206        ctx.parse::<&PrimitiveValue>().cloned()
1207    }
1208}
1209
1210impl FromEure<'_> for Identifier {
1211    type Error = ParseError;
1212
1213    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1214        if let PrimitiveValue::Text(text) = ctx.parse_primitive()? {
1215            return text
1216                .content
1217                .parse()
1218                .map_err(ParseErrorKind::InvalidIdentifier)
1219                .map_err(|kind| ParseError {
1220                    node_id: ctx.node_id(),
1221                    kind,
1222                });
1223        }
1224        Err(ctx.unexpected_kind(ValueKind::Text))
1225    }
1226}
1227
1228impl<'doc> FromEure<'doc> for &'doc NodeArray {
1229    type Error = ParseError;
1230
1231    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1232        ctx.ensure_no_variant_path()?;
1233        match &ctx.node().content {
1234            NodeValue::Array(array) => Ok(array),
1235            _ => Err(ctx.unexpected_kind(ValueKind::Array)),
1236        }
1237    }
1238}
1239
1240/// `Vec<M>` parses `Vec<T>` using M's FromEure implementation.
1241///
1242/// When `M = T`, this is standard `Vec<T>` parsing.
1243/// When `M ≠ T`, M acts as a strategy type for parsing remote type T.
1244#[diagnostic::do_not_recommend]
1245impl<'doc, M, T> FromEure<'doc, Vec<T>> for Vec<M>
1246where
1247    M: FromEure<'doc, T>,
1248    M::Error: From<ParseError>,
1249{
1250    type Error = M::Error;
1251
1252    fn parse(ctx: &ParseContext<'doc>) -> Result<Vec<T>, Self::Error> {
1253        ctx.ensure_no_variant_path()?;
1254        match &ctx.node().content {
1255            NodeValue::Array(array) => array
1256                .iter()
1257                .map(|item| M::parse(&ctx.at(*item)))
1258                .collect::<Result<Vec<_>, _>>(),
1259            _ => Err(ctx.unexpected_kind(ValueKind::Array).into()),
1260        }
1261    }
1262}
1263
1264/// `[M; N]` parses `[T; N]` using M's FromEure implementation.
1265///
1266/// When `M = T`, this is standard fixed-size array parsing.
1267/// When `M ≠ T`, M acts as a strategy type for parsing remote type T.
1268#[diagnostic::do_not_recommend]
1269impl<'doc, M, T, const N: usize> FromEure<'doc, [T; N]> for [M; N]
1270where
1271    M: FromEure<'doc, T>,
1272    M::Error: From<ParseError>,
1273{
1274    type Error = M::Error;
1275
1276    fn parse(ctx: &ParseContext<'doc>) -> Result<[T; N], Self::Error> {
1277        ctx.ensure_no_variant_path()?;
1278        match &ctx.node().content {
1279            NodeValue::Array(array) => {
1280                let node_ids: [NodeId; N] = array.try_into_array().ok_or_else(|| ParseError {
1281                    node_id: ctx.node_id(),
1282                    kind: ParseErrorKind::UnexpectedArrayLength {
1283                        expected: N,
1284                        actual: array.len(),
1285                    },
1286                })?;
1287                let mut parsed = Vec::with_capacity(N);
1288                for id in node_ids {
1289                    parsed.push(M::parse(&ctx.at(id))?);
1290                }
1291                let parsed: [T; N] = parsed
1292                    .try_into()
1293                    .unwrap_or_else(|_| unreachable!("length was asserted previously"));
1294                Ok(parsed)
1295            }
1296            _ => Err(ctx.unexpected_kind(ValueKind::Array).into()),
1297        }
1298    }
1299}
1300
1301/// `IndexSet<M>` parses `IndexSet<T>` using M's FromEure implementation.
1302#[diagnostic::do_not_recommend]
1303impl<'doc, M, T> FromEure<'doc, IndexSet<T>> for IndexSet<M>
1304where
1305    M: FromEure<'doc, T>,
1306    T: Eq + std::hash::Hash,
1307    M::Error: From<ParseError>,
1308{
1309    type Error = M::Error;
1310    fn parse(ctx: &ParseContext<'doc>) -> Result<IndexSet<T>, Self::Error> {
1311        ctx.ensure_no_variant_path()?;
1312        match &ctx.node().content {
1313            NodeValue::Array(array) => array
1314                .iter()
1315                .map(|item| M::parse(&ctx.at(*item)))
1316                .collect::<Result<IndexSet<_>, _>>(),
1317            _ => Err(ctx.unexpected_kind(ValueKind::Array).into()),
1318        }
1319    }
1320}
1321
1322macro_rules! parse_tuple {
1323    ($n:expr, $($var:ident),*) => {
1324        #[diagnostic::do_not_recommend]
1325        impl<'doc, $($var),*, Err> FromEure<'doc> for ($($var),*,)
1326            where $($var: FromEure<'doc, Error = Err>),*,
1327            Err: From<ParseError>,
1328        {
1329            type Error = Err;
1330
1331            fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1332                ctx.ensure_no_variant_path()?;
1333                let tuple = match &ctx.node().content {
1334                    NodeValue::Tuple(tuple) => tuple,
1335                    _ => return Err(ctx.unexpected_kind(ValueKind::Tuple).into()),
1336                };
1337                if tuple.len() != $n {
1338                    return Err(ParseError { node_id: ctx.node_id(), kind: ParseErrorKind::UnexpectedTupleLength { expected: $n, actual: tuple.len() } }.into());
1339                }
1340                let mut iter = tuple.iter();
1341                Ok(($($var::parse(&ctx.at(*iter.next().unwrap()))?),*,))
1342            }
1343        }
1344    }
1345}
1346
1347parse_tuple!(1, A);
1348parse_tuple!(2, A, B);
1349parse_tuple!(3, A, B, C);
1350parse_tuple!(4, A, B, C, D);
1351parse_tuple!(5, A, B, C, D, E);
1352parse_tuple!(6, A, B, C, D, E, F);
1353parse_tuple!(7, A, B, C, D, E, F, G);
1354parse_tuple!(8, A, B, C, D, E, F, G, H);
1355parse_tuple!(9, A, B, C, D, E, F, G, H, I);
1356parse_tuple!(10, A, B, C, D, E, F, G, H, I, J);
1357parse_tuple!(11, A, B, C, D, E, F, G, H, I, J, K);
1358parse_tuple!(12, A, B, C, D, E, F, G, H, I, J, K, L);
1359parse_tuple!(13, A, B, C, D, E, F, G, H, I, J, K, L, M);
1360parse_tuple!(14, A, B, C, D, E, F, G, H, I, J, K, L, M, N);
1361parse_tuple!(15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
1362parse_tuple!(16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
1363
1364/// Internal macro for parsing maps.
1365/// $M is the marker type, $T is the target type.
1366/// When $M = $T, this is standard parsing.
1367/// When $M ≠ $T, $M acts as a strategy type for parsing remote type $T.
1368macro_rules! parse_map {
1369    ($ctx:ident, $M:ty, $T:ty) => {{
1370        $ctx.ensure_no_variant_path()?;
1371
1372        // Check scope: Extension scope iterates extensions, otherwise record fields
1373        if $ctx.parser_scope() == Some(ParserScope::Extension) {
1374            // Extension scope: iterate UNACCESSED extensions only
1375            let node = $ctx.node();
1376            let flatten_ctx = $ctx.flatten_ctx();
1377            let accessed = flatten_ctx.map(|fc| fc.accessed_set());
1378            node.extensions
1379                .iter()
1380                .filter(|(ident, _)| {
1381                    // Only include extensions not already accessed
1382                    accessed.map_or(true, |a| !a.has_ext(ident))
1383                })
1384                .map(|(ident, &node_id)| {
1385                    // Mark extension as accessed so deny_unknown_extensions won't complain
1386                    if let Some(fc) = &flatten_ctx {
1387                        fc.add_ext((*ident).clone());
1388                    }
1389                    Ok((
1390                        K::from_extension_ident(ident).map_err(|kind| ParseError {
1391                            node_id: $ctx.node_id(),
1392                            kind,
1393                        })?,
1394                        <$M as FromEure<'doc, $T>>::parse(&$ctx.at(node_id))?,
1395                    ))
1396                })
1397                .collect::<Result<_, _>>()
1398        } else {
1399            // Record scope or no scope: iterate record fields
1400            let map = match &$ctx.node().content {
1401                NodeValue::Map(map) => map,
1402                _ => {
1403                    return Err($ctx.unexpected_kind(ValueKind::Map).into());
1404                }
1405            };
1406            // If in flatten context with Record scope, only iterate UNACCESSED fields
1407            let flatten_ctx = $ctx
1408                .flatten_ctx()
1409                .filter(|fc| fc.scope() == ParserScope::Record);
1410            let accessed = flatten_ctx.map(|fc| fc.accessed_set().clone());
1411            map.iter()
1412                .filter(|(key, _)| {
1413                    match &accessed {
1414                        Some(acc) => match key {
1415                            ObjectKey::String(s) => !acc.has_field(s),
1416                            _ => true, // Non-string keys are always included
1417                        },
1418                        None => true, // No flatten context means include all
1419                    }
1420                })
1421                .map(|(key, value)| {
1422                    // Mark field as accessed so deny_unknown_fields won't complain
1423                    if let Some(fc) = &flatten_ctx {
1424                        if let ObjectKey::String(s) = key {
1425                            fc.add_field(s);
1426                        }
1427                    }
1428                    Ok((
1429                        K::from_object_key(key).map_err(|kind| ParseError {
1430                            node_id: $ctx.node_id(),
1431                            kind,
1432                        })?,
1433                        <$M as FromEure<'doc, $T>>::parse(&$ctx.at(*value))?,
1434                    ))
1435                })
1436                .collect::<Result<_, _>>()
1437        }
1438    }};
1439}
1440
1441/// `Map<K, M>` parses `Map<K, T>` using M's FromEure implementation.
1442#[diagnostic::do_not_recommend]
1443impl<'doc, K, M, T> FromEure<'doc, Map<K, T>> for Map<K, M>
1444where
1445    K: ParseObjectKey<'doc>,
1446    M: FromEure<'doc, T>,
1447    M::Error: From<ParseError>,
1448{
1449    type Error = M::Error;
1450
1451    fn parse(ctx: &ParseContext<'doc>) -> Result<Map<K, T>, Self::Error> {
1452        parse_map!(ctx, M, T)
1453    }
1454}
1455
1456/// `BTreeMap<K, M>` parses `BTreeMap<K, T>` using M's FromEure implementation.
1457#[diagnostic::do_not_recommend]
1458impl<'doc, K, M, T> FromEure<'doc, BTreeMap<K, T>> for BTreeMap<K, M>
1459where
1460    K: ParseObjectKey<'doc>,
1461    M: FromEure<'doc, T>,
1462    M::Error: From<ParseError>,
1463{
1464    type Error = M::Error;
1465    fn parse(ctx: &ParseContext<'doc>) -> Result<BTreeMap<K, T>, Self::Error> {
1466        parse_map!(ctx, M, T)
1467    }
1468}
1469
1470/// `HashMap<K, M>` parses `HashMap<K, T>` using M's FromEure implementation.
1471#[diagnostic::do_not_recommend]
1472impl<'doc, K, M, T> FromEure<'doc, HashMap<K, T>> for HashMap<K, M>
1473where
1474    K: ParseObjectKey<'doc>,
1475    M: FromEure<'doc, T>,
1476    M::Error: From<ParseError>,
1477{
1478    type Error = M::Error;
1479    fn parse(ctx: &ParseContext<'doc>) -> Result<HashMap<K, T>, Self::Error> {
1480        parse_map!(ctx, M, T)
1481    }
1482}
1483
1484/// `IndexMap<K, M>` parses `IndexMap<K, T>` using M's FromEure implementation.
1485#[diagnostic::do_not_recommend]
1486impl<'doc, K, M, T> FromEure<'doc, IndexMap<K, T>> for IndexMap<K, M>
1487where
1488    K: ParseObjectKey<'doc>,
1489    M: FromEure<'doc, T>,
1490    M::Error: From<ParseError>,
1491{
1492    type Error = M::Error;
1493    fn parse(ctx: &ParseContext<'doc>) -> Result<IndexMap<K, T>, Self::Error> {
1494        parse_map!(ctx, M, T)
1495    }
1496}
1497
1498impl FromEure<'_> for regex::Regex {
1499    type Error = ParseError;
1500
1501    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1502        let pattern: &str = ctx.parse()?;
1503        regex::Regex::new(pattern).map_err(|e| ParseError {
1504            node_id: ctx.node_id(),
1505            kind: ParseErrorKind::InvalidPattern {
1506                kind: format!("regex '{}'", pattern),
1507                reason: e.to_string(),
1508            },
1509        })
1510    }
1511}
1512
1513/// `Option<M>` parses `Option<T>` using M's FromEure implementation.
1514///
1515/// When `M = T` (same type), this is standard `Option<T>` parsing.
1516/// When `M ≠ T`, M acts as a strategy type for parsing remote type T.
1517///
1518/// - `$variant: some` -> parse T via M
1519/// - `$variant: none` -> None
1520/// - No `$variant` and value is null -> None
1521/// - No `$variant` and value is not null -> try parsing as T (Some)
1522#[diagnostic::do_not_recommend]
1523impl<'doc, M, T> FromEure<'doc, Option<T>> for Option<M>
1524where
1525    M: FromEure<'doc, T>,
1526    M::Error: From<ParseError>,
1527{
1528    type Error = M::Error;
1529
1530    fn parse(ctx: &ParseContext<'doc>) -> Result<Option<T>, Self::Error> {
1531        ctx.parse_union::<Option<T>, M::Error>(VariantRepr::default())?
1532            .variant("some", (M::parse).map(Some))
1533            .variant("none", |ctx: &ParseContext<'_>| {
1534                if ctx.is_null() {
1535                    Ok(None)
1536                } else {
1537                    Err(ParseError {
1538                        node_id: ctx.node_id(),
1539                        kind: ParseErrorKind::TypeMismatch {
1540                            expected: ValueKind::Null,
1541                            actual: ctx.node().content.value_kind(),
1542                        },
1543                    }
1544                    .into())
1545                }
1546            })
1547            .parse()
1548    }
1549}
1550
1551/// `Result<MT, ME>` parses `Result<T, E>` using MT and ME's FromEure implementations.
1552///
1553/// When `MT = T` and `ME = E` (same types), this is standard `Result` parsing.
1554/// When different, MT and ME act as strategy types for parsing remote types.
1555///
1556/// - `$variant: ok` -> parse T via MT
1557/// - `$variant: err` -> parse E via ME
1558/// - No `$variant` -> try Ok first, then Err (priority-based)
1559#[diagnostic::do_not_recommend]
1560impl<'doc, MT, T, ME, E, Err> FromEure<'doc, Result<T, E>> for Result<MT, ME>
1561where
1562    MT: FromEure<'doc, T, Error = Err>,
1563    ME: FromEure<'doc, E, Error = Err>,
1564    Err: From<ParseError>,
1565{
1566    type Error = Err;
1567
1568    fn parse(ctx: &ParseContext<'doc>) -> Result<Result<T, E>, Self::Error> {
1569        ctx.parse_union::<Result<T, E>, Self::Error>(VariantRepr::default())?
1570            .variant("ok", (MT::parse).map(Ok))
1571            .variant("err", (ME::parse).map(Err))
1572            .parse()
1573    }
1574}
1575
1576impl FromEure<'_> for crate::data_model::VariantRepr {
1577    type Error = ParseError;
1578
1579    fn parse(ctx: &ParseContext<'_>) -> Result<Self, Self::Error> {
1580        use crate::data_model::VariantRepr;
1581
1582        // Check if it's a simple string value
1583        if let Ok(value) = ctx.parse::<&str>() {
1584            return match value {
1585                "external" => Ok(VariantRepr::External),
1586                "untagged" => Ok(VariantRepr::Untagged),
1587                _ => Err(ParseError {
1588                    node_id: ctx.node_id(),
1589                    kind: ParseErrorKind::UnknownVariant(value.to_string()),
1590                }),
1591            };
1592        }
1593
1594        // Otherwise, it should be a record with tag/content fields
1595        let rec = ctx.parse_record()?;
1596
1597        let tag = rec.parse_field_optional::<String>("tag")?;
1598        let content = rec.parse_field_optional::<String>("content")?;
1599
1600        rec.allow_unknown_fields()?;
1601
1602        match (tag, content) {
1603            (Some(tag), Some(content)) => Ok(VariantRepr::Adjacent { tag, content }),
1604            (Some(tag), None) => Ok(VariantRepr::Internal { tag }),
1605            (None, None) => Ok(VariantRepr::External),
1606            (None, Some(_)) => Err(ParseError {
1607                node_id: ctx.node_id(),
1608                kind: ParseErrorKind::MissingField(
1609                    "tag (required when content is present)".to_string(),
1610                ),
1611            }),
1612        }
1613    }
1614}
1615
1616#[diagnostic::do_not_recommend]
1617impl<'doc> FromEure<'doc> for () {
1618    type Error = ParseError;
1619    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1620        ctx.parse_tuple()?.finish()
1621    }
1622}
1623
1624impl<'doc> FromEure<'doc> for NodeId {
1625    type Error = ParseError;
1626    fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
1627        Ok(ctx.node_id())
1628    }
1629}
1630
1631pub trait DocumentParser<'doc> {
1632    type Output;
1633    type Error;
1634    fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error>;
1635}
1636
1637pub struct AlwaysParser<T, E>(T, PhantomData<E>);
1638
1639impl<T, E> AlwaysParser<T, E> {
1640    pub fn new(value: T) -> AlwaysParser<T, E> {
1641        Self(value, PhantomData)
1642    }
1643}
1644
1645impl<'doc, T, E> DocumentParser<'doc> for AlwaysParser<T, E>
1646where
1647    T: Clone,
1648{
1649    type Output = T;
1650    type Error = E;
1651    fn parse(&mut self, _ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1652        Ok(self.0.clone())
1653    }
1654}
1655
1656impl<'doc, T, F, E> DocumentParser<'doc> for F
1657where
1658    F: FnMut(&ParseContext<'doc>) -> Result<T, E>,
1659{
1660    type Output = T;
1661    type Error = E;
1662    fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1663        (*self)(ctx)
1664    }
1665}
1666
1667pub struct LiteralParser<T>(pub T);
1668
1669impl<'doc, T, E> DocumentParser<'doc> for LiteralParser<T>
1670where
1671    T: FromEure<'doc, Error = E> + PartialEq + core::fmt::Debug,
1672    E: From<ParseError>,
1673{
1674    type Output = T;
1675    type Error = E;
1676    fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1677        let value: T = ctx.parse::<T>()?;
1678        if value == self.0 {
1679            Ok(value)
1680        } else {
1681            Err(ParseError {
1682                node_id: ctx.node_id(),
1683                kind: ParseErrorKind::LiteralMismatch {
1684                    expected: format!("{:?}", self.0),
1685                    actual: format!("{:?}", value),
1686                },
1687            }
1688            .into())
1689        }
1690    }
1691}
1692
1693/// A parser that matches a specific string literal as an enum variant name.
1694///
1695/// Similar to [`LiteralParser`], but returns [`ParseErrorKind::UnknownVariant`]
1696/// on mismatch instead of [`ParseErrorKind::LiteralMismatch`]. This provides
1697/// better error messages when parsing unit enum variants as string literals.
1698pub struct VariantLiteralParser(pub &'static str);
1699
1700impl<'doc> DocumentParser<'doc> for VariantLiteralParser {
1701    type Output = &'static str;
1702    type Error = ParseError;
1703    fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1704        let value: &str = ctx.parse()?;
1705        if value == self.0 {
1706            Ok(self.0)
1707        } else {
1708            Err(ParseError {
1709                node_id: ctx.node_id(),
1710                kind: ParseErrorKind::UnknownVariant(value.to_string()),
1711            })
1712        }
1713    }
1714}
1715
1716pub struct MapParser<T, F> {
1717    parser: T,
1718    mapper: F,
1719}
1720
1721impl<'doc, T, O, F> DocumentParser<'doc> for MapParser<T, F>
1722where
1723    T: DocumentParser<'doc>,
1724    F: FnMut(T::Output) -> O,
1725{
1726    type Output = O;
1727    type Error = T::Error;
1728    fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1729        self.parser.parse(ctx).map(|value| (self.mapper)(value))
1730    }
1731}
1732
1733pub struct AndThenParser<T, F> {
1734    parser: T,
1735    mapper: F,
1736}
1737
1738impl<'doc, T, O, F, E> DocumentParser<'doc> for AndThenParser<T, F>
1739where
1740    T: DocumentParser<'doc, Error = E>,
1741    F: Fn(T::Output) -> Result<O, E>,
1742{
1743    type Output = O;
1744    type Error = E;
1745    fn parse(&mut self, ctx: &ParseContext<'doc>) -> Result<Self::Output, Self::Error> {
1746        let value = self.parser.parse(ctx)?;
1747        (self.mapper)(value)
1748    }
1749}
1750
1751pub trait DocumentParserExt<'doc>: DocumentParser<'doc> + Sized {
1752    fn map<O, F>(self, mapper: F) -> MapParser<Self, F>
1753    where
1754        F: Fn(Self::Output) -> O,
1755    {
1756        MapParser {
1757            parser: self,
1758            mapper,
1759        }
1760    }
1761
1762    fn and_then<O, F>(self, mapper: F) -> AndThenParser<Self, F>
1763    where
1764        F: Fn(Self::Output) -> Result<O, Self::Error>,
1765    {
1766        AndThenParser {
1767            parser: self,
1768            mapper,
1769        }
1770    }
1771}
1772
1773impl<'doc, T> DocumentParserExt<'doc> for T where T: DocumentParser<'doc> {}
1774
1775#[cfg(test)]
1776mod tests {
1777    use super::*;
1778    use crate::document::node::NodeValue;
1779    use crate::eure;
1780    use crate::identifier::Identifier;
1781    use crate::text::Text;
1782    use crate::value::ObjectKey;
1783    use num_bigint::BigInt;
1784
1785    fn identifier(s: &str) -> Identifier {
1786        s.parse().unwrap()
1787    }
1788
1789    /// Create a document with a single field that has a $variant extension
1790    fn create_record_with_variant(
1791        field_name: &str,
1792        value: NodeValue,
1793        variant: &str,
1794    ) -> EureDocument {
1795        let mut doc = EureDocument::new();
1796        let root_id = doc.get_root_id();
1797
1798        // Add field
1799        let field_id = doc
1800            .add_map_child(ObjectKey::String(field_name.to_string()), root_id)
1801            .unwrap()
1802            .node_id;
1803        doc.node_mut(field_id).content = value;
1804
1805        // Add $variant extension
1806        let variant_node_id = doc
1807            .add_extension(identifier("variant"), field_id)
1808            .unwrap()
1809            .node_id;
1810        doc.node_mut(variant_node_id).content =
1811            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(variant.to_string())));
1812
1813        doc
1814    }
1815
1816    #[test]
1817    fn test_option_some_tagged() {
1818        let doc = create_record_with_variant(
1819            "value",
1820            NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(42))),
1821            "some",
1822        );
1823        let root_id = doc.get_root_id();
1824        let rec = doc.parse_record(root_id).unwrap();
1825        let value: Option<i32> = rec.parse_field("value").unwrap();
1826        assert_eq!(value, Some(42));
1827    }
1828
1829    #[test]
1830    fn test_option_none_tagged() {
1831        let doc =
1832            create_record_with_variant("value", NodeValue::Primitive(PrimitiveValue::Null), "none");
1833        let root_id = doc.get_root_id();
1834        let rec = doc.parse_record(root_id).unwrap();
1835        let value: Option<i32> = rec.parse_field("value").unwrap();
1836        assert_eq!(value, None);
1837    }
1838
1839    #[test]
1840    fn test_option_some_untagged() {
1841        // Without $variant, non-null value is Some
1842        let doc = eure!({ value = 42 });
1843        let root_id = doc.get_root_id();
1844        let rec = doc.parse_record(root_id).unwrap();
1845        let value: Option<i32> = rec.parse_field("value").unwrap();
1846        assert_eq!(value, Some(42));
1847    }
1848
1849    #[test]
1850    fn test_option_none_untagged() {
1851        // Without $variant, null is None
1852        let doc = eure!({ value = null });
1853        let root_id = doc.get_root_id();
1854        let rec = doc.parse_record(root_id).unwrap();
1855        let value: Option<i32> = rec.parse_field("value").unwrap();
1856        assert_eq!(value, None);
1857    }
1858
1859    #[test]
1860    fn test_result_ok_tagged() {
1861        let doc = create_record_with_variant(
1862            "value",
1863            NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(42))),
1864            "ok",
1865        );
1866        let root_id = doc.get_root_id();
1867        let rec = doc.parse_record(root_id).unwrap();
1868        let value: Result<i32, String> = rec.parse_field("value").unwrap();
1869        assert_eq!(value, Ok(42));
1870    }
1871
1872    #[test]
1873    fn test_result_err_tagged() {
1874        let doc = create_record_with_variant(
1875            "value",
1876            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext(
1877                "error message".to_string(),
1878            ))),
1879            "err",
1880        );
1881        let root_id = doc.get_root_id();
1882        let rec = doc.parse_record(root_id).unwrap();
1883        let value: Result<i32, String> = rec.parse_field("value").unwrap();
1884        assert_eq!(value, Err("error message".to_string()));
1885    }
1886
1887    #[test]
1888    fn test_nested_result_option_ok_some() {
1889        // $variant: ok.some - Result<Option<i32>, String>
1890        let doc = create_record_with_variant(
1891            "value",
1892            NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(42))),
1893            "ok.some",
1894        );
1895        let root_id = doc.get_root_id();
1896        let rec = doc.parse_record(root_id).unwrap();
1897        let value: Result<Option<i32>, String> = rec.parse_field("value").unwrap();
1898        assert_eq!(value, Ok(Some(42)));
1899    }
1900
1901    #[test]
1902    fn test_nested_result_option_ok_none() {
1903        // $variant: ok.none - Result<Option<i32>, String>
1904        let doc = create_record_with_variant(
1905            "value",
1906            NodeValue::Primitive(PrimitiveValue::Null),
1907            "ok.none",
1908        );
1909        let root_id = doc.get_root_id();
1910        let rec = doc.parse_record(root_id).unwrap();
1911        let value: Result<Option<i32>, String> = rec.parse_field("value").unwrap();
1912        assert_eq!(value, Ok(None));
1913    }
1914
1915    #[test]
1916    fn test_nested_result_option_err() {
1917        // $variant: err - Result<Option<i32>, String>
1918        let doc = create_record_with_variant(
1919            "value",
1920            NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("error".to_string()))),
1921            "err",
1922        );
1923        let root_id = doc.get_root_id();
1924        let rec = doc.parse_record(root_id).unwrap();
1925        let value: Result<Option<i32>, String> = rec.parse_field("value").unwrap();
1926        assert_eq!(value, Err("error".to_string()));
1927    }
1928
1929    #[test]
1930    fn test_deeply_nested_option_option() {
1931        // $variant: some.some - Option<Option<i32>>
1932        let doc = create_record_with_variant(
1933            "value",
1934            NodeValue::Primitive(PrimitiveValue::Integer(BigInt::from(42))),
1935            "some.some",
1936        );
1937        let root_id = doc.get_root_id();
1938        let rec = doc.parse_record(root_id).unwrap();
1939        let value: Option<Option<i32>> = rec.parse_field("value").unwrap();
1940        assert_eq!(value, Some(Some(42)));
1941    }
1942
1943    #[test]
1944    fn test_deeply_nested_option_none() {
1945        // $variant: some.none - Option<Option<i32>> inner None
1946        let doc = create_record_with_variant(
1947            "value",
1948            NodeValue::Primitive(PrimitiveValue::Null),
1949            "some.none",
1950        );
1951        let root_id = doc.get_root_id();
1952        let rec = doc.parse_record(root_id).unwrap();
1953        let value: Option<Option<i32>> = rec.parse_field("value").unwrap();
1954        assert_eq!(value, Some(None));
1955    }
1956
1957    #[test]
1958    fn test_outer_none() {
1959        // $variant: none - Option<Option<i32>> outer None
1960        let doc =
1961            create_record_with_variant("value", NodeValue::Primitive(PrimitiveValue::Null), "none");
1962        let root_id = doc.get_root_id();
1963        let rec = doc.parse_record(root_id).unwrap();
1964        let value: Option<Option<i32>> = rec.parse_field("value").unwrap();
1965        assert_eq!(value, None);
1966    }
1967
1968    // =========================================================================
1969    // BUG: parse_map! doesn't mark fields as accessed
1970    // =========================================================================
1971
1972    /// BUG: When parsing IndexMap via flatten, fields are not marked as accessed.
1973    /// This causes deny_unknown_fields() to report them as unknown.
1974    #[test]
1975    fn test_flatten_indexmap_marks_fields_as_accessed() {
1976        use indexmap::IndexMap;
1977
1978        let doc = eure!({
1979            name = "test"
1980            foo = "bar"
1981            baz = "qux"
1982        });
1983
1984        let root_id = doc.get_root_id();
1985        let rec = doc.parse_record(root_id).unwrap();
1986
1987        // Parse "name" as a regular field
1988        let _name: String = rec.parse_field("name").unwrap();
1989
1990        // Parse remaining fields via flatten into IndexMap
1991        let extra: IndexMap<String, String> = rec.flatten().parse().unwrap();
1992
1993        // Verify IndexMap captured the extra fields
1994        assert_eq!(extra.get("foo"), Some(&"bar".to_string()));
1995        assert_eq!(extra.get("baz"), Some(&"qux".to_string()));
1996
1997        // BUG: This fails with UnknownField("foo") because parse_map! doesn't
1998        // mark "foo" and "baz" as accessed when parsing into IndexMap
1999        rec.deny_unknown_fields().unwrap();
2000    }
2001
2002    // =========================================================================
2003    // Remote type support tests
2004    // =========================================================================
2005
2006    /// A "remote" type that we can't implement FromEure for directly.
2007    #[derive(Debug, PartialEq)]
2008    struct RemoteDuration {
2009        secs: u64,
2010        nanos: u32,
2011    }
2012
2013    /// Marker type that implements FromEure<'doc, RemoteDuration>.
2014    struct RemoteDurationDef;
2015
2016    impl<'doc> FromEure<'doc, RemoteDuration> for RemoteDurationDef {
2017        type Error = ParseError;
2018
2019        fn parse(ctx: &ParseContext<'doc>) -> Result<RemoteDuration, Self::Error> {
2020            let rec = ctx.parse_record()?;
2021            let secs: u64 = rec.parse_field("secs")?;
2022            let nanos: u32 = rec.parse_field("nanos")?;
2023            rec.deny_unknown_fields()?;
2024            Ok(RemoteDuration { secs, nanos })
2025        }
2026    }
2027
2028    #[test]
2029    fn test_remote_type_basic_parsing() {
2030        let doc = eure!({ secs = 10, nanos = 500 });
2031        let root_id = doc.get_root_id();
2032
2033        // Use parse_via to parse RemoteDuration via RemoteDurationDef
2034        let duration: RemoteDuration = doc.parse_via::<RemoteDurationDef, _>(root_id).unwrap();
2035
2036        assert_eq!(
2037            duration,
2038            RemoteDuration {
2039                secs: 10,
2040                nanos: 500
2041            }
2042        );
2043    }
2044
2045    #[test]
2046    fn test_remote_type_in_option() {
2047        // When the marker type implements FromEure<T>,
2048        // Option<Marker> implements FromEure<Option<T>>
2049        let doc = eure!({ secs = 5, nanos = 0 });
2050        let root_id = doc.get_root_id();
2051
2052        // Parse Option<RemoteDuration> via Option<RemoteDurationDef>
2053        let duration: Option<RemoteDuration> = doc
2054            .parse_via::<Option<RemoteDurationDef>, _>(root_id)
2055            .unwrap();
2056
2057        assert_eq!(duration, Some(RemoteDuration { secs: 5, nanos: 0 }));
2058    }
2059
2060    #[test]
2061    fn test_remote_type_in_option_none() {
2062        let doc = eure!({ = null });
2063        let root_id = doc.get_root_id();
2064
2065        let duration: Option<RemoteDuration> = doc
2066            .parse_via::<Option<RemoteDurationDef>, _>(root_id)
2067            .unwrap();
2068
2069        assert_eq!(duration, None);
2070    }
2071
2072    #[test]
2073    fn test_remote_type_in_vec() {
2074        let doc = eure!({
2075            items[] { secs = 1, nanos = 0 }
2076            items[] { secs = 2, nanos = 100 }
2077        });
2078        let root_id = doc.get_root_id();
2079        let rec = doc.parse_record(root_id).unwrap();
2080        let items_ctx = rec.field("items").unwrap();
2081
2082        // Parse Vec<RemoteDuration> via Vec<RemoteDurationDef>
2083        let durations: Vec<RemoteDuration> =
2084            items_ctx.parse_via::<Vec<RemoteDurationDef>, _>().unwrap();
2085
2086        assert_eq!(
2087            durations,
2088            vec![
2089                RemoteDuration { secs: 1, nanos: 0 },
2090                RemoteDuration {
2091                    secs: 2,
2092                    nanos: 100
2093                },
2094            ]
2095        );
2096    }
2097
2098    #[test]
2099    fn test_remote_type_in_indexmap() {
2100        let doc = eure!({
2101            short { secs = 1, nanos = 0 }
2102            long { secs = 10, nanos = 0 }
2103        });
2104        let root_id = doc.get_root_id();
2105
2106        // Parse IndexMap<String, RemoteDuration> via IndexMap<String, RemoteDurationDef>
2107        let durations: IndexMap<String, RemoteDuration> = doc
2108            .parse_via::<IndexMap<String, RemoteDurationDef>, _>(root_id)
2109            .unwrap();
2110
2111        assert_eq!(durations.len(), 2);
2112        assert_eq!(
2113            durations.get("short"),
2114            Some(&RemoteDuration { secs: 1, nanos: 0 })
2115        );
2116        assert_eq!(
2117            durations.get("long"),
2118            Some(&RemoteDuration { secs: 10, nanos: 0 })
2119        );
2120    }
2121
2122    #[test]
2123    fn test_remote_type_in_nested_containers() {
2124        // Test Option<Vec<RemoteDuration>>
2125        let doc = eure!({
2126            items[] { secs = 1, nanos = 0 }
2127        });
2128        let root_id = doc.get_root_id();
2129        let rec = doc.parse_record(root_id).unwrap();
2130        let items_ctx = rec.field("items").unwrap();
2131
2132        // Parse Option<Vec<RemoteDuration>> via Option<Vec<RemoteDurationDef>>
2133        let durations: Option<Vec<RemoteDuration>> = items_ctx
2134            .parse_via::<Option<Vec<RemoteDurationDef>>, _>()
2135            .unwrap();
2136
2137        assert_eq!(durations, Some(vec![RemoteDuration { secs: 1, nanos: 0 }]));
2138    }
2139
2140    #[test]
2141    fn test_parse_context_parse_via() {
2142        let doc = eure!({ secs = 42, nanos = 123 });
2143        let root_id = doc.get_root_id();
2144        let ctx = doc.parse_context(root_id);
2145
2146        // Use parse_via on ParseContext
2147        let duration: RemoteDuration = ctx.parse_via::<RemoteDurationDef, _>().unwrap();
2148
2149        assert_eq!(
2150            duration,
2151            RemoteDuration {
2152                secs: 42,
2153                nanos: 123
2154            }
2155        );
2156    }
2157
2158    // =========================================================================
2159    // Fixed-size array tests
2160    // =========================================================================
2161
2162    #[test]
2163    fn test_array_basic_parsing() {
2164        let doc = eure!({ items = [1, 2, 3] });
2165        let root_id = doc.get_root_id();
2166        let rec = doc.parse_record(root_id).unwrap();
2167        let items: [i32; 3] = rec.parse_field("items").unwrap();
2168        assert_eq!(items, [1, 2, 3]);
2169    }
2170
2171    #[test]
2172    fn test_array_empty() {
2173        let doc = eure!({ items = [] });
2174        let root_id = doc.get_root_id();
2175        let rec = doc.parse_record(root_id).unwrap();
2176        let items: [i32; 0] = rec.parse_field("items").unwrap();
2177        assert_eq!(items, []);
2178    }
2179
2180    #[test]
2181    fn test_array_length_mismatch_too_few() {
2182        let doc = eure!({ items = [1, 2] });
2183        let root_id = doc.get_root_id();
2184        let rec = doc.parse_record(root_id).unwrap();
2185        let result: Result<[i32; 3], _> = rec.parse_field("items");
2186        assert!(result.is_err());
2187        let err = result.unwrap_err();
2188        assert!(matches!(
2189            err.kind,
2190            ParseErrorKind::UnexpectedArrayLength {
2191                expected: 3,
2192                actual: 2
2193            }
2194        ));
2195    }
2196
2197    #[test]
2198    fn test_array_length_mismatch_too_many() {
2199        let doc = eure!({ items = [1, 2, 3, 4] });
2200        let root_id = doc.get_root_id();
2201        let rec = doc.parse_record(root_id).unwrap();
2202        let result: Result<[i32; 3], _> = rec.parse_field("items");
2203        assert!(result.is_err());
2204        let err = result.unwrap_err();
2205        assert!(matches!(
2206            err.kind,
2207            ParseErrorKind::UnexpectedArrayLength {
2208                expected: 3,
2209                actual: 4
2210            }
2211        ));
2212    }
2213
2214    #[test]
2215    fn test_array_nested_types() {
2216        let doc = eure!({ items = ["a", "b"] });
2217        let root_id = doc.get_root_id();
2218        let rec = doc.parse_record(root_id).unwrap();
2219        let items: [String; 2] = rec.parse_field("items").unwrap();
2220        assert_eq!(items, ["a".to_string(), "b".to_string()]);
2221    }
2222
2223    #[test]
2224    fn test_array_in_option() {
2225        let doc = eure!({ items = [1, 2, 3] });
2226        let root_id = doc.get_root_id();
2227        let rec = doc.parse_record(root_id).unwrap();
2228        let items: Option<[i32; 3]> = rec.parse_field("items").unwrap();
2229        assert_eq!(items, Some([1, 2, 3]));
2230    }
2231
2232    #[test]
2233    fn test_array_of_arrays() {
2234        let doc = eure!({ matrix = [[1, 2], [3, 4]] });
2235        let root_id = doc.get_root_id();
2236        let rec = doc.parse_record(root_id).unwrap();
2237        let matrix: [[i32; 2]; 2] = rec.parse_field("matrix").unwrap();
2238        assert_eq!(matrix, [[1, 2], [3, 4]]);
2239    }
2240
2241    #[test]
2242    fn test_array_remote_type() {
2243        let doc = eure!({
2244            items[] { secs = 1, nanos = 0 }
2245            items[] { secs = 2, nanos = 100 }
2246        });
2247        let root_id = doc.get_root_id();
2248        let rec = doc.parse_record(root_id).unwrap();
2249        let items_ctx = rec.field("items").unwrap();
2250
2251        // Parse [RemoteDuration; 2] via [RemoteDurationDef; 2]
2252        let durations: [RemoteDuration; 2] =
2253            items_ctx.parse_via::<[RemoteDurationDef; 2], _>().unwrap();
2254
2255        assert_eq!(
2256            durations,
2257            [
2258                RemoteDuration { secs: 1, nanos: 0 },
2259                RemoteDuration {
2260                    secs: 2,
2261                    nanos: 100
2262                },
2263            ]
2264        );
2265    }
2266
2267    // =========================================================================
2268    // Cow tests
2269    // =========================================================================
2270
2271    #[test]
2272    fn test_cow_static_str_from_eure() {
2273        use alloc::borrow::Cow;
2274
2275        let doc = eure!({ name = "hello" });
2276        let root_id = doc.get_root_id();
2277        let rec = doc.parse_record(root_id).unwrap();
2278        let value: Cow<'static, str> = rec.parse_field("name").unwrap();
2279        assert_eq!(value, Cow::<str>::Owned("hello".to_string()));
2280    }
2281}