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

facet-rs / facet / 20165231858

12 Dec 2025 11:23AM UTC coverage: 57.831% (+0.2%) from 57.586%
20165231858

Pull #1255

github

web-flow
Merge d988a5ef7 into a8626bccf
Pull Request #1255: Introduce facet-format

968 of 1685 new or added lines in 18 files covered. (57.45%)

4 existing lines in 2 files now uncovered.

31400 of 54296 relevant lines covered (57.83%)

5827.68 hits per line

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

61.83
/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 {
10✔
24
        match build_events(input) {
10✔
25
            Ok(events) => Self {
10✔
26
                events,
10✔
27
                idx: 0,
10✔
28
                pending_error: None,
10✔
29
            },
10✔
NEW
30
            Err(err) => Self {
×
NEW
31
                events: Vec::new(),
×
NEW
32
                idx: 0,
×
NEW
33
                pending_error: Some(err),
×
NEW
34
            },
×
35
        }
36
    }
10✔
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 {
NEW
49
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
NEW
50
        match self {
×
NEW
51
            XmlError::ParseError(msg) => write!(f, "XML parse error: {}", msg),
×
NEW
52
            XmlError::UnexpectedEof => write!(f, "Unexpected end of XML"),
×
NEW
53
            XmlError::UnbalancedTags => write!(f, "Unbalanced XML tags"),
×
NEW
54
            XmlError::InvalidUtf8 => write!(f, "Invalid UTF-8 in XML"),
×
NEW
55
            XmlError::MultipleRoots => write!(f, "XML document has multiple root elements"),
×
56
        }
NEW
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> {
80✔
68
        if let Some(err) = &self.pending_error {
80✔
NEW
69
            return Err(err.clone());
×
70
        }
80✔
71
        if self.idx >= self.events.len() {
80✔
NEW
72
            return Err(XmlError::UnexpectedEof);
×
73
        }
80✔
74
        let event = self.events[self.idx].clone();
80✔
75
        self.idx += 1;
80✔
76
        Ok(event)
80✔
77
    }
80✔
78

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

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

NEW
118
    fn begin_probe(&mut self) -> Result<Self::Probe<'_>, Self::Error> {
×
NEW
119
        Ok(XmlProbe {
×
NEW
120
            evidence: Vec::new(),
×
NEW
121
            idx: 0,
×
NEW
122
        })
×
NEW
123
    }
×
124
}
125

126
pub struct XmlProbe<'de> {
127
    evidence: Vec<FieldEvidence<'de>>,
128
    idx: usize,
129
}
130

131
impl<'de> ProbeStream<'de> for XmlProbe<'de> {
132
    type Error = XmlError;
133

NEW
134
    fn next(&mut self) -> Result<Option<FieldEvidence<'de>>, Self::Error> {
×
NEW
135
        if self.idx >= self.evidence.len() {
×
NEW
136
            Ok(None)
×
137
        } else {
NEW
138
            let ev = self.evidence[self.idx].clone();
×
NEW
139
            self.idx += 1;
×
NEW
140
            Ok(Some(ev))
×
141
        }
NEW
142
    }
×
143
}
144

145
#[derive(Debug, Clone)]
146
struct Element {
147
    name: String,
148
    attributes: Vec<(String, String)>,
149
    children: Vec<Element>,
150
    text: String,
151
}
152

153
impl Element {
154
    fn from_start(start: BytesStart<'_>) -> Result<Self, XmlError> {
46✔
155
        let name = core::str::from_utf8(start.name().as_ref())
46✔
156
            .map_err(|_| XmlError::InvalidUtf8)?
46✔
157
            .to_string();
46✔
158
        let mut attributes = Vec::new();
46✔
159
        for attr in start.attributes() {
46✔
NEW
160
            let attr = attr.map_err(|e| XmlError::ParseError(e.to_string()))?;
×
NEW
161
            let key = core::str::from_utf8(attr.key.as_ref())
×
NEW
162
                .map_err(|_| XmlError::InvalidUtf8)?
×
NEW
163
                .to_string();
×
NEW
164
            let value = attr
×
NEW
165
                .unescape_value()
×
NEW
166
                .map_err(|e| XmlError::ParseError(e.to_string()))?
×
NEW
167
                .into_owned();
×
NEW
168
            attributes.push((key, value));
×
169
        }
170
        Ok(Self {
46✔
171
            name,
46✔
172
            attributes,
46✔
173
            children: Vec::new(),
46✔
174
            text: String::new(),
46✔
175
        })
46✔
176
    }
46✔
177

178
    fn push_text(&mut self, text: &str) {
30✔
179
        if text.trim().is_empty() {
30✔
NEW
180
            return;
×
181
        }
30✔
182
        if !self.text.is_empty() {
30✔
NEW
183
            self.text.push(' ');
×
184
        }
30✔
185
        self.text.push_str(text.trim());
30✔
186
    }
30✔
187
}
188

