• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 20188989810

13 Dec 2025 07:42AM UTC coverage: 57.967% (-0.2%) from 58.211%
20188989810

push

github

fasterthanlime
Add value-based probing for out-of-order tagged enum fields

Extends FieldEvidence with optional scalar_value capture, enabling
internally and adjacently tagged enums to work regardless of field order.

Changes:
- Add scalar_value field to FieldEvidence for capturing tag values during probing
- Update JSON build_probe to capture scalar values instead of skipping
- Update XML build_probe to properly scan remaining events for field evidence
- Rewrite internally/adjacently tagged enum deserialization to probe first
- Add out-of-order tests for both JSON and XML

Fixes cases like {"radius": 5.0, "type": "Circle"} where tag comes last.

Refs: #1271

117 of 205 new or added lines in 4 files covered. (57.07%)

568 existing lines in 4 files now uncovered.

32370 of 55842 relevant lines covered (57.97%)

5679.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.94
/facet-format-xml/src/parser.rs
1
extern crate alloc;
2

3
use alloc::borrow::Cow;
4
use alloc::collections::BTreeMap;
5
use alloc::string::String;
6
use alloc::vec::Vec;
7
use core::fmt;
8

9
use facet_format::{
10
    FieldEvidence, FieldLocationHint, FormatParser, ParseEvent, ProbeStream, ScalarValue,
11
};
12
use quick_xml::Reader;
13
use quick_xml::events::{BytesStart, Event};
14
use std::io::Cursor;
15

16
pub struct XmlParser<'de> {
17
    events: Vec<ParseEvent<'de>>,
18
    idx: usize,
19
    pending_error: Option<XmlError>,
20
}
21

22
impl<'de> XmlParser<'de> {
23
    pub fn new(input: &'de [u8]) -> Self {
66✔
24
        match build_events(input) {
66✔
25
            Ok(events) => Self {
66✔
26
                events,
66✔
27
                idx: 0,
66✔
28
                pending_error: None,
66✔
29
            },
66✔
30
            Err(err) => Self {
×
31
                events: Vec::new(),
×
32
                idx: 0,
×
33
                pending_error: Some(err),
×
34
            },
×
35
        }
36
    }
66✔
37
}
38

39
#[derive(Debug, Clone)]
40
pub enum XmlError {
41
    ParseError(alloc::string::String),
42
    UnexpectedEof,
43
    UnbalancedTags,
44
    InvalidUtf8,
45
    MultipleRoots,
46
}
47

48
impl fmt::Display for XmlError {
49
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
50
        match self {
×
51
            XmlError::ParseError(msg) => write!(f, "XML parse error: {}", msg),
×
52
            XmlError::UnexpectedEof => write!(f, "Unexpected end of XML"),
×
53
            XmlError::UnbalancedTags => write!(f, "Unbalanced XML tags"),
×
54
            XmlError::InvalidUtf8 => write!(f, "Invalid UTF-8 in XML"),
×
55
            XmlError::MultipleRoots => write!(f, "XML document has multiple root elements"),
×
56
        }
57
    }
×
58
}
59

60
impl<'de> FormatParser<'de> for XmlParser<'de> {
61
    type Error = XmlError;
62
    type Probe<'a>
63
        = XmlProbe<'de>
64
    where
65
        Self: 'a;
66

67
    fn next_event(&mut self) -> Result<ParseEvent<'de>, Self::Error> {
408✔
68
        if let Some(err) = &self.pending_error {
408✔
69
            return Err(err.clone());
×
70
        }
408✔
71
        if self.idx >= self.events.len() {
408✔
72
            return Err(XmlError::UnexpectedEof);
×
73
        }
408✔
74
        let event = self.events[self.idx].clone();
408✔
75
        self.idx += 1;
408✔
76
        Ok(event)
408✔
77
    }
408✔
78

79
    fn peek_event(&mut self) -> Result<ParseEvent<'de>, Self::Error> {
