Skip to main content

xamlx/
exceptions.rs

1//! Port of `Exceptions.cs`.
2//!
3//! Upstream throws `XamlParseException` (derived from `System.Xml.XmlException`),
4//! `XamlTransformException`, `XamlLoadException` and `XamlTypeSystemException`.
5//! The port maps them to a single error enum returned through [`XamlResult`].
6
7use std::fmt;
8
9use crate::ast::IXamlLineInfo;
10
11/// Payload shared by all `XmlException`-derived errors (message plus line info).
12#[derive(Debug, Clone)]
13pub struct XamlParseException {
14    /// The message as passed to the constructor (without the line info suffix).
15    pub title: String,
16    pub line_number: i32,
17    pub line_position: i32,
18    pub document: Option<String>,
19    pub inner_exception: Option<Box<XamlError>>,
20    /// The .NET type name of a class a host derives from `XamlParseException`,
21    /// `XamlTransformException` or `XamlLoadException` (C# `exception.GetType().Name`).
22    /// `None` for the exception types of this crate. Hosts tag their derived exceptions with
23    /// [`XamlError::with_derived_type_name`] and test them with [`XamlError::is_derived_type`],
24    /// which is what a C# type pattern (`e is XamlFooException`) on the derived class does.
25    pub derived_type_name: Option<&'static str>,
26}
27
28impl XamlParseException {
29    pub fn new(
30        message: impl Into<String>,
31        line: i32,
32        position: i32,
33        inner: Option<XamlError>,
34    ) -> Self {
35        Self {
36            title: message.into(),
37            line_number: line,
38            line_position: position,
39            document: None,
40            inner_exception: inner.map(Box::new),
41            derived_type_name: None,
42        }
43    }
44
45    /// `XmlException.Message`: the user message followed by the position when it is known.
46    pub fn message(&self) -> String {
47        if self.line_number == 0 && self.line_position == 0 {
48            self.title.clone()
49        } else {
50            format!(
51                "{} Line {}, position {}.",
52                self.title, self.line_number, self.line_position
53            )
54        }
55    }
56}
57
58/// `XamlTypeSystemException` payload.
59#[derive(Debug, Clone)]
60pub struct XamlTypeSystemException {
61    pub message: String,
62    pub inner_exception: Option<Box<XamlError>>,
63}
64
65/// Any other exception type the upstream code may raise (`InvalidOperationException`,
66/// `ArgumentException`, `InvalidCastException`, ...). These never carry line info.
67#[derive(Debug, Clone)]
68pub struct XamlInternalException {
69    /// .NET exception type name, kept for diagnostics.
70    pub type_name: &'static str,
71    pub message: String,
72}
73
74#[derive(Debug, Clone)]
75pub enum XamlError {
76    /// A raw `System.Xml.XmlException` (malformed XML).
77    Xml(XamlParseException),
78    /// `XamlParseException`.
79    Parse(XamlParseException),
80    /// `XamlTransformException`.
81    Transform(XamlParseException),
82    /// `XamlLoadException`.
83    Load(XamlParseException),
84    /// `XamlTypeSystemException`.
85    TypeSystem(XamlTypeSystemException),
86    /// Any other exception.
87    Internal(XamlInternalException),
88    /// `AggregateException`.
89    Aggregate(Vec<XamlError>),
90}
91
92pub type XamlResult<T> = Result<T, XamlError>;
93
94fn li(line_info: Option<&dyn IXamlLineInfo>) -> (i32, i32) {
95    line_info
96        .map(|l| (l.line(), l.position()))
97        .unwrap_or((0, 0))
98}
99
100impl XamlError {
101    pub fn xml_exception(message: impl Into<String>, line: i32, position: i32) -> Self {
102        XamlError::Xml(XamlParseException::new(message, line, position, None))
103    }
104
105    pub fn parse_exception_at(message: impl Into<String>, line: i32, position: i32) -> Self {
106        XamlError::Parse(XamlParseException::new(message, line, position, None))
107    }
108
109    pub fn parse_exception(
110        message: impl Into<String>,
111        line_info: Option<&dyn IXamlLineInfo>,
112    ) -> Self {
113        let (l, p) = li(line_info);
114        XamlError::Parse(XamlParseException::new(message, l, p, None))
115    }
116
117    pub fn parse_exception_with_inner(
118        message: impl Into<String>,
119        line_info: Option<&dyn IXamlLineInfo>,
120        inner: XamlError,
121    ) -> Self {
122        let (l, p) = li(line_info);
123        XamlError::Parse(XamlParseException::new(message, l, p, Some(inner)))
124    }
125
126    pub fn transform_exception(
127        message: impl Into<String>,
128        line_info: Option<&dyn IXamlLineInfo>,
129    ) -> Self {
130        let (l, p) = li(line_info);
131        XamlError::Transform(XamlParseException::new(message, l, p, None))
132    }
133
134    pub fn transform_exception_with_inner(
135        message: impl Into<String>,
136        line_info: Option<&dyn IXamlLineInfo>,
137        inner: XamlError,
138    ) -> Self {
139        let (l, p) = li(line_info);
140        XamlError::Transform(XamlParseException::new(message, l, p, Some(inner)))
141    }
142
143    pub fn load_exception(
144        message: impl Into<String>,
145        line_info: Option<&dyn IXamlLineInfo>,
146    ) -> Self {
147        let (l, p) = li(line_info);
148        XamlError::Load(XamlParseException::new(message, l, p, None))
149    }
150
151    pub fn type_system_exception(message: impl Into<String>) -> Self {
152        XamlError::TypeSystem(XamlTypeSystemException {
153            message: message.into(),
154            inner_exception: None,
155        })
156    }
157
158    pub fn internal(type_name: &'static str, message: impl Into<String>) -> Self {
159        XamlError::Internal(XamlInternalException {
160            type_name,
161            message: message.into(),
162        })
163    }
164
165    pub fn invalid_operation(message: impl Into<String>) -> Self {
166        Self::internal("InvalidOperationException", message)
167    }
168
169    pub fn argument(message: impl Into<String>) -> Self {
170        Self::internal("ArgumentException", message)
171    }
172
173    pub fn invalid_cast(message: impl Into<String>) -> Self {
174        Self::internal("InvalidCastException", message)
175    }
176
177    pub fn not_supported(message: impl Into<String>) -> Self {
178        Self::internal("NotSupportedException", message)
179    }
180
181    /// `e is XmlException`.
182    pub fn is_xml_exception(&self) -> bool {
183        matches!(
184            self,
185            XamlError::Xml(_) | XamlError::Parse(_) | XamlError::Transform(_) | XamlError::Load(_)
186        )
187    }
188
189    /// `e is XamlParseException`.
190    pub fn is_xaml_parse_exception(&self) -> bool {
191        matches!(
192            self,
193            XamlError::Parse(_) | XamlError::Transform(_) | XamlError::Load(_)
194        )
195    }
196
197    pub fn is_type_system_exception(&self) -> bool {
198        matches!(self, XamlError::TypeSystem(_))
199    }
200
201    /// The `XmlException` payload, when this error derives from `XmlException`.
202    pub fn as_xml_exception(&self) -> Option<&XamlParseException> {
203        match self {
204            XamlError::Xml(e)
205            | XamlError::Parse(e)
206            | XamlError::Transform(e)
207            | XamlError::Load(e) => Some(e),
208            _ => None,
209        }
210    }
211
212    fn as_xml_exception_mut(&mut self) -> Option<&mut XamlParseException> {
213        match self {
214            XamlError::Xml(e)
215            | XamlError::Parse(e)
216            | XamlError::Transform(e)
217            | XamlError::Load(e) => Some(e),
218            _ => None,
219        }
220    }
221
222    /// Marks this error as an instance of a host class derived from the exception type it was
223    /// created as (e.g. a `XamlTransformException` subclass). No effect on errors that do not
224    /// derive from `XmlException`.
225    pub fn with_derived_type_name(mut self, type_name: &'static str) -> Self {
226        if let Some(e) = self.as_xml_exception_mut() {
227            e.derived_type_name = Some(type_name);
228        }
229        self
230    }
231
232    /// The name set by [`XamlError::with_derived_type_name`].
233    pub fn derived_type_name(&self) -> Option<&'static str> {
234        self.as_xml_exception().and_then(|e| e.derived_type_name)
235    }
236
237    /// `e is TDerived` for a host exception class named `type_name`.
238    pub fn is_derived_type(&self, type_name: &str) -> bool {
239        self.derived_type_name() == Some(type_name)
240    }
241
242    /// .NET exception type name.
243    pub fn type_name(&self) -> &'static str {
244        if let Some(derived) = self.derived_type_name() {
245            return derived;
246        }
247        match self {
248            XamlError::Xml(_) => "XmlException",
249            XamlError::Parse(_) => "XamlParseException",
250            XamlError::Transform(_) => "XamlTransformException",
251            XamlError::Load(_) => "XamlLoadException",
252            XamlError::TypeSystem(_) => "XamlTypeSystemException",
253            XamlError::Internal(e) => e.type_name,
254            XamlError::Aggregate(_) => "AggregateException",
255        }
256    }
257
258    /// `Exception.Message`.
259    pub fn message(&self) -> String {
260        match self {
261            XamlError::Xml(e)
262            | XamlError::Parse(e)
263            | XamlError::Transform(e)
264            | XamlError::Load(e) => e.message(),
265            XamlError::TypeSystem(e) => e.message.clone(),
266            XamlError::Internal(e) => e.message.clone(),
267            XamlError::Aggregate(errors) => {
268                let inner: Vec<String> = errors
269                    .iter()
270                    .map(|e| format!("({})", e.message()))
271                    .collect();
272                format!("One or more errors occurred. {}", inner.join(" "))
273            }
274        }
275    }
276
277    pub fn line_number(&self) -> Option<i32> {
278        self.as_xml_exception().map(|e| e.line_number)
279    }
280
281    pub fn line_position(&self) -> Option<i32> {
282        self.as_xml_exception().map(|e| e.line_position)
283    }
284
285    /// `XamlParseException.Document` (only present on `XamlParseException` and derived).
286    pub fn document(&self) -> Option<&str> {
287        match self {
288            XamlError::Parse(e) | XamlError::Transform(e) | XamlError::Load(e) => {
289                e.document.as_deref()
290            }
291            _ => None,
292        }
293    }
294
295    /// Object-initializer `{ Document = ... }`.
296    pub fn with_document(mut self, document: Option<String>) -> Self {
297        if let Some(e) = self.as_xml_exception_mut() {
298            e.document = document;
299        }
300        self
301    }
302
303    pub fn inner_exception(&self) -> Option<&XamlError> {
304        match self {
305            XamlError::Xml(e)
306            | XamlError::Parse(e)
307            | XamlError::Transform(e)
308            | XamlError::Load(e) => e.inner_exception.as_deref(),
309            XamlError::TypeSystem(e) => e.inner_exception.as_deref(),
310            _ => None,
311        }
312    }
313}
314
315impl fmt::Display for XamlError {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(f, "{}: {}", self.type_name(), self.message())
318    }
319}
320
321impl std::error::Error for XamlError {}