Skip to main content

facet_format/
event.rs

1extern crate alloc;
2
3use alloc::borrow::Cow;
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use core::fmt;
7use facet_reflect::Span;
8
9/// Location hint for a serialized field.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum FieldLocationHint {
12    /// Key/value entry (JSON/YAML/TOML/etc).
13    #[default]
14    KeyValue,
15}
16
17/// Field key for a serialized field.
18///
19/// This enum is optimized for the common case (simple named keys) while still
20/// supporting rich metadata for formats like Styx.
21///
22/// - `Name`: Simple string key (24 bytes) - used by JSON, YAML, TOML, etc.
23/// - `Full`: Boxed full key with metadata (8 bytes) - used by Styx for doc/tag support.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum FieldKey<'de> {
26    /// Simple named key (common case for JSON/YAML/TOML).
27    Name(Cow<'de, str>),
28    /// Full key with metadata support (for Styx).
29    Full(Box<FullFieldKey<'de>>),
30}
31
32/// Full field key with metadata support.
33///
34/// Used by formats like Styx that support documentation comments and type tags on keys.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct FullFieldKey<'de> {
37    /// Field name.
38    ///
39    /// `None` represents a unit key (e.g., `@` in Styx) which can be deserialized as
40    /// `None` for `Option<String>` map keys. For struct field deserialization, `None`
41    /// is an error since struct fields always have names.
42    pub name: Option<Cow<'de, str>>,
43    /// Location hint.
44    pub location: FieldLocationHint,
45    /// Metadata (doc comments, type tags) attached to this field.
46    pub meta: ValueMeta<'de>,
47}
48
49impl<'de> FieldKey<'de> {
50    /// Create a new field key with a name (common case).
51    #[inline]
52    pub fn new(name: impl Into<Cow<'de, str>>, _location: FieldLocationHint) -> Self {
53        FieldKey::Name(name.into())
54    }
55
56    /// Create a new field key with a name and documentation.
57    pub fn with_doc(
58        name: impl Into<Cow<'de, str>>,
59        location: FieldLocationHint,
60        doc: Vec<Cow<'de, str>>,
61    ) -> Self {
62        if doc.is_empty() {
63            FieldKey::Name(name.into())
64        } else {
65            FieldKey::Full(Box::new(FullFieldKey {
66                name: Some(name.into()),
67                location,
68                meta: ValueMeta::builder().doc(doc).build(),
69            }))
70        }
71    }
72
73    /// Create a tagged field key (e.g., `@string` in Styx).
74    ///
75    /// Used for type pattern keys where the key is a tag rather than a bare identifier.
76    pub fn tagged(tag: impl Into<Cow<'de, str>>, location: FieldLocationHint) -> Self {
77        FieldKey::Full(Box::new(FullFieldKey {
78            name: None,
79            location,
80            meta: ValueMeta::builder().tag(tag.into()).build(),
81        }))
82    }
83
84    /// Create a tagged field key with documentation.
85    pub fn tagged_with_doc(
86        tag: impl Into<Cow<'de, str>>,
87        location: FieldLocationHint,
88        doc: Vec<Cow<'de, str>>,
89    ) -> Self {
90        FieldKey::Full(Box::new(FullFieldKey {
91            name: None,
92            location,
93            meta: ValueMeta::builder()
94                .tag(tag.into())
95                .maybe_doc(Some(doc))
96                .build(),
97        }))
98    }
99
100    /// Create a tagged field key with a name (e.g., `@string"mykey"` in Styx).
101    ///
102    /// Used for type pattern keys that also have an associated name/payload.
103    pub fn tagged_with_name(
104        tag: impl Into<Cow<'de, str>>,
105        name: impl Into<Cow<'de, str>>,
106        location: FieldLocationHint,
107    ) -> Self {
108        FieldKey::Full(Box::new(FullFieldKey {
109            name: Some(name.into()),
110            location,
111            meta: ValueMeta::builder().tag(tag.into()).build(),
112        }))
113    }
114
115    /// Create a tagged field key with a name and documentation.
116    pub fn tagged_with_name_and_doc(
117        tag: impl Into<Cow<'de, str>>,
118        name: impl Into<Cow<'de, str>>,
119        location: FieldLocationHint,
120        doc: Vec<Cow<'de, str>>,
121    ) -> Self {
122        FieldKey::Full(Box::new(FullFieldKey {
123            name: Some(name.into()),
124            location,
125            meta: ValueMeta::builder()
126                .tag(tag.into())
127                .maybe_doc(Some(doc))
128                .build(),
129        }))
130    }
131
132    /// Create a unit field key (no name).
133    ///
134    /// Used for formats like Styx where `@` represents a unit key in maps.
135    /// This is equivalent to `tagged("")` - a tag with an empty name.
136    pub fn unit(location: FieldLocationHint) -> Self {
137        FieldKey::Full(Box::new(FullFieldKey {
138            name: None,
139            location,
140            meta: ValueMeta::builder().tag(Cow::Borrowed("")).build(),
141        }))
142    }
143
144    /// Create a unit field key with documentation.
145    pub fn unit_with_doc(location: FieldLocationHint, doc: Vec<Cow<'de, str>>) -> Self {
146        FieldKey::Full(Box::new(FullFieldKey {
147            name: None,
148            location,
149            meta: ValueMeta::builder()
150                .tag(Cow::Borrowed(""))
151                .maybe_doc(Some(doc))
152                .build(),
153        }))
154    }
155
156    /// Get the field name, if any.
157    #[inline]
158    pub fn name(&self) -> Option<&Cow<'de, str>> {
159        match self {
160            FieldKey::Name(name) => Some(name),
161            FieldKey::Full(full) => full.name.as_ref(),
162        }
163    }
164
165    /// Get the documentation comments, if any.
166    #[inline]
167    pub fn doc(&self) -> Option<&[Cow<'de, str>]> {
168        match self {
169            FieldKey::Name(_) => None,
170            FieldKey::Full(full) => full.meta.doc(),
171        }
172    }
173
174    /// Get the tag name, if any.
175    #[inline]
176    pub fn tag(&self) -> Option<&Cow<'de, str>> {
177        match self {
178            FieldKey::Name(_) => None,
179            FieldKey::Full(full) => full.meta.tag(),
180        }
181    }
182
183    /// Get the metadata, if any.
184    #[inline]
185    pub fn meta(&self) -> Option<&ValueMeta<'de>> {
186        match self {
187            FieldKey::Name(_) => None,
188            FieldKey::Full(full) => Some(&full.meta),
189        }
190    }
191
192    /// Get the location hint.
193    #[inline]
194    pub fn location(&self) -> FieldLocationHint {
195        match self {
196            FieldKey::Name(_) => FieldLocationHint::KeyValue,
197            FieldKey::Full(full) => full.location,
198        }
199    }
200}
201
202/// The kind of container being parsed.
203///
204/// This distinguishes between format-specific container types to enable
205/// better error messages and type checking.
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum ContainerKind {
208    /// Object: struct-like with key-value pairs.
209    /// Type mismatches (e.g., object where array expected) should produce errors.
210    Object,
211    /// Array: sequence-like.
212    /// Type mismatches (e.g., array where object expected) should produce errors.
213    Array,
214}
215
216impl ContainerKind {
217    /// Human-readable name for error messages.
218    pub const fn name(self) -> &'static str {
219        match self {
220            ContainerKind::Object => "object",
221            ContainerKind::Array => "array",
222        }
223    }
224}
225
226/// Value classification hint for evidence gathering.
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub enum ValueTypeHint {
229    /// Null-like values.
230    Null,
231    /// Boolean.
232    Bool,
233    /// Numeric primitive.
234    Number,
235    /// Text string.
236    String,
237    /// Raw bytes (e.g., base64 segments).
238    Bytes,
239    /// Sequence (array/list/tuple).
240    Sequence,
241    /// Map/struct/object.
242    Map,
243}
244
245/// Scalar data extracted from the wire format.
246#[derive(Debug, Clone, PartialEq)]
247pub enum ScalarValue<'de> {
248    /// Unit type (Rust's `()`).
249    Unit,
250    /// Null literal.
251    Null,
252    /// Boolean literal.
253    Bool(bool),
254    /// Character literal.
255    Char(char),
256    /// Signed integer literal (fits in i64).
257    I64(i64),
258    /// Unsigned integer literal (fits in u64).
259    U64(u64),
260    /// Signed 128-bit integer literal.
261    I128(i128),
262    /// Unsigned 128-bit integer literal.
263    U128(u128),
264    /// Floating-point literal.
265    F64(f64),
266    /// UTF-8 string literal.
267    Str(Cow<'de, str>),
268    /// Binary literal.
269    Bytes(Cow<'de, [u8]>),
270}
271
272impl<'de> ScalarValue<'de> {
273    /// Convert scalar value to a string representation.
274    ///
275    /// This is a non-generic helper extracted to reduce monomorphization bloat.
276    /// Returns `None` for `Bytes` since that conversion is context-dependent.
277    pub fn to_string_value(&self) -> Option<alloc::string::String> {
278        match self {
279            ScalarValue::Str(s) => Some(s.to_string()),
280            ScalarValue::Bool(b) => Some(b.to_string()),
281            ScalarValue::I64(i) => Some(i.to_string()),
282            ScalarValue::U64(u) => Some(u.to_string()),
283            ScalarValue::I128(i) => Some(i.to_string()),
284            ScalarValue::U128(u) => Some(u.to_string()),
285            ScalarValue::F64(f) => Some(f.to_string()),
286            ScalarValue::Char(c) => Some(c.to_string()),
287            ScalarValue::Null => Some("null".to_string()),
288            ScalarValue::Unit => Some(alloc::string::String::new()),
289            ScalarValue::Bytes(_) => None,
290        }
291    }
292
293    /// Convert scalar value to a display string for error messages.
294    ///
295    /// This is a non-generic helper extracted to reduce monomorphization bloat.
296    pub fn to_display_string(&self) -> alloc::string::String {
297        match self {
298            ScalarValue::Str(s) => s.to_string(),
299            ScalarValue::Bool(b) => alloc::format!("bool({})", b),
300            ScalarValue::I64(i) => alloc::format!("i64({})", i),
301            ScalarValue::U64(u) => alloc::format!("u64({})", u),
302            ScalarValue::I128(i) => alloc::format!("i128({})", i),
303            ScalarValue::U128(u) => alloc::format!("u128({})", u),
304            ScalarValue::F64(f) => alloc::format!("f64({})", f),
305            ScalarValue::Char(c) => alloc::format!("char({})", c),
306            ScalarValue::Bytes(_) => "bytes".to_string(),
307            ScalarValue::Null => "null".to_string(),
308            ScalarValue::Unit => "unit".to_string(),
309        }
310    }
311
312    /// Returns a static string describing the kind of scalar for error messages.
313    #[inline]
314    pub const fn kind_name(&self) -> &'static str {
315        match self {
316            ScalarValue::Unit => "unit",
317            ScalarValue::Null => "null",
318            ScalarValue::Bool(_) => "bool",
319            ScalarValue::Char(_) => "char",
320            ScalarValue::I64(_) => "i64",
321            ScalarValue::U64(_) => "u64",
322            ScalarValue::I128(_) => "i128",
323            ScalarValue::U128(_) => "u128",
324            ScalarValue::F64(_) => "f64",
325            ScalarValue::Str(_) => "string",
326            ScalarValue::Bytes(_) => "bytes",
327        }
328    }
329}
330
331/// Metadata associated with a value being deserialized.
332///
333/// This includes documentation comments and type tags from formats that support them
334/// (like Styx). For formats that don't provide metadata (like JSON), these will be empty/none.
335///
336/// Use [`ValueMeta::builder()`] to construct instances.
337#[derive(Debug, Clone, Default, PartialEq, Eq)]
338#[non_exhaustive]
339pub struct ValueMeta<'a> {
340    doc: Option<Vec<Cow<'a, str>>>,
341    tag: Option<Cow<'a, str>>,
342    span: Option<Span>,
343}
344
345impl<'a> ValueMeta<'a> {
346    /// A const empty `ValueMeta` for use as a default reference.
347    pub const fn empty() -> Self {
348        Self {
349            doc: None,
350            tag: None,
351            span: None,
352        }
353    }
354
355    /// Create a new builder for `ValueMeta`.
356    #[inline]
357    pub fn builder() -> ValueMetaBuilder<'a> {
358        ValueMetaBuilder::default()
359    }
360
361    /// Get the documentation comments, if any.
362    #[inline]
363    pub fn doc(&self) -> Option<&[Cow<'a, str>]> {
364        self.doc.as_deref()
365    }
366
367    /// Get the type tag, if any (e.g., `@string` in Styx).
368    #[inline]
369    pub fn tag(&self) -> Option<&Cow<'a, str>> {
370        self.tag.as_ref()
371    }
372
373    /// Get the span where this value starts (e.g., where a VariantTag was found).
374    #[inline]
375    pub fn span(&self) -> Option<Span> {
376        self.span
377    }
378
379    /// Returns `true` if this metadata has no content.
380    #[inline]
381    pub fn is_empty(&self) -> bool {
382        self.doc.is_none() && self.tag.is_none() && self.span.is_none()
383    }
384}
385
386/// Builder for [`ValueMeta`].
387#[derive(Debug, Clone, Default)]
388pub struct ValueMetaBuilder<'a> {
389    doc: Option<Vec<Cow<'a, str>>>,
390    tag: Option<Cow<'a, str>>,
391    span: Option<Span>,
392}
393
394impl<'a> ValueMetaBuilder<'a> {
395    /// Set the documentation comments.
396    #[inline]
397    pub fn doc(mut self, doc: Vec<Cow<'a, str>>) -> Self {
398        if !doc.is_empty() {
399            self.doc = Some(doc);
400        }
401        self
402    }
403
404    /// Set the documentation comments if present.
405    #[inline]
406    pub fn maybe_doc(mut self, doc: Option<Vec<Cow<'a, str>>>) -> Self {
407        if let Some(d) = doc
408            && !d.is_empty()
409        {
410            self.doc = Some(d);
411        }
412        self
413    }
414
415    /// Set the type tag.
416    #[inline]
417    pub fn tag(mut self, tag: Cow<'a, str>) -> Self {
418        self.tag = Some(tag);
419        self
420    }
421
422    /// Set the type tag if present.
423    #[inline]
424    pub fn maybe_tag(mut self, tag: Option<Cow<'a, str>>) -> Self {
425        if tag.is_some() {
426            self.tag = tag;
427        }
428        self
429    }
430
431    /// Set the span where this value starts.
432    #[inline]
433    pub fn span(mut self, span: Span) -> Self {
434        self.span = Some(span);
435        self
436    }
437
438    /// Build the `ValueMeta`.
439    #[inline]
440    pub fn build(self) -> ValueMeta<'a> {
441        ValueMeta {
442            doc: self.doc,
443            tag: self.tag,
444            span: self.span,
445        }
446    }
447}
448
449/// Event emitted by a format parser while streaming through input.
450#[derive(Clone, PartialEq)]
451pub struct ParseEvent<'de> {
452    /// The kind of event.
453    pub kind: ParseEventKind<'de>,
454    /// Source span of this event in the input.
455    pub span: facet_reflect::Span,
456    /// Optional metadata (doc comments, type tags) attached to this value.
457    ///
458    /// For most formats (JSON, TOML, etc.) this will be `None`. Formats like Styx
459    /// that support documentation comments and type tags on values will populate this.
460    pub meta: Option<ValueMeta<'de>>,
461}
462
463impl<'de> ParseEvent<'de> {
464    /// Create a new event with the given kind and span.
465    #[inline]
466    pub fn new(kind: ParseEventKind<'de>, span: facet_reflect::Span) -> Self {
467        Self {
468            kind,
469            span,
470            meta: None,
471        }
472    }
473
474    /// Attach metadata to this event using a builder.
475    ///
476    /// # Example
477    /// ```ignore
478    /// ParseEvent::new(kind, span).with_meta(|m| m.doc(lines).tag(tag))
479    /// ```
480    #[inline]
481    pub fn with_meta(
482        mut self,
483        f: impl FnOnce(ValueMetaBuilder<'de>) -> ValueMetaBuilder<'de>,
484    ) -> Self {
485        let meta = f(ValueMetaBuilder::default()).build();
486        if !meta.is_empty() {
487            self.meta = Some(meta);
488        }
489        self
490    }
491}
492
493/// The kind of parse event.
494#[derive(Clone, PartialEq)]
495pub enum ParseEventKind<'de> {
496    /// Beginning of a struct/object/node.
497    StructStart(ContainerKind),
498    /// End of a struct/object/node.
499    StructEnd,
500    /// Encountered a field key (for self-describing formats like JSON/YAML).
501    FieldKey(FieldKey<'de>),
502    /// Next field value in struct field order (for non-self-describing formats like postcard).
503    ///
504    /// The driver tracks the current field index and uses the schema to determine
505    /// which field this value belongs to. This allows formats without field names
506    /// in the wire format to still support Tier-0 deserialization.
507    OrderedField,
508    /// Beginning of a sequence/array/tuple.
509    SequenceStart(ContainerKind),
510    /// End of a sequence/array/tuple.
511    SequenceEnd,
512    /// Scalar literal.
513    Scalar(ScalarValue<'de>),
514    /// Tagged value from a self-describing format with native tagged union syntax.
515    ///
516    /// This is used by formats like Styx that have explicit tag syntax (e.g., `@tag(value)`).
517    /// Most formats (JSON, TOML, etc.) don't need this - they represent enums as
518    /// `{"variant_name": value}` which goes through the struct/field path instead.
519    ///
520    /// `None` represents a unit tag (bare `@` in Styx) with no name.
521    VariantTag(Option<&'de str>),
522}
523
524impl<'de> fmt::Debug for ParseEvent<'de> {
525    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526        // Delegate to kind's debug, span is secondary
527        write!(f, "{:?}@{}", self.kind, self.span)
528    }
529}
530
531impl<'de> fmt::Debug for ParseEventKind<'de> {
532    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533        match self {
534            ParseEventKind::StructStart(kind) => f.debug_tuple("StructStart").field(kind).finish(),
535            ParseEventKind::StructEnd => f.write_str("StructEnd"),
536            ParseEventKind::FieldKey(key) => f.debug_tuple("FieldKey").field(key).finish(),
537            ParseEventKind::OrderedField => f.write_str("OrderedField"),
538            ParseEventKind::SequenceStart(kind) => {
539                f.debug_tuple("SequenceStart").field(kind).finish()
540            }
541            ParseEventKind::SequenceEnd => f.write_str("SequenceEnd"),
542            ParseEventKind::Scalar(value) => f.debug_tuple("Scalar").field(value).finish(),
543            ParseEventKind::VariantTag(tag) => f.debug_tuple("VariantTag").field(tag).finish(),
544        }
545    }
546}
547
548impl ParseEvent<'_> {
549    /// Returns a static string describing the kind of event for error messages.
550    #[inline]
551    pub const fn kind_name(&self) -> &'static str {
552        self.kind.kind_name()
553    }
554}
555
556impl ParseEventKind<'_> {
557    /// Returns a static string describing the kind of event for error messages.
558    #[inline]
559    pub const fn kind_name(&self) -> &'static str {
560        match self {
561            ParseEventKind::StructStart(_) => "struct start",
562            ParseEventKind::StructEnd => "struct end",
563            ParseEventKind::FieldKey(_) => "field key",
564            ParseEventKind::OrderedField => "ordered field",
565            ParseEventKind::SequenceStart(_) => "sequence start",
566            ParseEventKind::SequenceEnd => "sequence end",
567            ParseEventKind::Scalar(_) => "scalar",
568            ParseEventKind::VariantTag(_) => "variant tag",
569        }
570    }
571}