Skip to main content

eure_document/
parse.rs

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