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