tanzim_value/error.rs
1use crate::Location;
2use std::fmt::{self, Display, Formatter};
3
4/// Error while deserializing configuration input.
5///
6/// [`Display`] is one line by default; use `{error:#}` for source context and caret.
7///
8/// [`Location`] is boxed so the whole [`Error`] stays small enough to return by value without
9/// tripping `clippy::result_large_err` (a [`Location`] now carries the full originating
10/// [`tanzim_source::Source`]).
11#[derive(Debug, Clone, PartialEq)]
12pub enum Error {
13 /// Configuration input was not valid UTF-8.
14 InvalidUtf8 {
15 /// Where the invalid input came from.
16 location: Box<Location>,
17 },
18 /// A parsed value's type is not supported in this context.
19 UnsupportedType {
20 /// Where the unsupported value came from.
21 location: Box<Location>,
22 /// Name of the offending type.
23 found: &'static str,
24 },
25 /// A source failed to parse into a [`crate::Value`] tree.
26 Parse {
27 /// Where the parse failure occurred, if known.
28 location: Option<Box<Location>>,
29 /// Human-readable description of the failure.
30 message: String,
31 },
32 /// A value could not be deserialized into the requested type (`serde` Cargo feature).
33 ///
34 /// The offending node's [`Location`] is stamped on by the deserializer (see
35 /// `Error::or_location`); because every parsed node's `Location` already carries its
36 /// pre-rendered source [`snippet`](Location::snippet), `{error:#}` renders a caret underline
37 /// without any post-hoc source lookup.
38 #[cfg(feature = "serde")]
39 Deserialize {
40 /// Human-readable description of the failure.
41 message: String,
42 /// Where the failure occurred, if known.
43 location: Option<Box<Location>>,
44 },
45}
46
47impl Error {
48 /// Stamp `location` onto a [`Error::Deserialize`] that has none yet, so errors bubbling up from
49 /// a leaf get the nearest enclosing node's position (and its pre-rendered snippet). Errors that
50 /// already carry a location (or are not deserialize errors) are returned unchanged.
51 #[cfg(feature = "serde")]
52 pub(crate) fn or_location(mut self, location: &Location) -> Self {
53 if let Self::Deserialize {
54 location: slot @ None,
55 ..
56 } = &mut self
57 {
58 *slot = Some(Box::new(location.clone()));
59 }
60 self
61 }
62}
63
64fn located_message(location: &Location, message: &str) -> String {
65 format!("{message} at {location}")
66}
67
68impl Display for Error {
69 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70 match self {
71 Self::InvalidUtf8 { location } => {
72 write!(f, "invalid utf-8 in configuration input from {location}")?;
73 }
74 Self::UnsupportedType { location, found } => {
75 write!(
76 f,
77 "{}",
78 located_message(
79 location,
80 &format!("unsupported configuration input type `{found}`"),
81 )
82 )?;
83 }
84 Self::Parse {
85 location: Some(location),
86 message,
87 } => write!(f, "{}", located_message(location, message))?,
88 Self::Parse { message, .. } => write!(f, "{message}")?,
89 #[cfg(feature = "serde")]
90 Self::Deserialize {
91 message,
92 location: Some(location),
93 } => write!(
94 f,
95 "{}",
96 located_message(
97 location,
98 &format!("failed to deserialize configuration: {message}"),
99 )
100 )?,
101 #[cfg(feature = "serde")]
102 Self::Deserialize { message, .. } => {
103 write!(f, "failed to deserialize configuration: {message}")?
104 }
105 }
106
107 // Alternate form appends the offending value's pre-rendered source snippet (gutter + caret),
108 // computed once at parse time and stored on the `Location` — nothing is recomputed here.
109 if f.alternate() {
110 let location = match self {
111 Self::InvalidUtf8 { location } | Self::UnsupportedType { location, .. } => {
112 Some(location.as_ref())
113 }
114 Self::Parse {
115 location: Some(location),
116 ..
117 } => Some(location.as_ref()),
118 #[cfg(feature = "serde")]
119 Self::Deserialize {
120 location: Some(location),
121 ..
122 } => Some(location.as_ref()),
123 _ => None,
124 };
125 if let Some(location) = location
126 && !location.snippet.is_empty()
127 {
128 write!(f, "\n{}", location.snippet)?;
129 }
130 }
131
132 Ok(())
133 }
134}
135
136impl std::error::Error for Error {}