Skip to main content

facet_format/
event.rs

1extern crate alloc;
2
3use alloc::borrow::Cow;
4use core::fmt;
5
6/// Location hint for a serialized field.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum FieldLocationHint {
9    /// Key/value entry (JSON/YAML/TOML/etc).
10    #[default]
11    KeyValue,
12}
13
14/// Field key for a serialized field.
15///
16/// For self-describing formats, this represents either:
17/// - A named key (struct field or map key with string name)
18/// - A tagged key (e.g., `@string` in Styx for type pattern keys)
19/// - A unit key (map key with no name, e.g., `@` in Styx representing `None` in `Option<String>` keys)
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct FieldKey<'de> {
22    /// Field name.
23    ///
24    /// `None` represents a unit key (e.g., `@` in Styx) which can be deserialized as
25    /// `None` for `Option<String>` map keys. For struct field deserialization, `None`
26    /// is an error since struct fields always have names.
27    pub name: Option<Cow<'de, str>>,
28    /// Location hint.
29    pub location: FieldLocationHint,
30    /// Documentation comments attached to this field (for formats that support them).
31    ///
32    /// Used by formats like Styx where `/// comment` before a field is preserved.
33    /// When deserializing into a `metadata_container` type like `Documented<T>`,
34    /// these doc lines are used to populate the metadata.
35    pub doc: Option<Vec<Cow<'de, str>>>,
36    /// Tag name for tagged keys (for formats that support them).
37    ///
38    /// Used by formats like Styx where `@string` in key position represents a type pattern.
39    /// When deserializing into a `metadata_container` type with `#[facet(metadata = "tag")]`,
40    /// this tag name is used to populate the metadata.
41    ///
42    /// - `None`: not a tagged key (bare identifier like `name`)
43    /// - `Some("")`: unit tag (`@` alone)
44    /// - `Some("string")`: named tag (`@string`)
45    pub tag: Option<Cow<'de, str>>,
46}
47
48impl<'de> FieldKey<'de> {
49    /// Create a new field key with a name.
50    pub fn new(name: impl Into<Cow<'de, str>>, location: FieldLocationHint) -> Self {
51        Self {
52            name: Some(name.into()),
53            location,
54            doc: None,
55            tag: None,
56        }
57    }
58
59    /// Create a new field key with a name and documentation.
60    pub fn with_doc(
61        name: impl Into<Cow<'de, str>>,
62        location: FieldLocationHint,
63        doc: Vec<Cow<'de, str>>,
64    ) -> Self {
65        Self {
66            name: Some(name.into()),
67            location,
68            doc: if doc.is_empty() { None } else { Some(doc) },
69            tag: None,
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(
77        tag: impl Into<Cow<'de, str>>,
78        location: FieldLocationHint,
79    ) -> Self {
80        Self {
81            name: None,
82            location,
83            doc: None,
84            tag: Some(tag.into()),
85        }
86    }
87
88    /// Create a tagged field key with documentation.
89    pub fn tagged_with_doc(
90        tag: impl Into<Cow<'de, str>>,
91        location: FieldLocationHint,
92        doc: Vec<Cow<'de, str>>,
93    ) -> Self {
94        Self {
95            name: None,
96            location,
97            doc: if doc.is_empty() { None } else { Some(doc) },
98            tag: Some(tag.into()),
99        }
100    }
101
102    /// Create a unit field key (no name).
103    ///
104    /// Used for formats like Styx where `@` represents a unit key in maps.
105    /// This is equivalent to `tagged("")` - a tag with an empty name.
106    pub fn unit(location: FieldLocationHint) -> Self {
107        Self {
108            name: None,
109            location,
110            doc: None,
111            tag: Some(Cow::Borrowed("")),
112        }
113    }
114
115    /// Create a unit field key with documentation.
116    pub fn unit_with_doc(location: FieldLocationHint, doc: Vec<Cow<'de, str>>) -> Self {
117        Self {
118            name: None,
119            location,
120            doc: if doc.is_empty() { None } else { Some(doc) },
121            tag: Some(Cow::Borrowed("")),
122        }
123    }
124}
125
126/// The kind of container being parsed.
127///
128/// This distinguishes between format-specific container types to enable
129/// better error messages and type checking.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum ContainerKind {
132    /// Object: struct-like with key-value pairs.
133    /// Type mismatches (e.g., object where array expected) should produce errors.
134    Object,
135    /// Array: sequence-like.
136    /// Type mismatches (e.g., array where object expected) should produce errors.
137    Array,
138}
139
140impl ContainerKind {
141    /// Human-readable name for error messages.
142    pub const fn name(self) -> &'static str {
143        match self {
144            ContainerKind::Object => "object",
145            ContainerKind::Array => "array",
146        }
147    }
148}
149
150/// Value classification hint for evidence gathering.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum ValueTypeHint {
153    /// Null-like values.
154    Null,
155    /// Boolean.
156    Bool,
157    /// Numeric primitive.
158    Number,
159    /// Text string.
160    String,
161    /// Raw bytes (e.g., base64 segments).
162    Bytes,
163    /// Sequence (array/list/tuple).
164    Sequence,
165    /// Map/struct/object.
166    Map,
167}
168
169/// Scalar data extracted from the wire format.
170#[derive(Debug, Clone, PartialEq)]
171pub enum ScalarValue<'de> {
172    /// Unit type (Rust's `()`).
173    Unit,
174    /// Null literal.
175    Null,
176    /// Boolean literal.
177    Bool(bool),
178    /// Character literal.
179    Char(char),
180    /// Signed integer literal (fits in i64).
181    I64(i64),
182    /// Unsigned integer literal (fits in u64).
183    U64(u64),
184    /// Signed 128-bit integer literal.
185    I128(i128),
186    /// Unsigned 128-bit integer literal.
187    U128(u128),
188    /// Floating-point literal.
189    F64(f64),
190    /// UTF-8 string literal.
191    Str(Cow<'de, str>),
192    /// Binary literal.
193    Bytes(Cow<'de, [u8]>),
194}
195
196/// Event emitted by a format parser while streaming through input.
197#[derive(Clone, PartialEq)]
198pub enum ParseEvent<'de> {
199    /// Beginning of a struct/object/node.
200    StructStart(ContainerKind),
201    /// End of a struct/object/node.
202    StructEnd,
203    /// Encountered a field key (for self-describing formats like JSON/YAML).
204    FieldKey(FieldKey<'de>),
205    /// Next field value in struct field order (for non-self-describing formats like postcard).
206    ///
207    /// The driver tracks the current field index and uses the schema to determine
208    /// which field this value belongs to. This allows formats without field names
209    /// in the wire format to still support Tier-0 deserialization.
210    OrderedField,
211    /// Beginning of a sequence/array/tuple.
212    SequenceStart(ContainerKind),
213    /// End of a sequence/array/tuple.
214    SequenceEnd,
215    /// Scalar literal.
216    Scalar(ScalarValue<'de>),
217    /// Tagged value from a self-describing format with native tagged union syntax.
218    ///
219    /// This is used by formats like Styx that have explicit tag syntax (e.g., `@tag(value)`).
220    /// Most formats (JSON, TOML, etc.) don't need this - they represent enums as
221    /// `{"variant_name": value}` which goes through the struct/field path instead.
222    ///
223    /// `None` represents a unit tag (bare `@` in Styx) with no name.
224    VariantTag(Option<&'de str>),
225}
226
227impl<'de> fmt::Debug for ParseEvent<'de> {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        match self {
230            ParseEvent::StructStart(kind) => f.debug_tuple("StructStart").field(kind).finish(),
231            ParseEvent::StructEnd => f.write_str("StructEnd"),
232            ParseEvent::FieldKey(key) => f.debug_tuple("FieldKey").field(key).finish(),
233            ParseEvent::OrderedField => f.write_str("OrderedField"),
234            ParseEvent::SequenceStart(kind) => f.debug_tuple("SequenceStart").field(kind).finish(),
235            ParseEvent::SequenceEnd => f.write_str("SequenceEnd"),
236            ParseEvent::Scalar(value) => f.debug_tuple("Scalar").field(value).finish(),
237            ParseEvent::VariantTag(tag) => f.debug_tuple("VariantTag").field(tag).finish(),
238        }
239    }
240}