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 union;
8
9pub use object_key::ParseObjectKey;
10pub use record::{ExtParser, RecordParser};
11pub use union::UnionParser;
12
13use alloc::format;
14use num_bigint::BigInt;
15
16use crate::{
17    document::node::NodeArray, identifier::IdentifierError, prelude_internal::*, value::ValueKind,
18};
19
20/// Trait for parsing Rust types from Eure documents.
21///
22/// Types implementing this trait can be constructed from [`EureDocument`].
23/// Used for type-safe extraction of structures from documents during conversion.
24///
25/// # Lifetime Parameter
26///
27/// The `'doc` lifetime ties the parsed output to the document's lifetime,
28/// allowing zero-copy parsing for reference types like `&'doc str`.
29///
30/// # Examples
31///
32/// ```ignore
33/// // Reference type - borrows from document
34/// impl<'doc> ParseDocument<'doc> for &'doc str { ... }
35///
36/// // Owned type - no lifetime dependency
37/// impl ParseDocument<'_> for String { ... }
38/// ```
39pub trait ParseDocument<'doc>: Sized {
40    /// Parse a value of this type from an Eure document at the given node.
41    fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError>;
42}
43
44fn handle_unexpected_node_value(node_value: &NodeValue) -> ParseErrorKind {
45    match node_value {
46        NodeValue::Hole => ParseErrorKind::UnexpectedUninitialized,
47        value => value
48            .value_kind()
49            .map(|actual| ParseErrorKind::TypeMismatch {
50                expected: ValueKind::Text,
51                actual,
52            })
53            .unwrap_or_else(|| ParseErrorKind::UnexpectedUninitialized),
54    }
55}
56
57#[derive(Debug, thiserror::Error, Clone, PartialEq)]
58#[error("parse error: {kind}")]
59pub struct ParseError {
60    pub node_id: NodeId,
61    pub kind: ParseErrorKind,
62}
63
64/// Error type for parsing failures.
65#[derive(Debug, thiserror::Error, Clone, PartialEq)]
66pub enum ParseErrorKind {
67    /// Unexpected uninitialized value.
68    #[error("unexpected uninitialized value")]
69    UnexpectedUninitialized,
70
71    /// Type mismatch between expected and actual value.
72    #[error("type mismatch: expected {expected}, got {actual}")]
73    TypeMismatch {
74        expected: ValueKind,
75        actual: ValueKind,
76    },
77
78    /// Required field is missing.
79    #[error("missing field: {0}")]
80    MissingField(String),
81
82    /// Required extension is missing.
83    #[error("missing extension: ${0}")]
84    MissingExtension(String),
85
86    /// Unknown variant in a union type.
87    #[error("unknown variant: {0}")]
88    UnknownVariant(String),
89
90    /// Value is out of valid range.
91    #[error("value out of range: {0}")]
92    OutOfRange(String),
93
94    /// Invalid string pattern.
95    #[error("invalid pattern: expected {pattern}, got {value}")]
96    InvalidPattern { pattern: String, value: String },
97
98    /// Nested parse error with path context.
99    #[error("at {path}: {source}")]
100    Nested {
101        path: String,
102        #[source]
103        source: Box<ParseErrorKind>,
104    },
105
106    /// Invalid identifier.
107    #[error("invalid identifier: {0}")]
108    InvalidIdentifier(#[from] IdentifierError),
109
110    /// Unexpected tuple length.
111    #[error("unexpected tuple length: expected {expected}, got {actual}")]
112    UnexpectedTupleLength { expected: usize, actual: usize },
113
114    /// Unknown field in record.
115    #[error("unknown field: {0}")]
116    UnknownField(String),
117
118    /// Invalid key type in record (expected string).
119    #[error("invalid key type in record: expected string key, got {0:?}")]
120    InvalidKeyType(crate::value::ObjectKey),
121
122    /// No variant matched in union type.
123    #[error("no matching variant")]
124    NoMatchingVariant,
125
126    /// Multiple variants matched with no priority to resolve.
127    #[error("ambiguous union: {0:?}")]
128    AmbiguousUnion(Vec<String>),
129
130    /// Literal value mismatch.
131    #[error("literal value mismatch: expected {expected:?}, got {actual:?}]")]
132    LiteralMismatch {
133        expected: Box<Value>,
134        actual: Box<Value>,
135    },
136}
137
138impl ParseErrorKind {
139    /// Wrap this error with path context.
140    pub fn at(self, path: impl Into<String>) -> Self {
141        ParseErrorKind::Nested {
142            path: path.into(),
143            source: Box::new(self),
144        }
145    }
146}
147
148impl<'doc> EureDocument {
149    pub fn parse<T: ParseDocument<'doc>>(&'doc self, node_id: NodeId) -> Result<T, ParseError> {
150        T::parse(self, node_id)
151    }
152}
153
154impl<'doc> ParseDocument<'doc> for &'doc str {
155    fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
156        match &doc.node(node_id).content {
157            NodeValue::Primitive(PrimitiveValue::Text(text)) => Ok(text.as_str()),
158            value => Err(ParseError {
159                node_id,
160                kind: handle_unexpected_node_value(value),
161            }),
162        }
163    }
164}
165
166impl ParseDocument<'_> for String {
167    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
168        doc.parse::<&str>(node_id).map(String::from)
169    }
170}
171
172impl ParseDocument<'_> for bool {
173    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
174        match &doc.node(node_id).content {
175            NodeValue::Primitive(PrimitiveValue::Bool(b)) => Ok(*b),
176            value => Err(ParseError {
177                node_id,
178                kind: handle_unexpected_node_value(value),
179            }),
180        }
181    }
182}
183
184impl ParseDocument<'_> for BigInt {
185    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
186        match &doc.node(node_id).content {
187            NodeValue::Primitive(PrimitiveValue::Integer(i)) => Ok(i.clone()),
188            value => Err(ParseError {
189                node_id,
190                kind: handle_unexpected_node_value(value),
191            }),
192        }
193    }
194}
195
196impl ParseDocument<'_> for f32 {
197    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
198        match &doc.node(node_id).content {
199            NodeValue::Primitive(PrimitiveValue::F32(f)) => Ok(*f),
200            value => Err(ParseError {
201                node_id,
202                kind: handle_unexpected_node_value(value),
203            }),
204        }
205    }
206}
207
208impl ParseDocument<'_> for f64 {
209    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
210        match &doc.node(node_id).content {
211            // Accept both F32 (with conversion) and F64 if we add it later
212            NodeValue::Primitive(PrimitiveValue::F32(f)) => Ok(*f as f64),
213            value => Err(ParseError {
214                node_id,
215                kind: handle_unexpected_node_value(value),
216            }),
217        }
218    }
219}
220
221impl ParseDocument<'_> for u32 {
222    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
223        let value: BigInt = doc.parse(node_id)?;
224        u32::try_from(&value).map_err(|_| ParseError {
225            node_id,
226            kind: ParseErrorKind::OutOfRange(format!("value {} out of u32 range", value)),
227        })
228    }
229}
230
231impl ParseDocument<'_> for i32 {
232    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
233        let value: BigInt = doc.parse(node_id)?;
234        i32::try_from(&value).map_err(|_| ParseError {
235            node_id,
236            kind: ParseErrorKind::OutOfRange(format!("value {} out of i32 range", value)),
237        })
238    }
239}
240
241impl ParseDocument<'_> for i64 {
242    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
243        let value: BigInt = doc.parse(node_id)?;
244        i64::try_from(&value).map_err(|_| ParseError {
245            node_id,
246            kind: ParseErrorKind::OutOfRange(format!("value {} out of i64 range", value)),
247        })
248    }
249}
250
251impl ParseDocument<'_> for u64 {
252    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
253        let value: BigInt = doc.parse(node_id)?;
254        u64::try_from(&value).map_err(|_| ParseError {
255            node_id,
256            kind: ParseErrorKind::OutOfRange(format!("value {} out of u64 range", value)),
257        })
258    }
259}
260
261impl ParseDocument<'_> for usize {
262    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
263        let value: BigInt = doc.parse(node_id)?;
264        usize::try_from(&value).map_err(|_| ParseError {
265            node_id,
266            kind: ParseErrorKind::OutOfRange(format!("value {} out of usize range", value)),
267        })
268    }
269}
270
271impl<'doc> ParseDocument<'doc> for &'doc PrimitiveValue {
272    fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
273        match &doc.node(node_id).content {
274            NodeValue::Primitive(primitive) => Ok(primitive),
275            value => Err(ParseError {
276                node_id,
277                kind: handle_unexpected_node_value(value),
278            }),
279        }
280    }
281}
282
283impl ParseDocument<'_> for PrimitiveValue {
284    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
285        doc.parse::<&PrimitiveValue>(node_id).cloned()
286    }
287}
288
289impl ParseDocument<'_> for Identifier {
290    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
291        match &doc.node(node_id).content {
292            NodeValue::Primitive(PrimitiveValue::Text(text)) => Ok(text
293                .content
294                .parse()
295                .map_err(ParseErrorKind::InvalidIdentifier)
296                .map_err(|kind| ParseError { node_id, kind })?),
297            value => Err(ParseError {
298                node_id,
299                kind: handle_unexpected_node_value(value),
300            }),
301        }
302    }
303}
304
305impl<'doc> ParseDocument<'doc> for &'doc NodeArray {
306    fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
307        match &doc.node(node_id).content {
308            NodeValue::Array(array) => Ok(array),
309            value => Err(handle_unexpected_node_value(value)),
310        }
311        .map_err(|kind| ParseError { node_id, kind })
312    }
313}
314
315impl<'doc, T> ParseDocument<'doc> for Vec<T>
316where
317    T: ParseDocument<'doc>,
318{
319    fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
320        match &doc.node(node_id).content {
321            NodeValue::Array(array) => array
322                .iter()
323                .map(|item| T::parse(doc, *item))
324                .collect::<Result<Vec<_>, _>>(),
325            value => Err(ParseError {
326                node_id,
327                kind: handle_unexpected_node_value(value),
328            }),
329        }
330    }
331}
332
333macro_rules! parse_tuple {
334    ($n:expr, $($var:ident),*) => {
335        impl<'doc, $($var: ParseDocument<'doc>),*> ParseDocument<'doc> for ($($var),*,) {
336            fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
337                let tuple = match &doc.node(node_id).content {
338                    NodeValue::Tuple(tuple) => tuple,
339                    value => return Err(ParseError { node_id, kind: handle_unexpected_node_value(value) }),
340                };
341                if tuple.len() != $n {
342                    return Err(ParseError { node_id, kind: ParseErrorKind::UnexpectedTupleLength { expected: $n, actual: tuple.len() } });
343                }
344                let mut iter = tuple.iter();
345                Ok(($($var::parse(doc, *iter.next().unwrap())?),*,))
346            }
347        }
348    }
349}
350
351parse_tuple!(1, A);
352parse_tuple!(2, A, B);
353parse_tuple!(3, A, B, C);
354parse_tuple!(4, A, B, C, D);
355parse_tuple!(5, A, B, C, D, E);
356parse_tuple!(6, A, B, C, D, E, F);
357parse_tuple!(7, A, B, C, D, E, F, G);
358parse_tuple!(8, A, B, C, D, E, F, G, H);
359parse_tuple!(9, A, B, C, D, E, F, G, H, I);
360parse_tuple!(10, A, B, C, D, E, F, G, H, I, J);
361parse_tuple!(11, A, B, C, D, E, F, G, H, I, J, K);
362parse_tuple!(12, A, B, C, D, E, F, G, H, I, J, K, L);
363parse_tuple!(13, A, B, C, D, E, F, G, H, I, J, K, L, M);
364parse_tuple!(14, A, B, C, D, E, F, G, H, I, J, K, L, M, N);
365parse_tuple!(15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
366parse_tuple!(16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
367
368impl<'doc, K, T> ParseDocument<'doc> for Map<K, T>
369where
370    K: ParseObjectKey<'doc>,
371    T: ParseDocument<'doc>,
372{
373    fn parse(doc: &'doc EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
374        let map = match &doc.node(node_id).content {
375            NodeValue::Map(map) => map,
376            value => {
377                return Err(ParseError {
378                    node_id,
379                    kind: handle_unexpected_node_value(value),
380                });
381            }
382        };
383        map.iter()
384            .map(|(key, value)| {
385                Ok((
386                    K::from_object_key(key).map_err(|kind| ParseError { node_id, kind })?,
387                    T::parse(doc, *value)?,
388                ))
389            })
390            .collect::<Result<Map<_, _>, _>>()
391    }
392}
393
394impl ParseDocument<'_> for crate::data_model::VariantRepr {
395    fn parse(doc: &EureDocument, node_id: NodeId) -> Result<Self, ParseError> {
396        use crate::data_model::VariantRepr;
397
398        // Check if it's a simple string value
399        if let Ok(value) = doc.parse::<&str>(node_id) {
400            return match value {
401                "external" => Ok(VariantRepr::External),
402                "untagged" => Ok(VariantRepr::Untagged),
403                _ => Err(ParseError {
404                    node_id,
405                    kind: ParseErrorKind::UnknownVariant(value.to_string()),
406                }),
407            };
408        }
409
410        // Otherwise, it should be a record with tag/content fields
411        let mut rec = doc.parse_record(node_id)?;
412
413        let tag = rec.field_optional::<String>("tag")?;
414        let content = rec.field_optional::<String>("content")?;
415
416        rec.allow_unknown_fields()?;
417
418        match (tag, content) {
419            (Some(tag), Some(content)) => Ok(VariantRepr::Adjacent { tag, content }),
420            (Some(tag), None) => Ok(VariantRepr::Internal { tag }),
421            (None, None) => Ok(VariantRepr::External),
422            (None, Some(_)) => Err(ParseError {
423                node_id,
424                kind: ParseErrorKind::MissingField(
425                    "tag (required when content is present)".to_string(),
426                ),
427            }),
428        }
429    }
430}
431
432pub trait DocumentParser<'doc> {
433    type Output: 'doc;
434    fn parse(self, doc: &'doc EureDocument, node_id: NodeId) -> Result<Self::Output, ParseError>;
435}
436
437impl<'doc, T: 'doc, F> DocumentParser<'doc> for F
438where
439    F: FnOnce(&'doc EureDocument, NodeId) -> Result<T, ParseError>,
440{
441    type Output = T;
442    fn parse(self, doc: &'doc EureDocument, node_id: NodeId) -> Result<Self::Output, ParseError> {
443        self(doc, node_id)
444    }
445}
446
447pub struct LiteralParser<T>(T);
448
449impl<'doc, T> DocumentParser<'doc> for LiteralParser<T>
450where
451    T: 'doc + ParseDocument<'doc> + PartialEq + Into<Value>,
452{
453    type Output = T;
454    fn parse(self, doc: &'doc EureDocument, node_id: NodeId) -> Result<Self::Output, ParseError> {
455        let value: T = doc.parse(node_id)?;
456        if value == self.0 {
457            Ok(value)
458        } else {
459            Err(ParseError {
460                node_id,
461                kind: ParseErrorKind::LiteralMismatch {
462                    expected: Box::new(self.0.into()),
463                    actual: Box::new(value.into()),
464                },
465            })
466        }
467    }
468}
469
470pub struct MappedParser<T, F> {
471    parser: T,
472    mapper: F,
473}
474
475impl<'doc, T, O, F> DocumentParser<'doc> for MappedParser<T, F>
476where
477    T: DocumentParser<'doc>,
478    F: Fn(T::Output) -> Result<O, ParseError>,
479    O: 'doc,
480{
481    type Output = O;
482    fn parse(self, doc: &'doc EureDocument, node_id: NodeId) -> Result<Self::Output, ParseError> {
483        let value = self.parser.parse(doc, node_id)?;
484        (self.mapper)(value)
485    }
486}
487
488pub trait DocumentParserExt<'doc>: DocumentParser<'doc> + Sized {
489    fn map<O, F>(self, mapper: F) -> MappedParser<Self, F>
490    where
491        F: Fn(Self::Output) -> Result<O, ParseError>,
492        O: 'doc,
493    {
494        MappedParser {
495            parser: self,
496            mapper,
497        }
498    }
499}