facet_xml/
error.rs

1//! Error types for XML serialization and deserialization.
2
3use std::{
4    error::Error,
5    fmt::{self, Display},
6};
7
8use facet_core::Def;
9use facet_reflect::ReflectError;
10use miette::SourceSpan;
11
12/// Error type for XML deserialization.
13#[derive(Debug)]
14pub struct XmlError {
15    /// The specific kind of error
16    pub(crate) kind: XmlErrorKind,
17    /// Source code for diagnostics
18    pub(crate) source_code: Option<String>,
19    /// Primary span where the error occurred
20    pub(crate) span: Option<SourceSpan>,
21}
22
23impl XmlError {
24    /// Returns a reference to the error kind for detailed error inspection.
25    pub fn kind(&self) -> &XmlErrorKind {
26        &self.kind
27    }
28
29    /// Create a new error with the given kind.
30    pub(crate) fn new(kind: impl Into<XmlErrorKind>) -> Self {
31        XmlError {
32            kind: kind.into(),
33            source_code: None,
34            span: None,
35        }
36    }
37
38    /// Attach source code to this error for diagnostics.
39    pub(crate) fn with_source(mut self, source: impl Into<String>) -> Self {
40        self.source_code = Some(source.into());
41        self
42    }
43
44    /// Attach a span to this error for diagnostics.
45    pub(crate) fn with_span(mut self, span: impl Into<SourceSpan>) -> Self {
46        self.span = Some(span.into());
47        self
48    }
49}
50
51impl Display for XmlError {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
53        let kind = &self.kind;
54        write!(f, "{kind}")
55    }
56}
57
58impl Error for XmlError {}
59
60impl<K: Into<XmlErrorKind>> From<K> for XmlError {
61    fn from(value: K) -> Self {
62        XmlError::new(value)
63    }
64}
65
66/// Public phase indicator for [`XmlErrorKind::MissingXmlAnnotations`].
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum MissingAnnotationPhase {
69    /// Error triggered while serializing.
70    Serialize,
71    /// Error triggered while deserializing.
72    Deserialize,
73}
74
75/// Detailed classification of XML errors.
76#[derive(Debug)]
77#[non_exhaustive]
78pub enum XmlErrorKind {
79    // Deserialization errors
80    /// The document shape is invalid (expected struct with element fields).
81    InvalidDocumentShape(&'static Def),
82    /// Failed to parse the XML document.
83    Parse(String),
84    /// Error from the reflection system during deserialization.
85    Reflect(ReflectError),
86    /// Encountered an unsupported shape during deserialization.
87    UnsupportedShape(String),
88    /// No field matches the given element name.
89    NoMatchingElement(String),
90    /// No field matches the given attribute name.
91    NoMatchingAttribute(String),
92    /// Unknown attribute encountered.
93    UnknownAttribute {
94        /// The unknown attribute name.
95        attribute: String,
96        /// List of expected attribute names.
97        expected: Vec<&'static str>,
98    },
99    /// No text field found for text content.
100    NoTextField,
101    /// Unexpected text content.
102    UnexpectedText,
103    /// Unsupported value definition.
104    UnsupportedValueDef(String),
105    /// Value doesn't fit the expected shape.
106    InvalidValueForShape(String),
107    /// Solver error (ambiguous or no matching variant for flattened enum).
108    Solver(facet_solver::SolverError),
109    /// Schema construction error.
110    SchemaError(facet_solver::SchemaError),
111    /// Unexpected end of input.
112    UnexpectedEof,
113    /// Unexpected XML event.
114    UnexpectedEvent(String),
115    /// Missing required element.
116    MissingElement(String),
117    /// Missing required attribute.
118    MissingAttribute(String),
119    /// Invalid attribute value.
120    InvalidAttributeValue {
121        /// The attribute name.
122        name: String,
123        /// The invalid value.
124        value: String,
125        /// The expected type.
126        expected_type: String,
127    },
128
129    /// Unknown field encountered when deny_unknown_fields is set.
130    UnknownField {
131        /// The unknown field name.
132        field: String,
133        /// List of expected field names.
134        expected: Vec<&'static str>,
135    },
136    /// Invalid UTF-8 in input.
137    InvalidUtf8(String),
138    /// Base64 decoding error.
139    Base64Decode(String),
140
141    // Serialization errors
142    /// IO error during serialization.
143    Io(String),
144    /// Expected a struct for XML document serialization.
145    SerializeNotStruct,
146    /// Expected a list for elements field.
147    SerializeNotList,
148    /// Unknown element type during serialization.
149    SerializeUnknownElementType,
150    /// Unknown value type during serialization.
151    SerializeUnknownValueType,
152    /// Struct fields lack XML annotations, so they cannot be mapped automatically.
153    MissingXmlAnnotations {
154        /// Fully-qualified type name of the struct.
155        type_name: &'static str,
156        /// Whether the failure happened while serializing or deserializing.
157        phase: MissingAnnotationPhase,
158        /// Offending fields paired with their Rust type identifiers.
159        fields: Vec<(&'static str, &'static str)>,
160    },
161}
162
163impl XmlErrorKind {
164    /// Returns an error code for this error kind.
165    pub fn code(&self) -> &'static str {
166        match self {
167            XmlErrorKind::InvalidDocumentShape(_) => "xml::invalid_document_shape",
168            XmlErrorKind::Parse(_) => "xml::parse",
169            XmlErrorKind::Reflect(_) => "xml::reflect",
170            XmlErrorKind::UnsupportedShape(_) => "xml::unsupported_shape",
171            XmlErrorKind::NoMatchingElement(_) => "xml::no_matching_element",
172            XmlErrorKind::NoMatchingAttribute(_) => "xml::no_matching_attribute",
173            XmlErrorKind::UnknownAttribute { .. } => "xml::unknown_attribute",
174            XmlErrorKind::NoTextField => "xml::no_text_field",
175            XmlErrorKind::UnexpectedText => "xml::unexpected_text",
176            XmlErrorKind::UnsupportedValueDef(_) => "xml::unsupported_value_def",
177            XmlErrorKind::InvalidValueForShape(_) => "xml::invalid_value",
178            XmlErrorKind::Solver(_) => "xml::solver",
179            XmlErrorKind::SchemaError(_) => "xml::schema",
180            XmlErrorKind::UnexpectedEof => "xml::unexpected_eof",
181            XmlErrorKind::UnexpectedEvent(_) => "xml::unexpected_event",
182            XmlErrorKind::MissingElement(_) => "xml::missing_element",
183            XmlErrorKind::MissingAttribute(_) => "xml::missing_attribute",
184            XmlErrorKind::InvalidAttributeValue { .. } => "xml::invalid_attribute_value",
185            XmlErrorKind::UnknownField { .. } => "xml::unknown_field",
186            XmlErrorKind::InvalidUtf8(_) => "xml::invalid_utf8",
187            XmlErrorKind::Base64Decode(_) => "xml::base64_decode",
188            XmlErrorKind::Io(_) => "xml::io",
189            XmlErrorKind::SerializeNotStruct => "xml::serialize_not_struct",
190            XmlErrorKind::SerializeNotList => "xml::serialize_not_list",
191            XmlErrorKind::SerializeUnknownElementType => "xml::serialize_unknown_element_type",
192            XmlErrorKind::SerializeUnknownValueType => "xml::serialize_unknown_value_type",
193            XmlErrorKind::MissingXmlAnnotations { .. } => "xml::missing_xml_annotations",
194        }
195    }
196}
197
198impl Display for XmlErrorKind {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            XmlErrorKind::InvalidDocumentShape(def) => {
202                write!(
203                    f,
204                    "invalid shape {def:#?} — expected struct with element/attribute fields"
205                )
206            }
207            XmlErrorKind::Parse(msg) => write!(f, "XML parse error: {msg}"),
208            XmlErrorKind::Reflect(reflect_error) => write!(f, "{reflect_error}"),
209            XmlErrorKind::UnsupportedShape(msg) => write!(f, "unsupported shape: {msg}"),
210            XmlErrorKind::NoMatchingElement(element_name) => {
211                write!(f, "no matching field for element '{element_name}'")
212            }
213            XmlErrorKind::NoMatchingAttribute(attr_name) => {
214                write!(f, "no matching field for attribute '{attr_name}'")
215            }
216            XmlErrorKind::UnknownAttribute {
217                attribute,
218                expected,
219            } => {
220                write!(
221                    f,
222                    "unknown attribute '{}', expected one of: {}",
223                    attribute,
224                    expected.join(", ")
225                )
226            }
227            XmlErrorKind::NoTextField => {
228                write!(f, "no field marked with xml::text to receive text content")
229            }
230            XmlErrorKind::UnexpectedText => {
231                write!(f, "unexpected text content")
232            }
233            XmlErrorKind::UnsupportedValueDef(msg) => {
234                write!(f, "unsupported value definition: {msg}")
235            }
236            XmlErrorKind::InvalidValueForShape(msg) => {
237                write!(f, "invalid value for shape: {msg}")
238            }
239            XmlErrorKind::Solver(e) => write!(f, "{e}"),
240            XmlErrorKind::SchemaError(e) => write!(f, "schema error: {e}"),
241            XmlErrorKind::UnexpectedEof => write!(f, "unexpected end of XML input"),
242            XmlErrorKind::UnexpectedEvent(msg) => write!(f, "unexpected XML event: {msg}"),
243            XmlErrorKind::MissingElement(name) => write!(f, "missing required element '{name}'"),
244            XmlErrorKind::MissingAttribute(name) => {
245                write!(f, "missing required attribute '{name}'")
246            }
247            XmlErrorKind::InvalidAttributeValue {
248                name,
249                value,
250                expected_type,
251            } => {
252                write!(
253                    f,
254                    "invalid value '{value}' for attribute '{name}', expected {expected_type}"
255                )
256            }
257            XmlErrorKind::UnknownField { field, expected } => {
258                write!(
259                    f,
260                    "unknown field '{}', expected one of: {}",
261                    field,
262                    expected.join(", ")
263                )
264            }
265            XmlErrorKind::InvalidUtf8(msg) => write!(f, "invalid UTF-8: {msg}"),
266            XmlErrorKind::Base64Decode(msg) => write!(f, "base64 decode error: {msg}"),
267            XmlErrorKind::Io(msg) => write!(f, "IO error: {msg}"),
268            XmlErrorKind::SerializeNotStruct => {
269                write!(f, "expected struct for XML document serialization")
270            }
271            XmlErrorKind::SerializeNotList => {
272                write!(f, "expected list for elements field")
273            }
274            XmlErrorKind::SerializeUnknownElementType => {
275                write!(
276                    f,
277                    "cannot determine element name for value (expected enum or struct with element_name)"
278                )
279            }
280            XmlErrorKind::SerializeUnknownValueType => {
281                write!(f, "cannot serialize value: unknown type")
282            }
283            XmlErrorKind::MissingXmlAnnotations {
284                type_name,
285                phase,
286                fields,
287            } => {
288                let verb = match phase {
289                    MissingAnnotationPhase::Serialize => "serialize",
290                    MissingAnnotationPhase::Deserialize => "deserialize",
291                };
292
293                write!(
294                    f,
295                    "{type_name} cannot {verb} because these fields lack XML annotations: "
296                )?;
297                for (idx, (field, ty)) in fields.iter().enumerate() {
298                    if idx > 0 {
299                        write!(f, ", ")?;
300                    }
301                    write!(f, "{field}: {ty}")?;
302                }
303                write!(
304                    f,
305                    ". Each field must opt into XML via one of:\n\
306                     • #[facet(xml::attribute)]  → <{type_name} field=\"…\" /> (attributes)\n\
307                     • #[facet(xml::element)]    → <{type_name}><field>…</field></{type_name}> (single child)\n\
308                     • #[facet(xml::elements)]   → <{type_name}><field>…</field>…</{type_name}> (lists of children)\n\
309                     • #[facet(xml::text)]       → <{type_name}>…</{type_name}> (text content)\n\
310                     • #[facet(xml::element_name)] to capture the element/tag name itself.\n\
311                     `#[facet(child)]` is accepted as shorthand for xml::element. \
312                     Use #[facet(flatten)] or #[facet(skip*)] if the field should be omitted."
313                )
314            }
315        }
316    }
317}
318
319impl From<ReflectError> for XmlErrorKind {
320    fn from(value: ReflectError) -> Self {
321        Self::Reflect(value)
322    }
323}
324
325impl From<facet_solver::SchemaError> for XmlErrorKind {
326    fn from(value: facet_solver::SchemaError) -> Self {
327        Self::SchemaError(value)
328    }
329}
330
331// ============================================================================
332// Diagnostic Implementation
333// ============================================================================
334
335impl miette::Diagnostic for XmlError {
336    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
337        Some(Box::new(self.kind.code()))
338    }
339
340    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
341        self.source_code
342            .as_ref()
343            .map(|s| s as &dyn miette::SourceCode)
344    }
345
346    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
347        if let Some(span) = self.span {
348            let label = match &self.kind {
349                XmlErrorKind::UnknownAttribute { attribute, .. } => {
350                    format!("unknown attribute `{attribute}`")
351                }
352                XmlErrorKind::NoMatchingElement(name) => {
353                    format!("no field matches `{name}`")
354                }
355                XmlErrorKind::NoMatchingAttribute(name) => {
356                    format!("no field matches attribute `{name}`")
357                }
358                XmlErrorKind::MissingElement(name) => {
359                    format!("missing element `{name}`")
360                }
361                XmlErrorKind::MissingAttribute(name) => {
362                    format!("missing attribute `{name}`")
363                }
364                XmlErrorKind::UnknownField { field, .. } => {
365                    format!("unknown field `{field}`")
366                }
367                _ => "error occurred here".to_string(),
368            };
369            Some(Box::new(std::iter::once(miette::LabeledSpan::at(
370                span, label,
371            ))))
372        } else {
373            None
374        }
375    }
376
377    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
378        match &self.kind {
379            XmlErrorKind::UnknownAttribute { expected, .. } => Some(Box::new(format!(
380                "expected one of: {}",
381                expected.join(", ")
382            ))),
383            XmlErrorKind::NoTextField => Some(Box::new(
384                "add #[facet(xml::text)] to a String field to capture text content",
385            )),
386            XmlErrorKind::UnknownField { expected, .. } => Some(Box::new(format!(
387                "expected one of: {}",
388                expected.join(", ")
389            ))),
390            _ => None,
391        }
392    }
393}