1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
use std::collections::BTreeSet;
use facet_core::{Characteristic, Def};
use facet_reflect::{FieldCategory, FieldInfo, Partial, VariantSelection};
use facet_solver::PathSegment;
use super::entry::MetaSource;
use super::path_navigator::PathNavigator;
use crate::{
DeserializeError, DeserializeErrorKind, FieldKey, FormatDeserializer, ParseEventKind,
ScalarValue, SpanGuard,
};
impl<'parser, 'input, const BORROW: bool> FormatDeserializer<'parser, 'input, BORROW> {
/// Deserialize a struct with flattened fields using facet-solver.
///
/// This uses the solver's Schema/Resolution to handle arbitrarily nested
/// flatten structures by looking up the full path for each field.
/// It also handles flattened enums by using probing to collect keys first,
/// then using the Solver to disambiguate between resolutions.
pub(crate) fn deserialize_struct_with_flatten(
&mut self,
mut wip: Partial<'input, BORROW>,
) -> Result<Partial<'input, BORROW>, DeserializeError> {
use facet_solver::{Schema, Solver};
trace!(
"deserialize_struct_with_flatten: starting shape={}",
wip.shape()
);
let deny_unknown_fields = wip.struct_plan().unwrap().deny_unknown_fields;
let struct_type_has_default = wip.shape().is(Characteristic::Default);
// Peek at the next event first to handle EOF and null gracefully
let maybe_event = self.peek_event_opt()?;
// Handle EOF (empty input / comment-only files): use Default if available
if maybe_event.is_none() {
if struct_type_has_default {
let _guard = SpanGuard::new(self.last_span);
wip = wip.set_default()?;
return Ok(wip);
}
return Err(self.mk_err(
&wip,
DeserializeErrorKind::UnexpectedEof { expected: "value" },
));
}
// Handle Scalar(Null): use Default if available
if let Some(ref event) = maybe_event
&& matches!(event.kind, ParseEventKind::Scalar(ScalarValue::Null))
&& struct_type_has_default
{
let _ = self.expect_event("null")?;
let _guard = SpanGuard::new(self.last_span);
wip = wip.set_default()?;
return Ok(wip);
}
// Build the schema for this type - this recursively expands all flatten fields
let schema = Schema::build_auto(wip.shape()).map_err(|e| {
self.mk_err(
&wip,
DeserializeErrorKind::Solver {
message: format!("failed to build schema: {e}").into(),
},
)
})?;
// Check if we have multiple resolutions (i.e., flattened enums)
let resolutions = schema.resolutions();
if resolutions.is_empty() {
return Err(self.mk_err(
&wip,
DeserializeErrorKind::Solver {
message: "schema has no resolutions".into(),
},
));
}
// ========== PASS 1: Probe to collect all field keys ==========
let evidence = self.collect_evidence()?;
let mut solver = Solver::new(&schema);
// First pass: process tag hints BEFORE field-based narrowing.
// For internally-tagged enums we must apply it first
for ev in &evidence {
if let Some(ScalarValue::Str(variant_name)) = &ev.scalar_value {
solver.hint_variant_for_tag(&ev.name, variant_name);
}
}
// Second pass: feed keys to solver to narrow down resolutions.
for ev in &evidence {
solver.see_key(ev.name.clone());
}
// Get the resolved configuration
let config_handle = solver.finish().map_err(|e| {
self.mk_err(
&wip,
DeserializeErrorKind::Solver {
message: format!("solver failed: {e}").into(),
},
)
})?;
let resolution = config_handle.resolution();
// ========== PASS 2: Parse the struct with resolved paths ==========
// Expect StructStart
let event = self.expect_event("value")?;
if !matches!(event.kind, ParseEventKind::StructStart(_)) {
return Err(self.mk_err(
&wip,
DeserializeErrorKind::UnexpectedToken {
expected: "struct start",
got: event.kind_name().into(),
},
));
}
// Enter deferred mode for flatten handling (if not already in deferred mode)
let already_deferred = wip.is_deferred();
if !already_deferred {
let _guard = SpanGuard::new(self.last_span);
wip = wip.begin_deferred()?;
}
// Track which fields have been set (by serialized name)
let mut fields_set: BTreeSet<&'static str> = BTreeSet::new();
// Create navigator for path management
let mut nav = PathNavigator::new(wip, self.last_span);
let variant_selections = resolution.variant_selections();
loop {
let event = self.expect_event("value")?;
nav.set_span(self.last_span);
match event.kind {
ParseEventKind::StructEnd => break,
ParseEventKind::FieldKey(key) => {
// Unit keys don't make sense for struct fields
let key_name = match key.name() {
Some(name) => name.as_ref(),
None => {
self.skip_value()?;
continue;
}
};
// Look up field in the resolution
if let Some(field_info) = resolution.field_by_name(key_name) {
let nav_result = nav.navigate_to(&field_info.path, variant_selections)?;
// Handle trailing variant (externally-tagged or internally-tagged enum)
if let Some(variant_name) = nav_result.trailing_variant {
// Check if this is an internally-tagged enum tag field
let is_internally_tagged_tag = field_info
.value_shape
.get_tag_attr()
.is_some_and(|tag| tag == field_info.serialized_name);
if is_internally_tagged_tag {
// Read and validate the tag value
let tag_event =
self.expect_event("internally-tagged enum tag value")?;
nav.set_span(self.last_span);
let actual_tag = match &tag_event.kind {
ParseEventKind::Scalar(ScalarValue::Str(s)) => s.as_ref(),
_ => {
return Err(self.mk_err(
nav.wip(),
DeserializeErrorKind::UnexpectedToken {
expected: "string tag value",
got: tag_event.kind_name().into(),
},
));
}
};
if actual_tag != variant_name {
return Err(self.mk_err(
nav.wip(),
DeserializeErrorKind::InvalidValue {
message: format!(
"expected tag value '{}', got '{}'",
variant_name, actual_tag
)
.into(),
},
));
}
let _guard = SpanGuard::new(self.last_span);
let wip = nav.take_wip();
nav.return_wip(wip.select_variant_named(variant_name)?);
// For internally-tagged enums, keep the enum segment open so that
// subsequent fields of the variant can be deserialized into it.
// Mark it with has_selected_variant=true so it won't be closed
// by close_to() when navigating to sibling fields - only close_all()
// at the end will close it. This handles out-of-order fields.
// See https://github.com/facet-rs/facet/issues/2007
// See https://github.com/facet-rs/facet/issues/2010
nav.keep_final_open(&nav_result);
fields_set.insert(field_info.serialized_name);
continue;
}
// For externally-tagged enums: select variant and deserialize content
let _guard = SpanGuard::new(self.last_span);
let wip = nav.take_wip();
let wip = wip.select_variant_named(variant_name)?;
let wip = self.deserialize_variant_struct_fields(wip)?;
nav.return_wip(wip);
} else {
// Regular field: deserialize into it
let wip = nav.take_wip();
let wip = self.deserialize_into(wip, MetaSource::FromEvents)?;
nav.return_wip(wip);
}
nav.set_span(self.last_span);
nav.close_final(nav_result.final_is_option)?;
fields_set.insert(field_info.serialized_name);
continue;
}
// Check if we have a catch-all map for unknown fields
if let Some(catch_all_info) = resolution.catch_all_map(FieldCategory::Flat) {
self.insert_into_catch_all_map(
&mut nav,
catch_all_info,
&key,
&mut fields_set,
variant_selections,
)?;
continue;
}
if deny_unknown_fields {
return Err(self.mk_err(
nav.wip(),
DeserializeErrorKind::UnknownField {
field: key_name.to_owned().into(),
suggestion: None,
},
));
} else {
self.skip_value()?;
}
}
other => {
return Err(self.mk_err(
nav.wip(),
DeserializeErrorKind::UnexpectedToken {
expected: "field key or struct end",
got: other.kind_name().into(),
},
));
}
}
}
// Close any remaining open segments
nav.set_span(self.last_span);
nav.close_all()?;
let mut wip = nav.into_wip();
// Initialize catch-all map/value if it was never touched (no unknown fields)
if let Some(catch_all_info) = resolution.catch_all_map(FieldCategory::Flat)
&& !fields_set.contains(catch_all_info.serialized_name)
{
wip = self.initialize_empty_catch_all(wip, catch_all_info)?;
}
// Finish deferred mode (only if we started it)
if !already_deferred {
let _guard = SpanGuard::new(self.last_span);
wip = wip.finish_deferred()?;
}
Ok(wip)
}
/// Helper for inserting a key-value pair into a catch-all map field.
fn insert_into_catch_all_map(
&mut self,
nav: &mut PathNavigator<'input, BORROW>,
catch_all_info: &FieldInfo,
key: &FieldKey<'input>,
fields_set: &mut BTreeSet<&'static str>,
variant_selections: &[VariantSelection],
) -> Result<(), DeserializeError> {
// Navigate to the catch-all map field
let nav_result = nav.navigate_to(&catch_all_info.path, variant_selections)?;
// Initialize the map if this is our first time
let map_field_name = catch_all_info.serialized_name;
let is_dynamic_value = matches!(nav.wip().shape().def, Def::DynamicValue(_));
if !fields_set.contains(map_field_name) {
let _guard = SpanGuard::new(self.last_span);
let wip = nav.take_wip();
nav.return_wip(wip.init_map()?);
fields_set.insert(map_field_name);
}
// Insert the key-value pair
let _guard = SpanGuard::new(self.last_span);
if is_dynamic_value {
// Dynamic values use begin_object_entry which takes just the key name
let key_name = key.name().map(|n| n.as_ref()).unwrap_or("");
let wip = nav.take_wip();
let wip = wip
.begin_object_entry(key_name)?
.with(|w| self.deserialize_into(w, MetaSource::FromEvents))?
.end()?;
nav.return_wip(wip);
} else {
// Map uses begin_key() + set value + end() + begin_value() + deserialize + end()
// Use deserialize_map_key to properly handle metadata containers (like Spanned<String>)
let wip = nav.take_wip();
let wip = wip.begin_key()?;
let wip = self.deserialize_map_key(wip, key.name().cloned(), key.meta())?;
let wip = wip.end()?;
let wip = wip
.begin_value()?
.with(|w| self.deserialize_into(w, MetaSource::FromEvents))?
.end()?;
nav.return_wip(wip);
}
// Keep the map field open for potential future entries
nav.keep_final_open(&nav_result);
Ok(())
}
/// Helper for initializing an empty catch-all field (no parser calls).
fn initialize_empty_catch_all(
&self,
mut wip: Partial<'input, BORROW>,
catch_all_info: &FieldInfo,
) -> Result<Partial<'input, BORROW>, DeserializeError> {
let _guard = SpanGuard::new(self.last_span);
let segments = catch_all_info.path.segments();
// Extract field names from the path
let field_segments: Vec<&str> = segments
.iter()
.filter_map(|s| match s {
PathSegment::Field(name) => Some(*name),
PathSegment::Variant(_, _) => None,
})
.collect();
// Track opened segments so we can close them
let mut opened_options: Vec<bool> = Vec::new();
// Navigate to the catch-all field
for &segment in &field_segments {
wip = wip.begin_field(segment)?;
let is_option = matches!(wip.shape().def, Def::Option(_));
if is_option {
wip = wip.begin_some()?;
}
opened_options.push(is_option);
}
// Initialize as empty based on the field's type
match &wip.shape().def {
Def::Map(_) | Def::DynamicValue(_) => {
wip = wip.init_map()?;
}
_ => {
if wip.shape().is(Characteristic::Default) {
wip = wip.set_default()?;
}
}
}
// Close segments in reverse order
for is_option in opened_options.into_iter().rev() {
if is_option {
wip = wip.end()?;
}
wip = wip.end()?;
}
Ok(wip)
}
}