55✔
80
        if let Some(err) = &self.pending_error {
55✔
81
            return Err(err.clone());
×
82
        }
55✔
83
        self.events
55✔
84
            .get(self.idx)
55✔
85
            .cloned()
55✔
86
            .ok_or(XmlError::UnexpectedEof)
55✔
87
    }
55✔
88

89
    fn skip_value(&mut self) -> Result<(), Self::Error> {
6✔
90
        let mut depth = 0usize;
6✔
91
        loop {
92
            let event = self.next_event()?;
6✔
93
            match event {
6✔
94
                ParseEvent::StructStart | ParseEvent::SequenceStart => {
×
95
                    depth += 1;
×
96
                }
×
97
                ParseEvent::StructEnd | ParseEvent::SequenceEnd => {
98
                    if depth == 0 {
×
99
                        break;
×
100
                    } else {
×
101
                        depth -= 1;
×
102
                    }
×
103
                }
104
                ParseEvent::Scalar(_) | ParseEvent::VariantTag(_) => {
105
                    if depth == 0 {
6✔
106
                        break;
6✔
107
                    }
×
108
                }
109
                ParseEvent::FieldKey(_, _) => {
×
110
                    // Value will follow; treat as entering one more depth level.
×
111
                    depth += 1;
×
112
                }
×
113
            }
114
        }
115
        Ok(())
6✔
116
    }
6✔
117

118
    fn begin_probe(&mut self) -> Result<Self::Probe<'_>, Self::Error> {
6✔
119
        // Look ahead in the remaining events to build field evidence
120
        let evidence = self.build_probe();
6✔
121
        Ok(XmlProbe { evidence, idx: 0 })
6✔
122
    }
6✔
123
}
124

125
impl<'de> XmlParser<'de> {
126
    /// Build field evidence by looking ahead at remaining events.
127
    fn build_probe(&self) -> Vec<FieldEvidence<'de>> {
6✔
128
        let mut evidence = Vec::new();
6✔
129

130
        // Check if we're about to read a struct
131
        if self.idx >= self.events.len() {
6✔
NEW
132
            return evidence;
×
133
        }
6✔
134

135
        if !matches!(self.events.get(self.idx), Some(ParseEvent::StructStart)) {
6✔
NEW
136
            return evidence;
×
137
        }
6✔
138

139
        // Scan the struct's fields
140
        let mut i = self.idx + 1;
6✔
141
        let mut depth = 0usize;
6✔
142

143
        while i < self.events.len() {
30✔
144
            match &self.events[i] {
30✔
NEW
145
                ParseEvent::StructStart | ParseEvent::SequenceStart => {
×
NEW
146
                    depth += 1;
×
NEW
147
                    i += 1;
×
NEW
148
                }
×
149
                ParseEvent::StructEnd | ParseEvent::SequenceEnd => {
150
                    if depth == 0 {
6✔
151
                        // End of the struct we're probing
152
                        break;
6✔
NEW
153
                    }
×
NEW
154
                    depth -= 1;
×
NEW
155
                    i += 1;
×
156
                }
157
                ParseEvent::FieldKey(name, location) if depth == 0 => {
12✔
158
                    // This is a top-level field in the struct we're probing
159
                    // Look at the next event to see if it's a scalar
160
                    let scalar_value = if let Some(next_event) = self.events.get(i + 1) {
12✔
161
                        match next_event {
12✔
162
                            ParseEvent::Scalar(sv) => Some(sv.clone()),
12✔
NEW
163
                            _ => None,
×
164
                        }
165
                    } else {
NEW
166
                        None
×
167
                    };
168

169
                    if let Some(sv) = scalar_value {
12✔
170
                        evidence.push(FieldEvidence::with_scalar_value(
12✔
171
                            name.clone(),
12✔
172
                            *location,
12✔
173
                            None,
12✔
174
                            sv,
12✔
175
                        ));
12✔
176
                    } else {
12✔
NEW
177
                        evidence.push(FieldEvidence::new(name.clone(), *location, None));
×
NEW
178
                    }
×
179
                    i += 1;
12✔
180
                }
181
                _ => {
12✔
182
                    i += 1;
12✔
183
                }
12✔
184
            }
185
        }
186

187
        evidence
6✔
188
    }
