parse/xml/
error.rs

1// Lints
2// Todo: remove when `https://github.com/rust-lang/rust/issues/147648` will be resolved.
3#![allow(unused_assignments)]
4
5use camino::{
6    Utf8Path,
7    Utf8PathBuf,
8};
9use miette::{
10    NamedSource,
11    SourceOffset,
12    SourceSpan,
13};
14use roxmltree::{
15    TextPos,
16};
17
18#[derive(Debug)]
19#[derive(miette::Diagnostic)]
20#[derive(thiserror::Error)]
21pub enum ParseErrors<E>
22where
23    E: std::fmt::Debug + miette::Diagnostic,
24{
25    #[error("Could not open '{path}'")]
26    Open {
27        path: Utf8PathBuf,
28
29        #[source]
30        error: std::io::Error,
31    },
32    #[error("Could not parse XML")]
33    Xml {
34        #[source_code]
35        src: NamedSource<String>,
36
37        #[diagnostic_source]
38        error: XmlError,
39    },
40    #[error("Could not parse")]
41    Parse {
42        #[source_code]
43        src: NamedSource<String>,
44
45        #[related]
46        errors: Vec<E>,
47    },
48}
49
50impl<E> ParseErrors<E>
51where
52    E: miette::Diagnostic,
53{
54    pub (crate) fn open(error: std::io::Error, path: &Utf8Path) -> Self {
55        ParseErrors::Open {
56            path: path.into(),
57            error,
58        }
59    }
60
61    pub (crate) fn xml(error: roxmltree::Error, path: &Utf8Path, source: String) -> Self {
62        let error = XmlError::from(error, &source);
63
64        ParseErrors::Xml {
65            src: NamedSource::new(path, source),
66            error,
67        }
68    }
69
70    pub (crate) fn parse(path: &Utf8Path, source: String, errors: Vec<E>) -> Self {
71        ParseErrors::Parse {
72            src: NamedSource::new(path, source),
73
74            errors,
75        }
76    }
77}
78
79#[derive(Debug)]
80#[derive(miette::Diagnostic)]
81#[derive(thiserror::Error)]
82pub enum XmlError {
83    #[error("Duplicate attribute")]
84    DuplicatedAttribute {
85        #[label]
86        span: SourceSpan,
87    },
88    #[error("Unexpected close tag")]
89    UnexpectedCloseTag {
90        #[label("expected </{expected}>")]
91        span: SourceSpan,
92
93        expected: String,
94    },
95    #[error("{0}")]
96    Other(String),
97}
98
99impl XmlError {
100    fn from(error: roxmltree::Error, source: &str) -> Self {
101        match error {
102            roxmltree::Error::DuplicatedAttribute(_name, position) =>
103                XmlError::DuplicatedAttribute {
104                    span: from_text_pos(position, source),
105                },
106
107            roxmltree::Error::UnexpectedCloseTag(expected, _actual, position) =>
108                XmlError::UnexpectedCloseTag {
109                    span: from_text_pos(position, source),
110                    expected,
111                },
112
113            _ =>
114                XmlError::Other(error.to_string()),
115        }
116    }
117}
118
119fn from_text_pos(position: TextPos, source: &str) -> SourceSpan {
120    SourceOffset::from_location(
121        &source,
122        position.row as usize,
123        position.col as usize,
124    ).into()
125}
126
127#[derive(Debug)]
128#[derive(miette::Diagnostic)]
129#[derive(thiserror::Error)]
130pub enum ParseError {
131    #[error("Expecting one of 'yes', 'no', 'true' or 'false'")]
132    BooleanInvalid {
133        #[label]
134        value: SourceSpan,
135    },
136    #[error("Duplicate <{attribute}='...'>")]
137    DuplicateAttribute {
138        attribute: String,
139
140        #[label(primary, "duplicate")]
141        duplicate: SourceSpan,
142
143        #[label("existing")]
144        existing: SourceSpan,
145    },
146    #[error("Duplicate <{element}>")]
147    DuplicateElement {
148        element: String,
149
150        #[label(primary, "duplicate")]
151        duplicate: SourceSpan,
152
153        #[label("existing")]
154        existing: SourceSpan,
155    },
156    #[error("{message}")]
157    EnumValue {
158        message: String,
159        #[label]
160        value: SourceSpan,
161    },
162    // #[error("Identifier too long")]
163    // IdentifierTooLong {
164    //     #[label]
165    //     parent: SourceSpan,
166    // },
167    #[error("Invalid length")]
168    LengthInvalid {
169        #[label]
170        value: SourceSpan,
171    },
172    #[error("Length must be between {min} and {max}")]
173    LengthRange {
174        min: usize,
175        max: usize,
176        #[label]
177        value: SourceSpan,
178    },
179    #[error("Missing <{element} {attribute}='...'>")]
180    MissingAttribute {
181        element: String,
182        attribute: String,
183
184        #[label]
185        parent: SourceSpan,
186    },
187    #[error("Missing <{element}>")]
188    MissingElement {
189        element: String,
190
191        #[label]
192        parent: SourceSpan,
193    },
194    #[error("Invalid number")]
195    NumberValue {
196        #[label]
197        value: SourceSpan,
198    },
199    #[error("Empty string")]
200    StringEmpty {
201        #[label]
202        value: SourceSpan,
203    },
204    #[error("Unexpected attribute")]
205    UnexpectedAttribute {
206        #[label]
207        unexpected: SourceSpan,
208    },
209    #[error("Unexpected element")]
210    UnexpectedElement {
211        #[label]
212        unexpected: SourceSpan,
213    },
214    #[error("Must be 'yes'")]
215    YesAttribute {
216        #[label]
217        value: SourceSpan,
218    },
219}