189
#[derive(Debug, Clone)]
190
enum XmlValue {
191
    Null,
192
    Bool(bool),
193
    I64(i64),
194
    U64(u64),
195
    F64(f64),
196
    String(String),
197
    Array(Vec<XmlValue>),
198
    Object(Vec<XmlField>),
199
}
200

201
#[derive(Debug, Clone)]
202
struct XmlField {
203
    name: String,
204
    location: FieldLocationHint,
205
    value: XmlValue,
206
}
207

208
fn build_events<'de>(input: &'de [u8]) -> Result<Vec<ParseEvent<'de>>, XmlError> {
10✔
209
    let mut reader = Reader::from_reader(Cursor::new(input));
10✔
210
    reader.config_mut().trim_text(true);
10✔
211

212
    let mut buf = Vec::new();
10✔
213
    let mut stack: Vec<Element> = Vec::new();
10✔
214
    let mut root: Option<Element> = None;
10✔
215

216
    loop {
217
        buf.clear();
132✔
218
        match reader
132✔
219
            .read_event_into(&mut buf)
132✔
220
            .map_err(|e| XmlError::ParseError(e.to_string()))?
132✔
221
        {
222
            Event::Start(e) => {
46✔
223
                let elem = Element::from_start(e)?;
46✔
224
                stack.push(elem);
46✔
225
            }
226
            Event::End(_) => {
227
                let elem = stack.pop().ok_or(XmlError::UnbalancedTags)?;
46✔
228
                attach_element(stack.as_mut_slice(), elem, &mut root)?;
46✔
229
            }
NEW
230
            Event::Empty(e) => {
×
NEW
231
                let elem = Element::from_start(e)?;
×
NEW
232
                attach_element(stack.as_mut_slice(), elem, &mut root)?;
×
233
            }
234
            Event::Text(e) => {
30✔
235
                if let Some(current) = stack.last_mut() {
30✔
236
                    let text = e
30✔
237
                        .unescape()
30✔
238
                        .map_err(|err| XmlError::ParseError(err.to_string()))?;
30✔
239
                    current.push_text(text.as_ref());
30✔
NEW
240
                }
×
241
            }
NEW
242
            Event::CData(e) => {
×
NEW
243
                if let Some(current) = stack.last_mut() {
×
NEW
244
                    let text =
×
NEW
245
                        core::str::from_utf8(e.as_ref()).map_err(|_| XmlError::InvalidUtf8)?;
×
NEW
246
                    current.push_text(text);
×
NEW
247
                }
×
248
            }
NEW
249
            Event::Decl(_) | Event::Comment(_) | Event::PI(_) | Event::DocType(_) => {}
×
250
            Event::Eof => break,
10✔
251
        }
252
    }
253

254
    if !stack.is_empty() {
10✔
NEW
255
        return Err(XmlError::UnbalancedTags);
×
256
    }
10✔
257

258
    let root = root.ok_or(XmlError::UnexpectedEof)?;
10✔
259
    let value = element_to_value(&root);
10✔
260
    let mut events = Vec::new();
10✔
261
    emit_value_events(&value, &mut events);
10✔
262
    Ok(events)
10✔
263
}
10✔
264

265
fn attach_element(
46✔
266
    stack: &mut [Element],
46✔
267
    elem: Element,
46✔
268
    root: &mut Option<Element>,
46✔
269
) -> Result<(), XmlError> {
46✔
270
    if let Some(parent) = stack.last_mut() {
46✔
271
        parent.children.push(elem);
36✔
272
    } else if root.is_none() {
36✔
273
        *root = Some(elem);
10✔
274
    } else {
10✔
NEW
275
        return Err(XmlError::MultipleRoots);
×
276
    }
277
    Ok(())
46✔
278
}
46✔
279