6✔
189
}
190

191
pub struct XmlProbe<'de> {
192
    evidence: Vec<FieldEvidence<'de>>,
193
    idx: usize,
194
}
195

196
impl<'de> ProbeStream<'de> for XmlProbe<'de> {
197
    type Error = XmlError;
198

199
    fn next(&mut self) -> Result<Option<FieldEvidence<'de>>, Self::Error> {
18✔
200
        if self.idx >= self.evidence.len() {
18✔
201
            Ok(None)
6✔
202
        } else {
203
            let ev = self.evidence[self.idx].clone();
12✔
204
            self.idx += 1;
12✔
205
            Ok(Some(ev))
12✔
206
        }
207
    }
18✔
208
}
209

210
#[derive(Debug, Clone)]
211
struct Element {
212
    name: String,
213
    attributes: Vec<(String, String)>,
214
    children: Vec<Element>,
215
    text: String,
216
}
217

218
impl Element {
219
    fn from_start(start: BytesStart<'_>) -> Result<Self, XmlError> {
217✔
220
        let name = core::str::from_utf8(start.name().as_ref())
217✔
221
            .map_err(|_| XmlError::InvalidUtf8)?
217✔
222
            .to_string();
217✔
223
        let mut attributes = Vec::new();
217✔
224
        for attr in start.attributes() {
217✔
225
            let attr = attr.map_err(|e| XmlError::ParseError(e.to_string()))?;
×
226
            let key = core::str::from_utf8(attr.key.as_ref())
×
227
                .map_err(|_| XmlError::InvalidUtf8)?
×
228
                .to_string();
×
229
            let value = attr
×
230
                .unescape_value()
×
231
                .map_err(|e| XmlError::ParseError(e.to_string()))?
×
232
                .into_owned();
×
233
            attributes.push((key, value));
×
234
        }
235
        Ok(Self {
217✔
236
            name,
217✔
237
            attributes,
217✔
238
            children: Vec::new(),
217✔
239
            text: String::new(),
217✔
240
        })
217✔
241
    }
217✔
242

243
    fn push_text(&mut self, text: &str) {
143✔
244
        if text.trim().is_empty() {
143✔
245
            return;
×
246
        }
143✔
247
        if !self.text.is_empty() {
143✔
248
            self.text.push(' ');
×
249
        }
143✔
250
        self.text.push_str(text.trim());
143✔
251
    }
143✔
252
}
253

254
#[derive(Debug, Clone)]
255
enum XmlValue {
256
    Null,
257
    Bool(bool),
258
    I64(i64),
259
    U64(u64),
260
    F64(f64),
261
    String(String),
262
    Array(Vec<XmlValue>),
263
    Object(Vec<XmlField>),
264
}
265

266
#[derive(Debug, Clone)]
267
struct XmlField {
268
    name: String,
269
    location: FieldLocationHint,
270
    value: XmlValue,
271
}
272

273
fn build_events<'de>(input: &'de [u8]) -> Result<Vec<ParseEvent<'de>>, XmlError> {
66✔
274
    let mut reader = Reader::from_reader(Cursor::new(input));
66✔
275
    reader.config_mut().trim_text(true);
66✔
276

277
    let mut buf = Vec::new();
66✔
278
    let mut stack: Vec<Element> = Vec::new();
66✔
279
    let mut root: Option<Element> = None;
66✔
280

