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