280
fn element_to_value(elem: &Element) -> XmlValue {
46✔
281
    let text = elem.text.trim();
46✔
282
    let has_attrs = !elem.attributes.is_empty();
46✔
283
    let has_children = !elem.children.is_empty();
46✔
284

285
    if !has_attrs && !has_children {
46✔
286
        if text.is_empty() {
30✔
NEW
287
            return XmlValue::Null;
×
288
        }
30✔
289
        return parse_scalar(text);
30✔
290
    }
16✔
291

292
    if !has_attrs && has_children && text.is_empty() && elem.children.len() > 1 {
16✔
293
        let first = &elem.children[0].name;
12✔
294
        if elem.children.iter().all(|child| child.name == *first) {
30✔
295
            let items = elem
6✔
296
                .children
6✔
297
                .iter()
6✔
298
                .map(element_to_value)
6✔
299
                .collect::<Vec<_>>();
6✔
300
            return XmlValue::Array(items);
6✔
301
        }
6✔
302
    }
4✔
303

304
    let mut fields = Vec::new();
10✔
305
    for (name, value) in &elem.attributes {
10✔
NEW
306
        fields.push(XmlField {
×
NEW
307
            name: name.clone(),
×
NEW
308
            location: FieldLocationHint::Attribute,
×
NEW
309
            value: XmlValue::String(value.clone()),
×
NEW
310
        });
×
NEW
311
    }
×
312

313
    let mut grouped: BTreeMap<&str, Vec<XmlValue>> = BTreeMap::new();
10✔
314
    for child in &elem.children {
18✔
315
        grouped
18✔
316
            .entry(child.name.as_str())
18✔
317
            .or_default()
18✔
318
            .push(element_to_value(child));
18✔
319
    }
18✔
320

321
    for (name, mut values) in grouped {
18✔
322
        let value = if values.len() == 1 {
18✔
323
            values.pop().unwrap()
18✔
324
        } else {
NEW
325
            XmlValue::Array(values)
×
326
        };
327
        fields.push(XmlField {
18✔
328
            name: name.to_string(),
18✔
329
            location: FieldLocationHint::Child,
18✔
330
            value,
18✔
331
        });
18✔
332
    }
333

334
    if !text.is_empty() {
10✔
NEW
335
        if fields.is_empty() {
×
NEW
336
            return parse_scalar(text);
×
NEW
337
        }
×
NEW
338
        fields.push(XmlField {
×
NEW
339
            name: "_text".into(),
×
NEW
340
            location: FieldLocationHint::Text,
×
NEW
341
            value: XmlValue::String(text.to_string()),
×
NEW
342
        });
×
343
    }
10✔
344

345
    XmlValue::Object(fields)
10✔
346
}
46✔
347

348
fn parse_scalar(text: &str) -> XmlValue {
30✔
349
    if text.eq_ignore_ascii_case("null") {
30✔
350
        return XmlValue::Null;
2✔
351
    }
28✔
352
    if let Ok(b) = text.parse::<bool>() {
28✔
353
        return XmlValue::Bool(b);
4✔
354
    }
24✔
355
    if let Ok(i) = text.parse::<i64>() {
24✔
356
        return XmlValue::I64(i);
12✔
357
    }
12✔
358
    if let Ok(u) = text.parse::<u64>() {
12✔
NEW
359
        return XmlValue::U64(u);
×
360
    }
12✔
361
    if let Ok(f) = text.parse::<f64>() {
12✔
362
        return XmlValue::F64(f);
2✔
363
    }
10✔
364
    XmlValue::String(text.to_string())
10✔
365
}
30✔
366

367
fn emit_value_events<'de>(value: &XmlValue, events: &mut Vec<ParseEvent<'de>>) {
46✔
368
    match value {
46✔
369
        XmlValue::Null => events.push(ParseEvent::Scalar(ScalarValue::Null)),
2✔
370
        XmlValue::Bool(b) => events.push(ParseEvent::Scalar(ScalarValue::Bool(*b))),
4✔
371
        XmlValue::I64(n) => events.push(ParseEvent::Scalar(ScalarValue::I64(*n))),
12✔
NEW
372
        XmlValue::U64(n) => events.push(ParseEvent::Scalar(ScalarValue::U64(*n))),
×
373
        XmlValue::F64(n) => events.push(ParseEvent::Scalar(ScalarValue::F64(*n))),
2✔
374
        XmlValue::String(s) => {
10✔
375
            events.push(ParseEvent::Scalar(ScalarValue::Str(Cow::Owned(s.clone()))))
10✔
376
        }
377
        XmlValue::Array(items) => {
6✔
378
            events.push(ParseEvent::SequenceStart);
6✔
379
            for item in items {
18✔
380
                emit_value_events(item, events);
18✔
381
            }
18✔
382
            events.push(ParseEvent::SequenceEnd);
6✔
383
        }
384
        XmlValue::Object(fields) => {
10✔
385
            events.push(ParseEvent::StructStart);
10✔
386
            for field in fields {
18✔
387
                events.push(ParseEvent::FieldKey(
18✔
388
                    Cow::Owned(field.name.clone()),
18✔
389
                    field.location,
18✔
390
                ));
18✔
391
                emit_value_events(&field.value, events);
18✔
392
            }
18✔
393
            events.push(ParseEvent::StructEnd);
10✔
394
        }
395
    }
396
}
46✔
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