1use std::str::Utf8Error;
2
3use miette::{Diagnostic, NamedSource, Report, Result, SourceSpan};
4use quick_xml::{escape::EscapeError, events::attributes::AttrError};
5use thiserror::Error;
6
7#[derive(Debug, Error, Diagnostic)]
8#[error("Error parsing SVG!")]
9#[diagnostic()]
10pub struct SVGErrors {
11 #[source_code]
12 src: NamedSource<String>,
13 #[related]
14 errors: Vec<SVGError>,
15}
16
17#[derive(Debug)]
18pub enum ElementParseError {
19 Attribute(AttributeParseError),
20 Name(Utf8Error),
21}
22
23#[derive(Debug)]
24pub enum AttributeParseError {
25 AttrError(AttrError),
26 Utf8Error(Utf8Error),
27}
28
29impl SVGErrors {
30 pub fn from_errors(src: NamedSource<String>, errors: Vec<SVGError>) -> Self {
32 Self { src, errors }
33 }
34
35 pub fn emit(self) -> Result<()> {
40 if self.errors.is_empty() {
41 return Ok(());
42 }
43 if self.errors.len() == 1 {
44 match self.errors.first() {
45 Some(e) => e.clone().emit(self.src),
46 None => Ok(()),
47 }
48 } else {
49 Err(self.into())
50 }
51 }
52}
53
54#[derive(Debug, PartialEq, Clone, Diagnostic, Error)]
55#[error("{label}")]
56#[diagnostic()]
57pub struct SVGError {
58 label: String,
59 #[label]
60 span: Option<SourceSpan>,
61 #[help]
62 advice: Option<String>,
63 #[label("Caused by this")]
64 cause: Option<SourceSpan>,
65}
66
67impl SVGError {
68 pub fn new(label: &str, span: Option<SourceSpan>) -> Self {
70 SVGError {
71 label: label.into(),
72 span,
73 advice: None,
74 cause: None,
75 }
76 }
77
78 pub fn with_advice(self, advice: &str) -> Self {
80 Self {
81 advice: Some(advice.into()),
82 ..self
83 }
84 }
85
86 pub fn with_cause(self, cause: SourceSpan) -> Self {
88 Self {
89 cause: Some(cause),
90 ..self
91 }
92 }
93
94 pub fn emit(self, src: NamedSource<String>) -> Result<()> {
99 let report: Report = self.into();
100 Err(report.with_source_code(src))
101 }
102}
103
104impl From<(quick_xml::Error, usize)> for SVGError {
105 fn from(value: (quick_xml::Error, usize)) -> Self {
107 use quick_xml::Error::{
108 EmptyDocType, EndEventMismatch, EscapeError, InvalidAttr, InvalidPrefixBind, Io,
109 NonDecodable, TextNotFound, UnexpectedBang, UnexpectedEof, UnexpectedToken,
110 UnknownPrefix, XmlDeclWithoutVersion,
111 };
112
113 let (error, position) = value;
114 dbg!(&error, &position);
115 let position: SourceSpan = position.into();
116 match error {
117 EmptyDocType => SVGError::new("The doctype has no content", Some(position)),
118 EndEventMismatch { expected, found } => SVGError::new(
119 &format!("Expected to find closing tag for {expected}, but found {found} instead"),
120 Some(position),
121 ),
122 EscapeError(error) => ( error, value.1 ).into(),
123 InvalidAttr(error) => ( error, value.1 ).into(),
124 InvalidPrefixBind { prefix, namespace } => {
125 let prefix = String::from_utf8_lossy(&prefix);
126 let namespace = String::from_utf8_lossy(&namespace);
127 SVGError::new(&format!(r#"Cannot bind "{namespace}" to "{prefix}""#), Some(position))
128 },
129 Io(error) => SVGError::new(&error.kind().to_string(), None),
130 NonDecodable(error) => match error {
131 Some(error) => SVGError::new(&error.to_string(), Some(position)),
132 None => SVGError::new("Couldn't decode file format for some reason.", None),
133 },
134 TextNotFound => SVGError::new(
135 "Expected text here but found something else",
136 Some(position),
137 ),
138 UnexpectedBang(_) => SVGError::new(r#"Unexpected "!" here"#, Some(position)),
139 UnexpectedEof(error) | UnexpectedToken(error) => SVGError::new(&error, Some(position)),
140 UnknownPrefix(error) => SVGError::new(&format!(r#"The namespace prefix "{}" is unknown"#, String::from_utf8_lossy(&error)), Some(position)),
141 XmlDeclWithoutVersion(error) => {
142 match error {
143 Some(attribute) => SVGError::new(&format!("Expected xml declaration to start with a `version` attribute, but found {attribute} instead"), Some(position)),
144 None => SVGError::new("Expected xml declaration to start with a `version attribute`", Some(position))
145 }
146 }
147 }
148 }
149}
150
151impl From<(AttrError, usize)> for SVGError {
152 fn from(value: (AttrError, usize)) -> Self {
153 use AttrError::{Duplicated, ExpectedEq, ExpectedQuote, ExpectedValue, UnquotedValue};
154
155 let (error, error_position) = value;
156 match error {
157 Duplicated(position, other_position) => SVGError::new(
158 "Found duplicate attributes",
159 Some((error_position..position).into()),
160 )
161 .with_cause(other_position.into()),
162 ExpectedEq(position) => {
163 dbg!(&error, &error_position);
164 SVGError::new("Expected an `=` for this attribute", Some(position.into()))
165 }
166 ExpectedQuote(position, char) => {
167 let char = &[char];
168 let char = String::from_utf8_lossy(char);
169 SVGError::new(
170 &format!(r#"Expected a quote (`'` or `"`), but found {char} instead"#),
171 Some(position.into()),
172 )
173 }
174 ExpectedValue(position) => {
175 if error_position == position {
176 SVGError::new("Expected a value after `=`", Some(position.into()))
177 } else {
178 SVGError::new(
179 "Expected a value directly after `=`, but found something else instead",
180 Some((error_position..position).into()),
181 )
182 }
183 }
184 UnquotedValue(position) => SVGError::new(
185 "Expected quotes around value",
186 Some((error_position..position).into()),
187 ),
188 }
189 }
190}
191
192impl From<(EscapeError, usize)> for SVGError {
193 fn from(value: (EscapeError, usize)) -> Self {
194 use EscapeError::{
195 EntityWithNull, InvalidCodepoint, InvalidDecimal, InvalidHexadecimal, TooLongDecimal,
196 TooLongHexadecimal, UnrecognizedSymbol, UnterminatedEntity,
197 };
198
199 let (error, position) = value;
200 match error {
201 EntityWithNull(range) => {
202 SVGError::new("Entity is a null character", Some(range.into()))
203 }
204 InvalidCodepoint(point) => SVGError::new(
205 &format!("{point} is not a valid unicode codepoint"),
206 Some(position.into()),
207 ),
208 InvalidDecimal(char) => SVGError::new(
209 &format!("Found invalid decimal character `{char}`"),
210 Some(position.into()),
211 ),
212 InvalidHexadecimal(char) => SVGError::new(
213 &format!("Found invalid hex character `{char}`"),
214 Some(position.into()),
215 ),
216 TooLongDecimal => SVGError::new("Decimal entity is too long", Some(position.into())),
217 TooLongHexadecimal => SVGError::new("Hex entity is too long", Some(position.into())),
218 UnrecognizedSymbol(range, symbol) => SVGError::new(
219 &format!("{symbol} is not a recognised symbol"),
220 Some(range.into()),
221 ),
222 UnterminatedEntity(range) => {
223 SVGError::new("Cannot find `;` after `&`", Some(range.into()))
224 }
225 }
226 }
227}
228
229impl From<(ElementParseError, usize)> for SVGError {
230 fn from(value: (ElementParseError, usize)) -> Self {
231 let (error, position) = value;
232 match error {
233 ElementParseError::Name(error)
234 | ElementParseError::Attribute(AttributeParseError::Utf8Error(error)) => {
235 SVGError::new(&error.to_string(), Some(position.into()))
236 }
237 ElementParseError::Attribute(AttributeParseError::AttrError(error)) => {
238 (error, position).into()
239 }
240 }
241 }
242}