281
    loop {
282
        buf.clear();
643✔
283
        match reader
643✔
284
            .read_event_into(&mut buf)
643✔
285
            .map_err(|e| XmlError::ParseError(e.to_string()))?
643✔
286
        {
287
            Event::Start(e) => {
217✔
288
                let elem = Element::from_start(e)?;
217✔
289
                stack.push(elem);
217✔
290
            }
291
            Event::End(_) => {
292
                let elem = stack.pop().ok_or(XmlError::UnbalancedTags)?;
217✔
293
                attach_element(stack.as_mut_slice(), elem, &mut root)?;
217✔
294
            }
295
            Event::Empty(e) => {
×
296
                let elem = Element::from_start(e)?;
×
297
                attach_element(stack.as_mut_slice(), elem, &mut root)?;
×
298
            }
299
            Event::Text(e) => {
143✔
300
                if let Some(current) = stack.last_mut() {
143✔
301
                    let text = e
143✔
302
                        .unescape()
143✔
303
                        .map_err(|err| XmlError::ParseError(err.to_string()))?;
143✔
304
                    current.push_text(text.as_ref());
143✔
305
                }
×
306
            }
307
            Event::CData(e) => {
×
308
                if let Some(current) = stack.last_mut() {
×
309
                    let text =
×
310
                        core::str::from_utf8(e.as_ref()).map_err(|_| XmlError::InvalidUtf8)?;
×
311
                    current.push_text(text);
×
312
                }
×
313
            }
314
            Event::Decl(_) | Event::Comment(_) | Event::PI(_) | Event::DocType(_) => {}
×
315
            Event::Eof => break,
66✔
316
        }
317
    }
318

319
    if !stack.is_empty() {
66✔
320
        return Err(XmlError::UnbalancedTags);
×
321
    }
66✔
322

323
    let root = root.ok_or(XmlError::UnexpectedEof)?;
66✔
324
    let value = element_to_value(&root);
66✔
325
    let mut events = Vec::new();
66✔
326
    emit_value_events(&value, &mut events);
66✔
327
    Ok(events)
66✔
328
}
66✔
329

330
fn attach_element(
217✔
331
    stack: &mut [Element],
217✔
332
    elem: Element,
217✔
333
    root: &mut Option<Element>,
217✔
334
) -> Result<(), XmlError> {
217✔
335
    if let Some(parent) = stack.last_mut() {
217✔
336
        parent.children.push(elem);
151✔
337
    } else if root.is_none() {
151✔
338
        *root = Some(elem);
66✔
339
    } else {
66✔
340
        return Err(XmlError::MultipleRoots);
×
341
    }
342
    Ok(())
217✔
343
}
217✔
344

345
fn element_to_value(elem: &Element) -> XmlValue {
217✔
346
    let text = elem.text.trim();
217✔
347
    let has_attrs = !elem.attributes.is_empty();
217✔
348
    let has_children = !elem.children.is_empty();
217✔
349

350
    if !has_attrs && !has_children {
217✔
351
        if text.is_empty() {
143✔
352
            return XmlValue::Null;
×
353
        }
143✔
354
        return parse_scalar(text);
143✔
355
    }
74✔
356

357
    if !has_attrs && has_children && text.is_empty() && elem.children.len() > 1 {
74✔
358
        let first = &elem.children[0].name;
52✔
359
        if elem.children.iter().all(|child| child.name == *first) {
114✔
360
            let items = elem
10✔
361
                .children
10✔
362
                .iter()
10✔
363
                .map(element_to_value)
10✔
364
                .collect::<Vec<_>>();
10✔
365
            return XmlValue::Array(items);
10✔
366
        }
42✔
367
    }
22✔
368

369
    let mut fields = Vec::new();
64✔
370
    for (name, value) in &elem.attributes {
64✔
371
        fields.push(XmlField {
×
372
            name: name.clone(),
×
373
            location: FieldLocationHint::Attribute,
×
374
            value: XmlValue::String(value.clone()),
×
375
        });
×
376
    }
×
377

378
    let mut grouped: BTreeMap<&str, Vec<XmlValue>> = BTreeMap::new();
64✔
379
    for child in &elem.children {
121✔
380
        grouped
121✔
381
            .entry(child.name.as_str())
121✔
382
            .or_default()
121✔
383
            .push(element_to_value(child));
121✔
384
    }
121✔
385

386
    for (name, mut values) in grouped {
121✔
387
        let value = if values.len() == 1 {
121✔
388
            values.pop().unwrap()
121✔
389
        } else {
390
            XmlValue::Array(values)
×
391
        };
392
        fields.push(XmlField {
121✔
393
            name: name.to_string(),
121✔
394
            location: FieldLocationHint::Child,
121✔
395
            value,
121✔
396
        });
121✔
397
    }
398

399
    if !text.is_empty() {
64✔
400
        if fields.is_empty() {
×
401
            return parse_scalar(text);
×
402
        }
×
403
        fields.push(XmlField {
×
404
            name: "_text".into(),
×
405
            location: FieldLocationHint::Text,
×
406
            value: XmlValue::String(text.to_string()),
×
407
        });
×
408
    }
64✔
409

410
    XmlValue::Object(fields)
64✔
411
}
217✔
412

