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}