xso/
fromxml.rs

1//! # Generic builder type implementations
2//!
3//! This module contains [`FromEventsBuilder`] implementations for types from
4//! foreign libraries (such as the standard library).
5//!
6//! In order to not clutter the `xso` crate's main namespace, they are
7//! stashed away in a separate module.
8
9// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
10//
11// This Source Code Form is subject to the terms of the Mozilla Public
12// License, v. 2.0. If a copy of the MPL was not distributed with this
13// file, You can obtain one at http://mozilla.org/MPL/2.0/.
14
15use alloc::boxed::Box;
16
17use crate::error::{Error, FromEventsError};
18use crate::{Context, FromEventsBuilder, FromXml};
19
20/// Helper struct to construct an `Option<T>` from XML events.
21pub struct OptionBuilder<T: FromEventsBuilder>(T);
22
23impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> {
24    type Output = Option<T::Output>;
25
26    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
27        self.0.feed(ev, ctx).map(|ok| ok.map(Some))
28    }
29}
30
31/// Parsers `T` into `Some(.)`.
32///
33/// Note that this never generates `None`: The main use case is to allow
34/// external (i.e. without calling `from_events`) defaulting to `None` and
35/// for optional serialisation (the [`AsXml`][`crate::AsXml`] implementation
36/// on `Option<T>` emits nothing for `None`).
37impl<T: FromXml> FromXml for Option<T> {
38    type Builder = OptionBuilder<T::Builder>;
39
40    fn from_events(
41        name: rxml::QName,
42        attrs: rxml::AttrMap,
43        ctx: &Context<'_>,
44    ) -> Result<Self::Builder, FromEventsError> {
45        Ok(OptionBuilder(T::from_events(name, attrs, ctx)?))
46    }
47}
48
49/// Helper struct to construct an `Box<T>` from XML events.
50pub struct BoxBuilder<T: FromEventsBuilder>(Box<T>);
51
52impl<T: FromEventsBuilder> FromEventsBuilder for BoxBuilder<T> {
53    type Output = Box<T::Output>;
54
55    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
56        self.0.feed(ev, ctx).map(|ok| ok.map(Box::new))
57    }
58}
59
60/// Parsers `T` into a `Box`.
61impl<T: FromXml> FromXml for Box<T> {
62    type Builder = BoxBuilder<T::Builder>;
63
64    fn from_events(
65        name: rxml::QName,
66        attrs: rxml::AttrMap,
67        ctx: &Context<'_>,
68    ) -> Result<Self::Builder, FromEventsError> {
69        Ok(BoxBuilder(Box::new(T::from_events(name, attrs, ctx)?)))
70    }
71}
72
73#[derive(Debug)]
74enum FallibleBuilderInner<T: FromEventsBuilder, E> {
75    Processing { depth: usize, builder: T },
76    Failed { depth: usize, err: Option<E> },
77    Done,
78}
79
80/// Build a `Result<T, E>` from XML.
81///
82/// This builder, invoked generally via the [`FromXml`] implementation on
83/// `Result<T, E> where T: FromXml, E: From<Error>`, allows to fallably parse
84/// an XSO from XML.
85///
86/// If an error occurs while parsing the XSO, the remaining events which
87/// belong to that XSO are discarded. Once all events have been seen, the
88/// error is returned as `Err(.)` value.
89///
90/// If parsing succeeds, the parsed XSO is returned as `Ok(.)` value.
91#[derive(Debug)]
92pub struct FallibleBuilder<T: FromEventsBuilder, E>(FallibleBuilderInner<T, E>);
93
94impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder<T, E> {
95    type Output = Result<T::Output, E>;
96
97    fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
98        match self.0 {
99            FallibleBuilderInner::Processing {
100                ref mut depth,
101                ref mut builder,
102            } => {
103                let new_depth = match ev {
104                    rxml::Event::StartElement(..) => match depth.checked_add(1) {
105                        // I *think* it is OK to return an err here
106                        // instead of panicking. The reason is that anyone
107                        // who intends to resume processing at the level
108                        // of where we started to parse this thing in case
109                        // of an error either has to:
110                        // - Use this fallible implementation and rely on
111                        //   it capturing the error (which we don't in
112                        //   this case).
113                        // - Or count the depth themselves, which will
114                        //   either fail in the same way, or they use a
115                        //   wider type (in which case it's ok).
116                        None => {
117                            self.0 = FallibleBuilderInner::Done;
118                            return Err(Error::Other("maximum XML nesting depth exceeded"));
119                        }
120                        Some(v) => Some(v),
121                    },
122                    // In case of an element end, underflow means that we
123                    // have reached the end of the XSO we wanted to process.
124                    // We handle that case at the end of the outer match's
125                    // body: Either we have returned a value then (good), or,
126                    // if we reach the end there with a new_depth == None,
127                    // something went horribly wrong (and we panic).
128                    rxml::Event::EndElement(..) => depth.checked_sub(1),
129
130                    // Text and XML declarations have no influence on parsing
131                    // depth.
132                    rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth),
133                };
134
135                match builder.feed(ev, ctx) {
136                    Ok(Some(v)) => {
137                        self.0 = FallibleBuilderInner::Done;
138                        return Ok(Some(Ok(v)));
139                    }
140                    Ok(None) => {
141                        // continue processing in the next round.
142                    }
143                    Err(e) => {
144                        // We are now officially failed ..
145                        match new_depth {
146                            // .. but we are not done yet, so enter the
147                            // failure backtracking state.
148                            Some(depth) => {
149                                self.0 = FallibleBuilderInner::Failed {
150                                    depth,
151                                    err: Some(e.into()),
152                                };
153                                return Ok(None);
154                            }
155                            // .. and we are done with parsing, so we return
156                            // the error as value.
157                            None => {
158                                self.0 = FallibleBuilderInner::Done;
159                                return Ok(Some(Err(e.into())));
160                            }
161                        }
162                    }
163                };
164
165                *depth = match new_depth {
166                    Some(v) => v,
167                    None => unreachable!("fallible parsing continued beyond end of element"),
168                };
169
170                // Need more events.
171                Ok(None)
172            }
173            FallibleBuilderInner::Failed {
174                ref mut depth,
175                ref mut err,
176            } => {
177                *depth = match ev {
178                    rxml::Event::StartElement(..) => match depth.checked_add(1) {
179                        // See above for error return rationale.
180                        None => {
181                            self.0 = FallibleBuilderInner::Done;
182                            return Err(Error::Other("maximum XML nesting depth exceeded"));
183                        }
184                        Some(v) => v,
185                    },
186                    rxml::Event::EndElement(..) => match depth.checked_sub(1) {
187                        Some(v) => v,
188                        None => {
189                            // We are officially done, return a value, switch
190                            // states, and be done with it.
191                            let err = err.take().expect("fallible parsing somehow lost its error");
192                            self.0 = FallibleBuilderInner::Done;
193                            return Ok(Some(Err(err)));
194                        }
195                    },
196
197                    // Text and XML declarations have no influence on parsing
198                    // depth.
199                    rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => *depth,
200                };
201
202                // Need more events
203                Ok(None)
204            }
205            FallibleBuilderInner::Done => {
206                panic!("FromEventsBuilder called after it returned a value")
207            }
208        }
209    }
210}
211
212/// Parsers `T` fallibly. See [`FallibleBuilder`] for details.
213impl<T: FromXml, E: From<Error>> FromXml for Result<T, E> {
214    type Builder = FallibleBuilder<T::Builder, E>;
215
216    fn from_events(
217        name: rxml::QName,
218        attrs: rxml::AttrMap,
219        ctx: &Context<'_>,
220    ) -> Result<Self::Builder, FromEventsError> {
221        match T::from_events(name, attrs, ctx) {
222            Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing {
223                depth: 0,
224                builder,
225            })),
226            Err(FromEventsError::Mismatch { name, attrs }) => {
227                Err(FromEventsError::Mismatch { name, attrs })
228            }
229            Err(FromEventsError::Invalid(e)) => Ok(FallibleBuilder(FallibleBuilderInner::Failed {
230                depth: 0,
231                err: Some(e.into()),
232            })),
233        }
234    }
235}
236
237/// Builder which discards an entire child tree without inspecting the
238/// contents.
239#[derive(Debug, Default)]
240pub struct Discard {
241    depth: usize,
242}
243
244impl Discard {
245    /// Create a new discarding builder.
246    pub fn new() -> Self {
247        Self::default()
248    }
249}
250
251impl FromEventsBuilder for Discard {
252    type Output = ();
253
254    fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
255        match ev {
256            rxml::Event::StartElement(..) => {
257                self.depth = match self.depth.checked_add(1) {
258                    Some(v) => v,
259                    None => return Err(Error::Other("maximum XML nesting depth exceeded")),
260                };
261                Ok(None)
262            }
263            rxml::Event::EndElement(..) => match self.depth.checked_sub(1) {
264                None => Ok(Some(())),
265                Some(v) => {
266                    self.depth = v;
267                    Ok(None)
268                }
269            },
270            _ => Ok(None),
271        }
272    }
273}
274
275/// Builder which discards the contents (or raises on unexpected contents).
276///
277/// This builder is only to be used from within the proc macros and is not
278/// stable, public API.
279#[doc(hidden)]
280#[cfg(feature = "macros")]
281pub struct EmptyBuilder {
282    childerr: &'static str,
283    texterr: &'static str,
284}
285
286#[cfg(feature = "macros")]
287impl FromEventsBuilder for EmptyBuilder {
288    type Output = ();
289
290    fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
291        match ev {
292            rxml::Event::EndElement(..) => Ok(Some(())),
293            rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)),
294            rxml::Event::Text(..) => Err(Error::Other(self.texterr)),
295            _ => Err(Error::Other(
296                "unexpected content in supposed-to-be-empty element",
297            )),
298        }
299    }
300}
301
302/// Precursor struct for [`EmptyBuilder`].
303///
304/// This struct is only to be used from within the proc macros and is not
305/// stable, public API.
306#[doc(hidden)]
307#[cfg(feature = "macros")]
308pub struct Empty {
309    pub attributeerr: &'static str,
310    pub childerr: &'static str,
311    pub texterr: &'static str,
312}
313
314#[cfg(feature = "macros")]
315impl Empty {
316    pub fn start(self, attr: rxml::AttrMap) -> Result<EmptyBuilder, Error> {
317        if !attr.is_empty() {
318            return Err(Error::Other(self.attributeerr));
319        }
320        Ok(EmptyBuilder {
321            childerr: self.childerr,
322            texterr: self.texterr,
323        })
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    use alloc::borrow::ToOwned;
332    use rxml::{parser::EventMetrics, Event, Namespace, NcName};
333
334    macro_rules! null_builder {
335        ($name:ident for $output:ident) => {
336            #[derive(Debug)]
337            enum $name {}
338
339            impl FromEventsBuilder for $name {
340                type Output = $output;
341
342                fn feed(
343                    &mut self,
344                    _: Event,
345                    _: &Context<'_>,
346                ) -> Result<Option<Self::Output>, Error> {
347                    unreachable!();
348                }
349            }
350        };
351    }
352
353    null_builder!(AlwaysMismatchBuilder for AlwaysMismatch);
354    null_builder!(InitialErrorBuilder for InitialError);
355
356    #[derive(Debug)]
357    struct AlwaysMismatch;
358
359    impl FromXml for AlwaysMismatch {
360        type Builder = AlwaysMismatchBuilder;
361
362        fn from_events(
363            name: rxml::QName,
364            attrs: rxml::AttrMap,
365            _ctx: &Context<'_>,
366        ) -> Result<Self::Builder, FromEventsError> {
367            Err(FromEventsError::Mismatch { name, attrs })
368        }
369    }
370
371    #[derive(Debug)]
372    struct InitialError;
373
374    impl FromXml for InitialError {
375        type Builder = InitialErrorBuilder;
376
377        fn from_events(
378            _: rxml::QName,
379            _: rxml::AttrMap,
380            _: &Context<'_>,
381        ) -> Result<Self::Builder, FromEventsError> {
382            Err(FromEventsError::Invalid(Error::Other("some error")))
383        }
384    }
385
386    #[derive(Debug)]
387    struct FailOnContentBuilder;
388
389    impl FromEventsBuilder for FailOnContentBuilder {
390        type Output = FailOnContent;
391
392        fn feed(&mut self, _: Event, _: &Context<'_>) -> Result<Option<Self::Output>, Error> {
393            Err(Error::Other("content error"))
394        }
395    }
396
397    #[derive(Debug)]
398    struct FailOnContent;
399
400    impl FromXml for FailOnContent {
401        type Builder = FailOnContentBuilder;
402
403        fn from_events(
404            _: rxml::QName,
405            _: rxml::AttrMap,
406            _: &Context<'_>,
407        ) -> Result<Self::Builder, FromEventsError> {
408            Ok(FailOnContentBuilder)
409        }
410    }
411
412    fn qname() -> rxml::QName {
413        (Namespace::NONE, NcName::try_from("test").unwrap())
414    }
415
416    fn attrs() -> rxml::AttrMap {
417        rxml::AttrMap::new()
418    }
419
420    #[test]
421    fn fallible_builder_mismatch_passthrough() {
422        match Result::<AlwaysMismatch, Error>::from_events(qname(), attrs(), &Context::empty()) {
423            Err(FromEventsError::Mismatch { .. }) => (),
424            other => panic!("unexpected result: {:?}", other),
425        }
426    }
427
428    #[test]
429    fn fallible_builder_initial_error_capture() {
430        let ctx = Context::empty();
431        let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
432            Ok(v) => v,
433            other => panic!("unexpected result: {:?}", other),
434        };
435        match builder.feed(
436            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
437            &ctx,
438        ) {
439            Ok(None) => (),
440            other => panic!("unexpected result: {:?}", other),
441        };
442        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
443            Ok(Some(Err(Error::Other("some error")))) => (),
444            other => panic!("unexpected result: {:?}", other),
445        };
446    }
447
448    #[test]
449    fn fallible_builder_initial_error_capture_allows_nested_stuff() {
450        let ctx = Context::empty();
451        let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
452            Ok(v) => v,
453            other => panic!("unexpected result: {:?}", other),
454        };
455        match builder.feed(
456            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
457            &ctx,
458        ) {
459            Ok(None) => (),
460            other => panic!("unexpected result: {:?}", other),
461        };
462        match builder.feed(
463            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
464            &ctx,
465        ) {
466            Ok(None) => (),
467            other => panic!("unexpected result: {:?}", other),
468        };
469        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
470            Ok(None) => (),
471            other => panic!("unexpected result: {:?}", other),
472        };
473        match builder.feed(
474            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
475            &ctx,
476        ) {
477            Ok(None) => (),
478            other => panic!("unexpected result: {:?}", other),
479        };
480        match builder.feed(
481            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
482            &ctx,
483        ) {
484            Ok(None) => (),
485            other => panic!("unexpected result: {:?}", other),
486        };
487        match builder.feed(
488            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
489            &ctx,
490        ) {
491            Ok(None) => (),
492            other => panic!("unexpected result: {:?}", other),
493        };
494        match builder.feed(
495            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
496            &ctx,
497        ) {
498            Ok(None) => (),
499            other => panic!("unexpected result: {:?}", other),
500        };
501        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
502            Ok(None) => (),
503            other => panic!("unexpected result: {:?}", other),
504        };
505        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
506            Ok(None) => (),
507            other => panic!("unexpected result: {:?}", other),
508        };
509        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
510            Ok(Some(Err(Error::Other("some error")))) => (),
511            other => panic!("unexpected result: {:?}", other),
512        };
513    }
514
515    #[test]
516    fn fallible_builder_content_error_capture() {
517        let ctx = Context::empty();
518        let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
519        {
520            Ok(v) => v,
521            other => panic!("unexpected result: {:?}", other),
522        };
523        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
524            Ok(Some(Err(Error::Other("content error")))) => (),
525            other => panic!("unexpected result: {:?}", other),
526        };
527    }
528
529    #[test]
530    fn fallible_builder_content_error_capture_with_more_content() {
531        let ctx = Context::empty();
532        let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
533        {
534            Ok(v) => v,
535            other => panic!("unexpected result: {:?}", other),
536        };
537        match builder.feed(
538            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
539            &ctx,
540        ) {
541            Ok(None) => (),
542            other => panic!("unexpected result: {:?}", other),
543        };
544        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
545            Ok(Some(Err(Error::Other("content error")))) => (),
546            other => panic!("unexpected result: {:?}", other),
547        };
548    }
549
550    #[test]
551    fn fallible_builder_content_error_capture_with_nested_content() {
552        let ctx = Context::empty();
553        let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
554        {
555            Ok(v) => v,
556            other => panic!("unexpected result: {:?}", other),
557        };
558        match builder.feed(
559            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
560            &ctx,
561        ) {
562            Ok(None) => (),
563            other => panic!("unexpected result: {:?}", other),
564        };
565        match builder.feed(
566            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
567            &ctx,
568        ) {
569            Ok(None) => (),
570            other => panic!("unexpected result: {:?}", other),
571        };
572        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
573            Ok(None) => (),
574            other => panic!("unexpected result: {:?}", other),
575        };
576        match builder.feed(
577            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
578            &ctx,
579        ) {
580            Ok(None) => (),
581            other => panic!("unexpected result: {:?}", other),
582        };
583        match builder.feed(
584            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
585            &ctx,
586        ) {
587            Ok(None) => (),
588            other => panic!("unexpected result: {:?}", other),
589        };
590        match builder.feed(
591            Event::StartElement(EventMetrics::zero(), qname(), attrs()),
592            &ctx,
593        ) {
594            Ok(None) => (),
595            other => panic!("unexpected result: {:?}", other),
596        };
597        match builder.feed(
598            Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
599            &ctx,
600        ) {
601            Ok(None) => (),
602            other => panic!("unexpected result: {:?}", other),
603        };
604        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
605            Ok(None) => (),
606            other => panic!("unexpected result: {:?}", other),
607        };
608        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
609            Ok(None) => (),
610            other => panic!("unexpected result: {:?}", other),
611        };
612        match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
613            Ok(Some(Err(Error::Other("content error")))) => (),
614            other => panic!("unexpected result: {:?}", other),
615        };
616    }
617}