Skip to main content

eure_document/parse/
record.rs

1//! RecordParser for parsing record types from Eure documents.
2
3use crate::parse::DocumentParser;
4use crate::prelude_internal::*;
5
6use super::{FromEure, ParseContext, ParseError, ParseErrorKind, ParserScope, UnionTagMode};
7
8/// Helper for parsing record (map with string keys) from Eure documents.
9///
10/// Tracks accessed fields for unknown field checking.
11///
12/// # Flatten Context
13///
14/// When `flatten_ctx` is `Some`, this parser is part of a flattened chain:
15/// - Field accesses are recorded in the shared `FlattenContext`
16/// - `deny_unknown_fields()` is a no-op (root parser validates)
17///
18/// When `flatten_ctx` is `None`, this is a root parser:
19/// - Field accesses are recorded in local `accessed` set
20/// - `deny_unknown_fields()` actually validates
21///
22/// # Example
23///
24/// ```ignore
25/// impl<'doc> FromEure<'doc> for User {
26///     fn parse(ctx: &ParseContext<'doc>) -> Result<Self, ParseError> {
27///         let mut rec = ctx.parse_record()?;
28///         let name = rec.field::<String>("name")?;
29///         let age = rec.field_optional::<u32>("age")?;
30///         rec.deny_unknown_fields()?;
31///         Ok(User { name, age })
32///     }
33/// }
34/// ```
35#[must_use]
36pub struct RecordParser<'doc> {
37    map: &'doc NodeMap,
38    /// Union tag mode inherited from context.
39    union_tag_mode: UnionTagMode,
40    /// The parse context (holds doc, node_id, accessed, flatten_ctx).
41    ctx: ParseContext<'doc>,
42}
43
44impl<'doc> RecordParser<'doc> {
45    /// Create a new RecordParser for the given context.
46    pub(crate) fn new(ctx: &ParseContext<'doc>) -> Result<Self, ParseError> {
47        // Error if called in Extension scope - this is a user mistake
48        // (using #[eure(flatten_ext)] with a record-parsing type)
49        if let Some(fc) = ctx.flatten_ctx()
50            && fc.scope() == ParserScope::Extension
51        {
52            return Err(ParseError {
53                node_id: ctx.node_id(),
54                kind: ParseErrorKind::RecordInExtensionScope,
55            });
56        }
57
58        let node = ctx.node();
59        match &node.content {
60            NodeValue::Map(map) => Ok(Self {
61                map,
62                union_tag_mode: ctx.union_tag_mode(),
63                ctx: ctx.clone(),
64            }),
65            NodeValue::Hole(_) => Err(ParseError {
66                node_id: ctx.node_id(),
67                kind: ParseErrorKind::UnexpectedHole,
68            }),
69            _ => Err(ctx.unexpected_kind(crate::value::ValueKind::Map)),
70        }
71    }
72
73    /// Create a new RecordParser from document and node ID directly.
74    pub(crate) fn from_doc_and_node(
75        doc: &'doc EureDocument,
76        node_id: NodeId,
77    ) -> Result<Self, ParseError> {
78        let ctx = ParseContext::new(doc, node_id);
79        Self::new(&ctx)
80    }
81
82    /// Mark a field as accessed.
83    fn mark_accessed(&self, name: &str) {
84        self.ctx.accessed().add_field(name);
85    }
86
87    /// Get the node ID being parsed.
88    pub fn node_id(&self) -> NodeId {
89        self.ctx.node_id()
90    }
91
92    /// Get a required field.
93    ///
94    /// Returns `ParseErrorKind::MissingField` if the field is not present or is excluded.
95    pub fn parse_field<T>(&self, name: &str) -> Result<T, T::Error>
96    where
97        T: FromEure<'doc>,
98        T::Error: From<ParseError>,
99    {
100        self.parse_field_with(name, T::parse)
101    }
102
103    pub fn parse_field_with<T>(&self, name: &str, mut parser: T) -> Result<T::Output, T::Error>
104    where
105        T: DocumentParser<'doc>,
106        T::Error: From<ParseError>,
107    {
108        self.mark_accessed(name);
109        let field_node_id = self
110            .map
111            .get(&ObjectKey::String(name.to_string()))
112            .ok_or_else(|| ParseError {
113                node_id: self.ctx.node_id(),
114                kind: ParseErrorKind::MissingField(name.to_string()),
115            })?;
116        let ctx =
117            ParseContext::with_union_tag_mode(self.ctx.doc(), *field_node_id, self.union_tag_mode);
118        parser.parse(&ctx)
119    }
120
121    pub fn parse_field_optional<T>(&self, name: &str) -> Result<Option<T>, T::Error>
122    where
123        T: FromEure<'doc>,
124        T::Error: From<ParseError>,
125    {
126        self.parse_field_optional_with(name, T::parse)
127    }
128
129    /// Get an optional field.
130    ///
131    /// Returns `Ok(None)` if the field is not present.
132    pub fn parse_field_optional_with<T>(
133        &self,
134        name: &str,
135        mut parser: T,
136    ) -> Result<Option<T::Output>, T::Error>
137    where
138        T: DocumentParser<'doc>,
139        T::Error: From<ParseError>,
140    {
141        self.mark_accessed(name);
142        match self.map.get(&ObjectKey::String(name.to_string())) {
143            Some(field_node_id) => {
144                let ctx = ParseContext::with_union_tag_mode(
145                    self.ctx.doc(),
146                    *field_node_id,
147                    self.union_tag_mode,
148                );
149                Ok(Some(parser.parse(&ctx)?))
150            }
151            None => Ok(None),
152        }
153    }
154
155    /// Get the parse context for a field without parsing it.
156    ///
157    /// Use this when you need access to the field's NodeId or want to defer parsing.
158    /// Returns `ParseErrorKind::MissingField` if the field is not present.
159    pub fn field(&self, name: &str) -> Result<ParseContext<'doc>, ParseError> {
160        self.mark_accessed(name);
161        let field_node_id = self
162            .map
163            .get(&ObjectKey::String(name.to_string()))
164            .ok_or_else(|| ParseError {
165                node_id: self.ctx.node_id(),
166                kind: ParseErrorKind::MissingField(name.to_string()),
167            })?;
168        Ok(ParseContext::with_union_tag_mode(
169            self.ctx.doc(),
170            *field_node_id,
171            self.union_tag_mode,
172        ))
173    }
174
175    /// Get the parse context for an optional field without parsing it.
176    ///
177    /// Use this when you need access to the field's NodeId or want to defer parsing.
178    /// Returns `None` if the field is not present.
179    pub fn field_optional(&self, name: &str) -> Option<ParseContext<'doc>> {
180        self.mark_accessed(name);
181        self.map
182            .get(&ObjectKey::String(name.to_string()))
183            .map(|node_id| {
184                ParseContext::with_union_tag_mode(self.ctx.doc(), *node_id, self.union_tag_mode)
185            })
186    }
187
188    /// Get a field as a nested record parser.
189    ///
190    /// Returns `ParseErrorKind::MissingField` if the field is not present.
191    pub fn field_record(&self, name: &str) -> Result<RecordParser<'doc>, ParseError> {
192        self.mark_accessed(name);
193        let field_node_id = self
194            .map
195            .get(&ObjectKey::String(name.to_string()))
196            .ok_or_else(|| ParseError {
197                node_id: self.ctx.node_id(),
198                kind: ParseErrorKind::MissingField(name.to_string()),
199            })?;
200        let ctx =
201            ParseContext::with_union_tag_mode(self.ctx.doc(), *field_node_id, self.union_tag_mode);
202        RecordParser::new(&ctx)
203    }
204
205    /// Get an optional field as a nested record parser.
206    ///
207    /// Returns `Ok(None)` if the field is not present.
208    pub fn field_record_optional(
209        &self,
210        name: &str,
211    ) -> Result<Option<RecordParser<'doc>>, ParseError> {
212        self.mark_accessed(name);
213        match self.map.get(&ObjectKey::String(name.to_string())) {
214            Some(field_node_id) => {
215                let ctx = ParseContext::with_union_tag_mode(
216                    self.ctx.doc(),
217                    *field_node_id,
218                    self.union_tag_mode,
219                );
220                Ok(Some(RecordParser::new(&ctx)?))
221            }
222            None => Ok(None),
223        }
224    }
225
226    /// Finish parsing with Deny policy (error if unknown fields exist).
227    ///
228    /// This also errors if the map contains non-string keys, as records
229    /// should only have string-keyed fields.
230    ///
231    /// **Flatten behavior**: If this parser has a flatten_ctx (i.e., is a child
232    /// in a flatten chain), this is a no-op. Only root parsers validate.
233    pub fn deny_unknown_fields(self) -> Result<(), ParseError> {
234        // If child (has flatten_ctx with Record scope), no-op - parent will validate
235        if let Some(fc) = self.ctx.flatten_ctx()
236            && fc.scope() == ParserScope::Record
237        {
238            return Ok(());
239        }
240
241        // Root parser - validate using accessed set
242        let accessed = self.ctx.accessed();
243        for (key, _) in self.map.iter() {
244            match key {
245                ObjectKey::String(name) => {
246                    if !accessed.has_field(name.as_str()) {
247                        return Err(ParseError {
248                            node_id: self.ctx.node_id(),
249                            kind: ParseErrorKind::UnknownField(name.clone()),
250                        });
251                    }
252                }
253                // Non-string keys are invalid in records
254                other => {
255                    return Err(ParseError {
256                        node_id: self.ctx.node_id(),
257                        kind: ParseErrorKind::InvalidKeyType(other.clone()),
258                    });
259                }
260            }
261        }
262        Ok(())
263    }
264
265    /// Finish parsing with Allow policy (allow unknown string fields).
266    ///
267    /// This still errors if the map contains non-string keys, as records
268    /// should only have string-keyed fields.
269    pub fn allow_unknown_fields(self) -> Result<(), ParseError> {
270        // Check for non-string keys (invalid in records)
271        for (key, _) in self.map.iter() {
272            if !matches!(key, ObjectKey::String(_)) {
273                return Err(ParseError {
274                    node_id: self.ctx.node_id(),
275                    kind: ParseErrorKind::InvalidKeyType(key.clone()),
276                });
277            }
278        }
279        Ok(())
280    }
281
282    /// Get an iterator over unknown fields (for Schema policy or custom handling).
283    ///
284    /// Returns `Result` items:
285    /// - `Ok((field_name, context))` for unaccessed string-keyed fields
286    /// - `Err((invalid_key, context))` for non-string keys, allowing caller to handle directly
287    ///
288    /// Note: In flattened contexts, this still returns fields - use `deny_unknown_fields()`
289    /// if you want the automatic no-op behavior for child parsers.
290    pub fn unknown_fields(
291        &self,
292    ) -> impl Iterator<
293        Item = Result<(&'doc str, ParseContext<'doc>), (&'doc ObjectKey, ParseContext<'doc>)>,
294    > + '_ {
295        let doc = self.ctx.doc();
296        let mode = self.union_tag_mode;
297        // Clone the accessed set for filtering - we need the current state
298        let accessed = self.ctx.accessed().clone();
299        self.map
300            .iter()
301            .filter_map(move |(key, &node_id)| match key {
302                ObjectKey::String(name) => {
303                    if !accessed.has_field(name.as_str()) {
304                        Some(Ok((
305                            name.as_str(),
306                            ParseContext::with_union_tag_mode(doc, node_id, mode),
307                        )))
308                    } else {
309                        None // Accessed, skip
310                    }
311                }
312                other => Some(Err((
313                    other,
314                    ParseContext::with_union_tag_mode(doc, node_id, mode),
315                ))),
316            })
317    }
318
319    /// Get an iterator over all unknown entries including non-string keys.
320    ///
321    /// Returns (ObjectKey, context) pairs for:
322    /// - String keys that haven't been accessed
323    /// - All non-string keys (e.g., integer keys)
324    ///
325    /// This is useful for flatten map validation where both string and integer
326    /// keys need to be validated against the map's key schema.
327    pub fn unknown_entries(
328        &self,
329    ) -> impl Iterator<Item = (&'doc ObjectKey, ParseContext<'doc>)> + '_ {
330        let doc = self.ctx.doc();
331        let mode = self.union_tag_mode;
332        // Clone the accessed set for filtering - we need the current state
333        let accessed = self.ctx.accessed().clone();
334        self.map.iter().filter_map(move |(key, &node_id)| {
335            match key {
336                ObjectKey::String(name) => {
337                    // For string keys, only return if not accessed
338                    if !accessed.has_field(name.as_str()) {
339                        Some((key, ParseContext::with_union_tag_mode(doc, node_id, mode)))
340                    } else {
341                        None
342                    }
343                }
344                // Non-string keys are always returned (they can't be "accessed" via field methods)
345                _ => Some((key, ParseContext::with_union_tag_mode(doc, node_id, mode))),
346            }
347        })
348    }
349
350    /// Create a flatten context for child parsers in Record scope.
351    ///
352    /// This creates a FlattenContext initialized with the current accessed fields,
353    /// and returns a ParseContext that children can use. Children created from this
354    /// context will:
355    /// - Add their accessed fields to the shared FlattenContext
356    /// - Have deny_unknown_fields() be a no-op
357    ///
358    /// The root parser should call deny_unknown_fields() after all children are done.
359    pub fn flatten(&self) -> ParseContext<'doc> {
360        self.ctx.flatten()
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367    use crate::value::PrimitiveValue;
368
369    fn create_test_doc() -> EureDocument {
370        let mut doc = EureDocument::new();
371        let root_id = doc.get_root_id();
372
373        // Add fields: name = "Alice", age = 30
374        let name_id = doc
375            .add_map_child(ObjectKey::String("name".to_string()), root_id)
376            .unwrap()
377            .node_id;
378        doc.node_mut(name_id).content = NodeValue::Primitive(PrimitiveValue::Text(
379            crate::text::Text::plaintext("Alice".to_string()),
380        ));
381
382        let age_id = doc
383            .add_map_child(ObjectKey::String("age".to_string()), root_id)
384            .unwrap()
385            .node_id;
386        doc.node_mut(age_id).content = NodeValue::Primitive(PrimitiveValue::Integer(30.into()));
387
388        doc
389    }
390
391    #[test]
392    fn test_record_field() {
393        let doc = create_test_doc();
394        let rec = doc.parse_record(doc.get_root_id()).unwrap();
395
396        let name: String = rec.parse_field("name").unwrap();
397        assert_eq!(name, "Alice");
398    }
399
400    #[test]
401    fn test_record_field_missing() {
402        let doc = create_test_doc();
403        let rec = doc.parse_record(doc.get_root_id()).unwrap();
404
405        let result: Result<String, _> = rec.parse_field("nonexistent");
406        assert!(matches!(
407            result.unwrap_err().kind,
408            ParseErrorKind::MissingField(_)
409        ));
410    }
411
412    #[test]
413    fn test_record_field_optional() {
414        let doc = create_test_doc();
415        let rec = doc.parse_record(doc.get_root_id()).unwrap();
416
417        let name: Option<String> = rec.parse_field_optional("name").unwrap();
418        assert_eq!(name, Some("Alice".to_string()));
419
420        let missing: Option<String> = rec.parse_field_optional("nonexistent").unwrap();
421        assert_eq!(missing, None);
422    }
423
424    #[test]
425    fn test_record_deny_unknown_fields() {
426        let doc = create_test_doc();
427        let rec = doc.parse_record(doc.get_root_id()).unwrap();
428
429        let _name: String = rec.parse_field("name").unwrap();
430        // Didn't access "age", so deny should fail
431        let result = rec.deny_unknown_fields();
432        assert!(matches!(
433            result.unwrap_err().kind,
434            ParseErrorKind::UnknownField(_)
435        ));
436    }
437
438    #[test]
439    fn test_record_deny_unknown_fields_all_accessed() {
440        let doc = create_test_doc();
441        let rec = doc.parse_record(doc.get_root_id()).unwrap();
442
443        let _name: String = rec.parse_field("name").unwrap();
444        let _age: num_bigint::BigInt = rec.parse_field("age").unwrap();
445        // Accessed all fields, should succeed
446        rec.deny_unknown_fields().unwrap();
447    }
448
449    #[test]
450    fn test_record_allow_unknown_fields() {
451        let doc = create_test_doc();
452        let rec = doc.parse_record(doc.get_root_id()).unwrap();
453
454        let _name: String = rec.parse_field("name").unwrap();
455        // Didn't access "age", but allow should succeed
456        rec.allow_unknown_fields().unwrap();
457    }
458
459    #[test]
460    fn test_record_unknown_fields_iterator() {
461        let doc = create_test_doc();
462        let rec = doc.parse_record(doc.get_root_id()).unwrap();
463
464        let _name: String = rec.parse_field("name").unwrap();
465        // "age" should be in unknown fields
466        let unknown: Vec<_> = rec.unknown_fields().collect::<Result<Vec<_>, _>>().unwrap();
467        assert_eq!(unknown.len(), 1);
468        assert_eq!(unknown[0].0, "age");
469    }
470
471    #[test]
472    fn test_record_with_non_string_keys_deny_should_error() {
473        // deny_unknown_fields() errors on non-string keys
474        use crate::eure;
475
476        let doc = eure!({ 0 = "value" });
477        let rec = doc.parse_record(doc.get_root_id()).unwrap();
478
479        let result = rec.deny_unknown_fields();
480        assert!(
481            matches!(result.unwrap_err().kind, ParseErrorKind::InvalidKeyType(_)),
482            "deny_unknown_fields() should error on non-string keys"
483        );
484    }
485
486    #[test]
487    fn test_record_with_non_string_keys_unknown_fields_iterator() {
488        // unknown_fields() returns Err for non-string keys
489        use crate::eure;
490
491        let doc = eure!({ 0 = "value" });
492        let rec = doc.parse_record(doc.get_root_id()).unwrap();
493
494        // unknown_fields() should return an error for the non-string key
495        let result: Result<Vec<_>, _> = rec.unknown_fields().collect();
496        let (invalid_key, _ctx) = result.unwrap_err();
497        assert!(
498            matches!(invalid_key, ObjectKey::Number(_)),
499            "unknown_fields() should return the invalid key directly"
500        );
501    }
502
503    #[test]
504    fn test_unknown_fields_err_contains_correct_context() {
505        // Verify that the Err case contains a context pointing to the value node
506        use crate::eure;
507
508        let doc = eure!({ 42 = "test" });
509        let rec = doc.parse_record(doc.get_root_id()).unwrap();
510
511        let result: Result<Vec<_>, _> = rec.unknown_fields().collect();
512        let (key, ctx) = result.unwrap_err();
513
514        // Verify the key is the numeric key
515        assert_eq!(key, &ObjectKey::Number(42.into()));
516        // Verify the context can be used to parse the value
517        let value: String = ctx.parse().unwrap();
518        assert_eq!(value, "test");
519    }
520
521    #[test]
522    fn test_unknown_fields_mixed_string_and_non_string_keys() {
523        // Test that string keys return Ok, non-string keys return Err
524        use crate::eure;
525
526        let doc = eure!({
527            name = "Alice"
528            123 = "numeric"
529        });
530        let rec = doc.parse_record(doc.get_root_id()).unwrap();
531
532        // Collect results to inspect both Ok and Err
533        let mut ok_fields = Vec::new();
534        let mut err_keys = Vec::new();
535        for result in rec.unknown_fields() {
536            match result {
537                Ok((name, _ctx)) => ok_fields.push(name.to_string()),
538                Err((key, _ctx)) => err_keys.push(key.clone()),
539            }
540        }
541
542        // Should have one Ok (string key) and one Err (numeric key)
543        assert_eq!(ok_fields, vec!["name"]);
544        assert_eq!(err_keys, vec![ObjectKey::Number(123.into())]);
545    }
546
547    #[test]
548    fn test_unknown_fields_accessed_fields_filtered_non_string_always_returned() {
549        // Accessed string keys are filtered out, but non-string keys always return Err
550        use crate::eure;
551
552        let doc = eure!({
553            name = "Alice"
554            age = 30
555            999 = "numeric"
556        });
557        let rec = doc.parse_record(doc.get_root_id()).unwrap();
558
559        // Access the "name" field
560        let _name: String = rec.parse_field("name").unwrap();
561
562        // Check unknown_fields - "name" filtered, "age" is Ok, numeric is Err
563        let mut ok_fields = Vec::new();
564        let mut err_keys = Vec::new();
565        for result in rec.unknown_fields() {
566            match result {
567                Ok((name, _ctx)) => ok_fields.push(name.to_string()),
568                Err((key, _ctx)) => err_keys.push(key.clone()),
569            }
570        }
571
572        assert_eq!(ok_fields, vec!["age"]);
573        assert_eq!(err_keys, vec![ObjectKey::Number(999.into())]);
574    }
575
576    #[test]
577    fn test_unknown_fields_multiple_non_string_keys() {
578        // Test handling of multiple non-string keys
579        use crate::eure;
580
581        let doc = eure!({
582            1 = "one"
583            2 = "two"
584        });
585        let rec = doc.parse_record(doc.get_root_id()).unwrap();
586
587        // Collect all errors
588        let mut err_keys: Vec<ObjectKey> = Vec::new();
589        for result in rec.unknown_fields() {
590            if let Err((key, _ctx)) = result {
591                err_keys.push(key.clone());
592            }
593        }
594
595        // Should have both numeric keys as errors
596        assert_eq!(err_keys.len(), 2);
597        assert!(err_keys.contains(&ObjectKey::Number(1.into())));
598        assert!(err_keys.contains(&ObjectKey::Number(2.into())));
599    }
600
601    #[test]
602    fn test_parse_ext() {
603        let mut doc = EureDocument::new();
604        let root_id = doc.get_root_id();
605
606        // Add extension: $ext-type.optional = true
607        let ext_id = doc
608            .add_extension("optional".parse().unwrap(), root_id)
609            .unwrap()
610            .node_id;
611        doc.node_mut(ext_id).content = NodeValue::Primitive(PrimitiveValue::Bool(true));
612
613        let ctx = doc.parse_extension_context(root_id);
614        let optional: bool = ctx.parse_ext("optional").unwrap();
615        assert!(optional);
616    }
617
618    #[test]
619    fn test_parse_ext_optional_missing() {
620        let doc = EureDocument::new();
621        let root_id = doc.get_root_id();
622
623        let ctx = doc.parse_extension_context(root_id);
624        let optional: Option<bool> = ctx.parse_ext_optional("optional").unwrap();
625        assert_eq!(optional, None);
626    }
627
628    /// Helper struct for testing three-level nested flatten pattern.
629    /// Parses: { a, b, c, d, e } with three-level flatten.
630    #[derive(Debug, PartialEq)]
631    struct ThreeLevelFlatten {
632        a: i32,
633        b: i32,
634        c: i32,
635        d: i32,
636        e: i32,
637    }
638
639    impl<'doc> FromEure<'doc> for ThreeLevelFlatten {
640        type Error = ParseError;
641
642        fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
643            // Level 1
644            let rec1 = ctx.parse_record()?;
645            let a = rec1.parse_field("a")?;
646            let ctx2 = rec1.flatten();
647
648            // Level 2
649            let rec2 = ctx2.parse_record()?;
650            let b = rec2.parse_field("b")?;
651            let c = rec2.parse_field("c")?;
652            let ctx3 = rec2.flatten();
653
654            // Level 3
655            let rec3 = ctx3.parse_record()?;
656            let d = rec3.parse_field("d")?;
657            let e = rec3.parse_field("e")?;
658            rec3.deny_unknown_fields()?;
659
660            // Level 2 deny (no-op since child)
661            rec2.deny_unknown_fields()?;
662
663            // Level 1 deny (root - validates all)
664            rec1.deny_unknown_fields()?;
665
666            Ok(Self { a, b, c, d, e })
667        }
668    }
669
670    #[test]
671    fn test_nested_flatten_preserves_consumed_fields() {
672        // Document: { a = 1, b = 2, c = 3, d = 4, e = 5 }
673        //
674        // Parsing structure:
675        // Level 1: parse_record(), field(a), flatten() →
676        //   Level 2: field(b), field(c), flatten() →
677        //     Level 3: field(d), field(e), deny_unknown_fields()
678        //   Level 2: deny_unknown_fields()
679        // Level 1: deny_unknown_fields()
680        //
681        // Expected: All deny_unknown_fields() should succeed
682        use crate::eure;
683
684        let doc = eure!({ a = 1, b = 2, c = 3, d = 4, e = 5 });
685        let result: ThreeLevelFlatten = doc.parse(doc.get_root_id()).unwrap();
686
687        assert_eq!(
688            result,
689            ThreeLevelFlatten {
690                a: 1,
691                b: 2,
692                c: 3,
693                d: 4,
694                e: 5
695            }
696        );
697    }
698
699    #[test]
700    fn test_nested_flatten_catches_unaccessed_field() {
701        // Document: { a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 }
702        //
703        // Parsing structure (NOT accessing f):
704        // Level 1: field(a), flatten() →
705        //   Level 2: field(b), field(c), flatten() →
706        //     Level 3: field(d), field(e), deny_unknown_fields()
707        //   Level 2: deny_unknown_fields()
708        // Level 1: deny_unknown_fields() <- Should FAIL because f is not accessed
709        //
710        // Expected: Level 1's deny_unknown_fields() should fail with UnknownField("f")
711        use crate::eure;
712
713        let doc = eure!({ a = 1, b = 2, c = 3, d = 4, e = 5, f = 6 });
714        let result: Result<ThreeLevelFlatten, _> = doc.parse(doc.get_root_id());
715
716        assert_eq!(
717            result.unwrap_err().kind,
718            ParseErrorKind::UnknownField("f".to_string())
719        );
720    }
721
722    #[test]
723    fn test_flatten_union_reverts_accessed_fields_on_failure() {
724        use crate::eure;
725
726        let doc = eure!({
727            a = 1
728            b = 2
729            c = 3
730            d = 4
731        });
732
733        // Define enum with two variants
734        #[derive(Debug, PartialEq)]
735        enum TestOption {
736            A { a: i32, c: i32, e: i32 },
737            B { a: i32, b: i32 },
738        }
739
740        impl<'doc> FromEure<'doc> for TestOption {
741            type Error = ParseError;
742
743            fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
744                ctx.parse_union(VariantRepr::default())?
745                    .variant("A", |ctx: &ParseContext<'_>| {
746                        let rec = ctx.parse_record()?;
747                        let a = rec.parse_field("a")?;
748                        let c = rec.parse_field("c")?;
749                        let e = rec.parse_field("e")?; // Will fail - field doesn't exist
750                        rec.deny_unknown_fields()?;
751                        Ok(TestOption::A { a, c, e })
752                    })
753                    .variant("B", |ctx: &ParseContext<'_>| {
754                        let rec = ctx.parse_record()?;
755                        let a = rec.parse_field("a")?;
756                        let b = rec.parse_field("b")?;
757                        rec.deny_unknown_fields()?;
758                        Ok(TestOption::B { a, b })
759                    })
760                    .parse()
761            }
762        }
763
764        // Parse with flatten
765        let root_id = doc.get_root_id();
766        let root_ctx = ParseContext::new(&doc, root_id);
767        let record = root_ctx.parse_record().unwrap();
768
769        // Parse union - should succeed with VariantB
770        let option = record.flatten().parse::<TestOption>().unwrap();
771        assert_eq!(option, TestOption::B { a: 1, b: 2 });
772
773        // Access field d
774        let d: i32 = record.parse_field("d").unwrap();
775        assert_eq!(d, 4);
776
777        // BUG: This should FAIL because field 'c' was never accessed by VariantB
778        // (the successful variant), but it SUCCEEDS because VariantA tried 'c'
779        // before failing
780        let result = record.deny_unknown_fields();
781
782        assert_eq!(
783            result.unwrap_err(),
784            ParseError {
785                node_id: root_id,
786                kind: ParseErrorKind::UnknownField("c".to_string()),
787            }
788        );
789    }
790
791    // =========================================================================
792    // Tests for alternating flatten/flatten_ext scope changes
793    // =========================================================================
794
795    /// Tests alternating flatten -> flatten_ext -> flatten -> flatten_ext pattern
796    #[derive(Debug, PartialEq)]
797    struct AlternatingFlattenTest {
798        normal1: i32,
799        ext_normal2: i32,
800        ext_normal3: i32,
801        ext_content: String,
802    }
803
804    impl<'doc> FromEure<'doc> for AlternatingFlattenTest {
805        type Error = ParseError;
806
807        fn parse(ctx: &ParseContext<'doc>) -> Result<Self, Self::Error> {
808            // Level 1: Record scope (flatten)
809            let rec1 = ctx.parse_record()?;
810            let normal1 = rec1.parse_field("normal")?;
811            let ctx1 = rec1.flatten();
812
813            // Level 2: Extension scope (flatten_ext)
814            let ctx2 = ctx1.flatten_ext();
815            assert_eq!(ctx2.parser_scope(), Some(ParserScope::Extension));
816            let ext1_ctx = ctx2.ext("item")?;
817
818            // Level 3: Record scope (flatten) - inside extension value
819            let rec2 = ext1_ctx.parse_record()?;
820            let ext_normal2 = rec2.parse_field("normal")?;
821            let ctx3 = rec2.flatten();
822            assert_eq!(ctx3.parser_scope(), Some(ParserScope::Record));
823
824            // Level 4: Extension scope (flatten_ext)
825            let ctx4 = ctx3.flatten_ext();
826            assert_eq!(ctx4.parser_scope(), Some(ParserScope::Extension));
827            let ext2_ctx = ctx4.ext("item")?;
828
829            // Level 5: Record scope (flatten)
830            let rec3 = ext2_ctx.parse_record()?;
831            let ext_normal3 = rec3.parse_field("normal")?;
832            let ctx5 = rec3.flatten();
833
834            // Level 6: Extension scope (flatten_ext)
835            let ctx6 = ctx5.flatten_ext();
836            let ext3_ctx = ctx6.ext("item")?;
837
838            // Innermost: Record
839            let rec4 = ext3_ctx.parse_record()?;
840            let ext_content = rec4.parse_field("content")?;
841
842            // Deny at all levels
843            rec4.deny_unknown_fields()?;
844            ctx6.deny_unknown_extensions()?;
845            rec3.deny_unknown_fields()?;
846            ctx4.deny_unknown_extensions()?;
847            rec2.deny_unknown_fields()?;
848            ctx2.deny_unknown_extensions()?;
849            rec1.deny_unknown_fields()?;
850            ctx1.deny_unknown_extensions()?;
851
852            Ok(Self {
853                normal1,
854                ext_normal2,
855                ext_normal3,
856                ext_content,
857            })
858        }
859    }
860
861    #[test]
862    fn test_alternating_flatten_flatten_ext() {
863        use crate::eure;
864
865        let doc = eure!({
866            normal = 1
867            %item {
868                normal = 2
869                %item {
870                    normal = 3
871                    %item {
872                        content = "Hello"
873                    }
874                }
875            }
876        });
877
878        let result: AlternatingFlattenTest = doc.parse(doc.get_root_id()).unwrap();
879        assert_eq!(
880            result,
881            AlternatingFlattenTest {
882                normal1: 1,
883                ext_normal2: 2,
884                ext_normal3: 3,
885                ext_content: "Hello".to_string(),
886            }
887        );
888    }
889
890    #[test]
891    fn test_alternating_flatten_scope_changes() {
892        use crate::eure;
893
894        let doc = eure!({});
895        let root_id = doc.get_root_id();
896        let ctx = ParseContext::new(&doc, root_id);
897
898        // flatten -> Record
899        let ctx1 = ctx.flatten();
900        assert_eq!(ctx1.parser_scope(), Some(ParserScope::Record));
901
902        // flatten_ext -> Extension
903        let ctx2 = ctx1.flatten_ext();
904        assert_eq!(ctx2.parser_scope(), Some(ParserScope::Extension));
905
906        // flatten -> Record (THIS IS THE BUG - currently stays Extension)
907        let ctx3 = ctx2.flatten();
908        assert_eq!(ctx3.parser_scope(), Some(ParserScope::Record));
909
910        // flatten_ext -> Extension
911        let ctx4 = ctx3.flatten_ext();
912        assert_eq!(ctx4.parser_scope(), Some(ParserScope::Extension));
913    }
914}