Skip to main content

dbt_yaml/
error.rs

1use crate::libyaml::{emitter, error as libyaml};
2use crate::path::Path;
3use crate::{Marker, Span};
4use serde::{de, ser};
5use std::error::Error as StdError;
6use std::fmt::{self, Debug, Display};
7use std::io;
8use std::result;
9use std::string;
10use std::sync::Arc;
11
12/// An error that happened serializing or deserializing YAML data.
13pub struct Error(Box<ErrorImpl>);
14
15/// Alias for a `Result` with the error type `dbt_yaml::Error`.
16pub type Result<T> = result::Result<T, Error>;
17
18#[derive(Debug)]
19pub(crate) enum ErrorImpl {
20    Message(String, Option<Pos>),
21
22    Libyaml(libyaml::Error),
23    Io(io::Error),
24    FromUtf8(string::FromUtf8Error),
25
26    EndOfStream,
27    MoreThanOneDocument,
28    RecursionLimitExceeded(Marker),
29    RepetitionLimitExceeded,
30    BytesUnsupported,
31    UnknownAnchor(Marker),
32    SerializeNestedEnum,
33    ScalarInMerge,
34    TaggedInMerge,
35    ScalarInMergeElement,
36    SequenceInMergeElement,
37    EmptyTag,
38    FailedToParseNumber,
39    FlattenNotMapping,
40
41    External(Box<dyn StdError + 'static + Send + Sync>),
42
43    Shared(Arc<ErrorImpl>),
44}
45
46#[derive(Debug)]
47pub(crate) struct Pos {
48    span: Span,
49    path: String,
50}
51
52impl Error {
53    /// Returns the Location from the error if one exists.
54    ///
55    /// Not all types of errors have a location so this can return `None`.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// # use dbt_yaml::{Value, Error};
61    /// #
62    /// // The `@` character as the first character makes this invalid yaml
63    /// let invalid_yaml: Result<Value, Error> = dbt_yaml::from_str("@invalid_yaml");
64    ///
65    /// let location = invalid_yaml.unwrap_err().location().unwrap();
66    ///
67    /// assert_eq!(location.line(), 1);
68    /// assert_eq!(location.column(), 1);
69    /// ```
70    pub fn location(&self) -> Option<Marker> {
71        self.0.location()
72    }
73
74    /// Returns the Span from the error if one exists.    
75    ///
76    /// Not all types of errors have a span so this can return `None`.
77    pub fn span(&self) -> Option<Span> {
78        self.0.span()
79    }
80
81    /// Unwraps the error and returns the underlying error if it is an external
82    /// error; otherwise returns `None`.
83    pub fn into_external(self) -> Option<Box<dyn StdError + 'static + Send + Sync>> {
84        if let ErrorImpl::External(err) = *self.0 {
85            Some(err)
86        } else {
87            None
88        }
89    }
90
91    /// Returns the error message without the location information.
92    pub fn display_no_mark(&self) -> impl Display + use<'_> {
93        struct MessageNoMark<'a>(&'a ErrorImpl);
94        impl Display for MessageNoMark<'_> {
95            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96                match &self.0 {
97                    ErrorImpl::Libyaml(err) => Display::fmt(err, f),
98                    ErrorImpl::Shared(err) => err.display(f),
99                    _ => self.0.message_no_mark(f),
100                }
101            }
102        }
103        MessageNoMark(&self.0)
104    }
105}
106
107pub(crate) fn new(inner: ErrorImpl) -> Error {
108    Error(Box::new(inner))
109}
110
111pub(crate) fn shared(shared: Arc<ErrorImpl>) -> Error {
112    Error(Box::new(ErrorImpl::Shared(shared)))
113}
114
115pub(crate) fn fix_mark(mut error: Error, mark: libyaml::Mark, path: Path) -> Error {
116    if let ErrorImpl::Message(_, none @ None) = error.0.as_mut() {
117        let span = Span::from(Marker::from(mark));
118
119        #[cfg(feature = "filename")]
120        let span = span.maybe_capture_filename();
121
122        *none = Some(Pos {
123            span,
124            path: path.to_string(),
125        });
126    }
127    error
128}
129
130pub(crate) fn set_span(mut error: Error, span: Span) -> Error {
131    if let ErrorImpl::Message(_, pos) = error.0.as_mut() {
132        if let Some(pos) = pos {
133            if !pos.span.is_valid() {
134                pos.span = span;
135            }
136        } else {
137            *pos = Some(Pos {
138                span,
139                path: ".".to_string(),
140            })
141        }
142    }
143    error
144}
145
146impl Error {
147    pub(crate) fn shared(self) -> Arc<ErrorImpl> {
148        if let ErrorImpl::Shared(err) = *self.0 {
149            err
150        } else {
151            Arc::from(self.0)
152        }
153    }
154}
155
156impl From<libyaml::Error> for Error {
157    fn from(err: libyaml::Error) -> Self {
158        Error(Box::new(ErrorImpl::Libyaml(err)))
159    }
160}
161
162impl From<emitter::Error> for Error {
163    fn from(err: emitter::Error) -> Self {
164        match err {
165            emitter::Error::Libyaml(err) => Self::from(err),
166            emitter::Error::Io(err) => new(ErrorImpl::Io(err)),
167        }
168    }
169}
170
171impl From<Box<dyn StdError + 'static + Send + Sync>> for Error {
172    fn from(err: Box<dyn StdError + 'static + Send + Sync>) -> Self {
173        Error(Box::new(ErrorImpl::External(err)))
174    }
175}
176
177impl StdError for Error {
178    fn source(&self) -> Option<&(dyn StdError + 'static)> {
179        self.0.source()
180    }
181}
182
183impl Display for Error {
184    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185        self.0.display(f)
186    }
187}
188
189// Remove two layers of verbosity from the debug representation. Humans often
190// end up seeing this representation because it is what unwrap() shows.
191impl Debug for Error {
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        self.0.debug(f)
194    }
195}
196
197impl ser::Error for Error {
198    fn custom<T: Display>(msg: T) -> Self {
199        Error(Box::new(ErrorImpl::Message(msg.to_string(), None)))
200    }
201}
202
203impl de::Error for Error {
204    fn custom<T: Display>(msg: T) -> Self {
205        Error(Box::new(ErrorImpl::Message(msg.to_string(), None)))
206    }
207}
208
209impl ErrorImpl {
210    fn location(&self) -> Option<Marker> {
211        self.span().map(|span| span.start)
212    }
213
214    fn source(&self) -> Option<&(dyn StdError + 'static)> {
215        match self {
216            ErrorImpl::Io(err) => err.source(),
217            ErrorImpl::FromUtf8(err) => err.source(),
218            ErrorImpl::Shared(err) => err.source(),
219            ErrorImpl::External(err) => err.source(),
220            _ => None,
221        }
222    }
223
224    fn span(&self) -> Option<Span> {
225        match self {
226            ErrorImpl::Message(_, Some(Pos { span, path: _ })) => Some(span.clone()),
227            ErrorImpl::RecursionLimitExceeded(mark) | ErrorImpl::UnknownAnchor(mark) => {
228                Some(Span::from(*mark))
229            }
230            ErrorImpl::Libyaml(err) => Some(Marker::from(err.mark()).into()),
231            ErrorImpl::Shared(err) => err.span(),
232            _ => None,
233        }
234    }
235
236    fn message_no_mark(&self, f: &mut fmt::Formatter) -> fmt::Result {
237        match self {
238            ErrorImpl::Message(msg, None) => f.write_str(msg),
239            ErrorImpl::Message(msg, Some(Pos { span: _, path })) => {
240                if path != "." {
241                    write!(f, "{}: ", path)?;
242                }
243                f.write_str(msg)
244            }
245            ErrorImpl::Libyaml(_) => unreachable!(),
246            ErrorImpl::Io(err) => Display::fmt(err, f),
247            ErrorImpl::FromUtf8(err) => Display::fmt(err, f),
248            ErrorImpl::EndOfStream => f.write_str("EOF while parsing a value"),
249            ErrorImpl::MoreThanOneDocument => f.write_str(
250                "deserializing from YAML containing more than one document is not supported",
251            ),
252            ErrorImpl::RecursionLimitExceeded(_mark) => f.write_str("recursion limit exceeded"),
253            ErrorImpl::RepetitionLimitExceeded => f.write_str("repetition limit exceeded"),
254            ErrorImpl::BytesUnsupported => {
255                f.write_str("serialization and deserialization of bytes in YAML is not implemented")
256            }
257            ErrorImpl::UnknownAnchor(_mark) => f.write_str("unknown anchor"),
258            ErrorImpl::SerializeNestedEnum => {
259                f.write_str("serializing nested enums in YAML is not supported yet")
260            }
261            ErrorImpl::ScalarInMerge => {
262                f.write_str("expected a mapping or list of mappings for merging, but found scalar")
263            }
264            ErrorImpl::TaggedInMerge => f.write_str("unexpected tagged value in merge"),
265            ErrorImpl::ScalarInMergeElement => {
266                f.write_str("expected a mapping for merging, but found scalar")
267            }
268            ErrorImpl::SequenceInMergeElement => {
269                f.write_str("expected a mapping for merging, but found sequence")
270            }
271            ErrorImpl::EmptyTag => f.write_str("empty YAML tag is not allowed"),
272            ErrorImpl::FailedToParseNumber => f.write_str("failed to parse YAML number"),
273            ErrorImpl::External(err) => Display::fmt(err.as_ref(), f),
274            ErrorImpl::Shared(_) => unreachable!(),
275            ErrorImpl::FlattenNotMapping => write!(f, "expected the flatten field to be a mapping"),
276        }
277    }
278
279    fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
280        match self {
281            ErrorImpl::Libyaml(err) => Display::fmt(err, f),
282            ErrorImpl::Shared(err) => err.display(f),
283            _ => {
284                self.message_no_mark(f)?;
285                if let Some(mark) = self.location() {
286                    if mark.line() != 0 || mark.column() != 0 {
287                        write!(f, " at {}", mark)?;
288                    }
289                }
290                Ok(())
291            }
292        }
293    }
294
295    fn debug(&self, f: &mut fmt::Formatter) -> fmt::Result {
296        match self {
297            ErrorImpl::Libyaml(err) => Debug::fmt(err, f),
298            ErrorImpl::Shared(err) => err.debug(f),
299            _ => {
300                f.write_str("Error(")?;
301                struct MessageNoMark<'a>(&'a ErrorImpl);
302                impl Display for MessageNoMark<'_> {
303                    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
304                        self.0.message_no_mark(f)
305                    }
306                }
307                let msg = MessageNoMark(self).to_string();
308                Debug::fmt(&msg, f)?;
309                if let Some(mark) = self.location() {
310                    write!(f, ", line: {}, column: {}", mark.line(), mark.column(),)?;
311                }
312                f.write_str(")")
313            }
314        }
315    }
316}