1use std::fmt;
2
3pub type XmlResult<T> = Result<T, XmlError>;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum ErrorKind {
9 InvalidName,
10 InvalidNamespace,
11 DuplicateNamespacePrefix,
12 UnknownNamespacePrefix,
13 InvalidOperation,
14 NotFound,
15 Parse,
16 Build,
17 Query,
18 Validation,
19 Signature,
20 Io,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct Span {
26 line: Option<usize>,
27 column: Option<usize>,
28}
29
30impl Span {
31 pub fn new(line: usize, column: usize) -> Self {
33 Self {
34 line: Some(line),
35 column: Some(column),
36 }
37 }
38
39 pub fn unknown() -> Self {
41 Self {
42 line: None,
43 column: None,
44 }
45 }
46
47 pub fn line(&self) -> Option<usize> {
48 self.line
49 }
50
51 pub fn column(&self) -> Option<usize> {
52 self.column
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct XmlError {
59 kind: ErrorKind,
60 message: String,
61 span: Option<Span>,
62}
63
64impl XmlError {
65 pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
66 Self {
67 kind,
68 message: message.into(),
69 span: None,
70 }
71 }
72
73 pub fn with_span(mut self, span: Span) -> Self {
74 self.span = Some(span);
75 self
76 }
77
78 pub fn invalid_name(name: impl AsRef<str>, reason: impl AsRef<str>) -> Self {
79 Self::new(
80 ErrorKind::InvalidName,
81 format!("invalid XML name `{}`: {}", name.as_ref(), reason.as_ref()),
82 )
83 }
84
85 pub fn kind(&self) -> &ErrorKind {
86 &self.kind
87 }
88
89 pub fn message(&self) -> &str {
90 &self.message
91 }
92
93 pub fn span(&self) -> Option<Span> {
94 self.span
95 }
96}
97
98impl fmt::Display for XmlError {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self.span {
101 Some(span) => match (span.line(), span.column()) {
102 (Some(line), Some(column)) => {
103 write!(
104 f,
105 "{:?}: {} at line {}, column {}",
106 self.kind, self.message, line, column
107 )
108 }
109 _ => write!(f, "{:?}: {} at unknown location", self.kind, self.message),
110 },
111 None => write!(f, "{:?}: {}", self.kind, self.message),
112 }
113 }
114}
115
116impl std::error::Error for XmlError {}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn error_includes_kind_and_message() {
124 let error = XmlError::new(ErrorKind::InvalidName, "bad name");
125
126 assert_eq!(error.kind(), &ErrorKind::InvalidName);
127 assert_eq!(error.message(), "bad name");
128 assert_eq!(error.span(), None);
129 }
130
131 #[test]
132 fn error_can_include_span() {
133 let error = XmlError::new(ErrorKind::Parse, "unexpected token").with_span(Span::new(3, 9));
134
135 assert_eq!(error.span(), Some(Span::new(3, 9)));
136 assert!(error.to_string().contains("line 3, column 9"));
137 }
138}