Skip to main content

yaml_rt_serde/
error.rs

1use std::{fmt, io};
2
3use yaml_rt_core::{LineCol, Span, YamlDoc, YamlError};
4
5/// Result type used by this crate.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Location of a deserialization error in the YAML input.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Location {
11    index: usize,
12    line: usize,
13    column: usize,
14}
15
16impl Location {
17    /// Zero-based byte index.
18    #[must_use]
19    pub const fn index(&self) -> usize {
20        self.index
21    }
22
23    /// One-based line number.
24    #[must_use]
25    pub const fn line(&self) -> usize {
26        self.line
27    }
28
29    /// One-based byte column.
30    #[must_use]
31    pub const fn column(&self) -> usize {
32        self.column
33    }
34}
35
36/// Error produced while parsing, deserializing, serializing, or writing YAML.
37#[derive(Debug)]
38pub struct Error {
39    pub(crate) message: String,
40    pub(crate) path: Option<String>,
41    pub(crate) location: Option<Location>,
42    source: Option<Box<dyn std::error::Error + Send + Sync>>,
43}
44
45impl Error {
46    pub(crate) fn message(message: impl Into<String>) -> Self {
47        Self {
48            message: message.into(),
49            path: None,
50            location: None,
51            source: None,
52        }
53    }
54
55    pub(crate) fn at(mut self, doc: &YamlDoc, span: Span) -> Self {
56        if self.location.is_none() {
57            let LineCol { line, column } = doc.source().line_col(span.start as usize);
58            self.location = Some(Location {
59                index: span.start as usize,
60                line,
61                column,
62            });
63        }
64        self
65    }
66
67    pub(crate) fn with_path(mut self, path: &str) -> Self {
68        if path != "." && self.path.is_none() {
69            self.path = Some(path.to_owned());
70        }
71        self
72    }
73
74    pub(crate) fn io(error: io::Error) -> Self {
75        Self {
76            message: error.to_string(),
77            path: None,
78            location: None,
79            source: Some(Box::new(error)),
80        }
81    }
82
83    /// Returns the input location associated with this error, when available.
84    #[must_use]
85    pub const fn location(&self) -> Option<Location> {
86        self.location
87    }
88}
89
90impl Clone for Error {
91    fn clone(&self) -> Self {
92        Self {
93            message: self.message.clone(),
94            path: self.path.clone(),
95            location: self.location,
96            source: None,
97        }
98    }
99}
100
101impl From<YamlError> for Error {
102    fn from(error: YamlError) -> Self {
103        let location = error.diagnostic.position.map(|position| Location {
104            index: error.diagnostic.span.start as usize,
105            line: position.line,
106            column: position.column,
107        });
108        Self {
109            message: error.to_string(),
110            path: None,
111            location,
112            source: Some(Box::new(error)),
113        }
114    }
115}
116
117impl From<io::Error> for Error {
118    fn from(error: io::Error) -> Self {
119        Self::io(error)
120    }
121}
122
123impl fmt::Display for Error {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        if let Some(path) = &self.path {
126            write!(formatter, "{path}: ")?;
127        }
128        formatter.write_str(&self.message)?;
129        if let Some(location) = self.location {
130            write!(
131                formatter,
132                " at line {} column {}",
133                location.line, location.column
134            )?;
135        }
136        Ok(())
137    }
138}
139
140impl std::error::Error for Error {
141    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
142        self.source
143            .as_deref()
144            .map(|source| source as &(dyn std::error::Error + 'static))
145    }
146}
147
148impl serde::de::Error for Error {
149    fn custom<T>(message: T) -> Self
150    where
151        T: fmt::Display,
152    {
153        Self::message(message.to_string())
154    }
155}
156
157impl serde::ser::Error for Error {
158    fn custom<T>(message: T) -> Self
159    where
160        T: fmt::Display,
161    {
162        Self::message(message.to_string())
163    }
164}