413
fn parse_scalar(text: &str) -> XmlValue {
143✔
414
    if text.eq_ignore_ascii_case("null") {
143✔
415
        return XmlValue::Null;
3✔
416
    }
140✔
417
    if let Ok(b) = text.parse::<bool>() {
140✔
418
        return XmlValue::Bool(b);
13✔
419
    }
127✔
420
    if let Ok(i) = text.parse::<i64>() {
127✔
421
        return XmlValue::I64(i);
64✔
422
    }
63✔
423
    if let Ok(u) = text.parse::<u64>() {
63✔
424
        return XmlValue::U64(u);
2✔
425
    }
61✔
426
    if let Ok(f) = text.parse::<f64>() {
61✔
427
        return XmlValue::F64(f);
8✔
428
    }
53✔
429
    XmlValue::String(text.to_string())
53✔
430
}
143✔
431

432
fn emit_value_events<'de>(value: &XmlValue, events: &mut Vec<ParseEvent<'de>>) {
217✔
433
    match value {
217✔
434
        XmlValue::Null => events.push(ParseEvent::Scalar(ScalarValue::Null)),
3✔
435
        XmlValue::Bool(b) => events.push(ParseEvent::Scalar(ScalarValue::Bool(*b))),
13✔
436
        XmlValue::I64(n) => events.push(ParseEvent::Scalar(ScalarValue::I64(*n))),
64✔
437
        XmlValue::U64(n) => events.push(ParseEvent::Scalar(ScalarValue::U64(*n))),
2✔
438
        XmlValue::F64(n) => events.push(ParseEvent::Scalar(ScalarValue::F64(*n))),
8✔
439
        XmlValue::String(s) => {
53✔
440
            events.push(ParseEvent::Scalar(ScalarValue::Str(Cow::Owned(s.clone()))))
53✔
441
        }
442
        XmlValue::Array(items) => {
10✔
443
            events.push(ParseEvent::SequenceStart);
10✔
444
            for item in items {
30✔
445
                emit_value_events(item, events);
30✔
446
            }
30✔
447
            events.push(ParseEvent::SequenceEnd);
10✔
448
        }
449
        XmlValue::Object(fields) => {
64✔
450
            events.push(ParseEvent::StructStart);
64✔
451
            for field in fields {
121✔
452
                events.push(ParseEvent::FieldKey(
121✔
453
                    Cow::Owned(field.name.clone()),
121✔
454
                    field.location,
121✔
455
                ));
121✔
456
                emit_value_events(&field.value, events);
121✔
457
            }
121✔
458
            events.push(ParseEvent::StructEnd);
64✔
459
        }
460
    }
461
}
217✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc