smart_config/
error.rs

1//! Config deserialization errors.
2
3use std::{fmt, sync::Arc};
4
5use serde::{de, de::Error};
6
7use crate::{
8    metadata::{ConfigMetadata, ParamMetadata},
9    value::{ValueOrigin, WithOrigin},
10};
11
12/// Marker error for [`DeserializeConfig`](crate::DeserializeConfig) operations. The error info os stored
13/// in [`DeserializeContext`](crate::de::DeserializeContext) as [`ParseErrors`].
14#[derive(Debug)]
15pub struct DeserializeConfigError(());
16
17impl DeserializeConfigError {
18    pub(crate) fn new() -> Self {
19        Self(())
20    }
21}
22
23#[derive(Debug, Clone, Copy)]
24pub(crate) enum LocationInConfig {
25    Param(usize),
26}
27
28#[doc(hidden)] // variants not stabilized yet
29#[derive(Debug, Clone, Copy)]
30#[non_exhaustive]
31pub enum ParseErrorCategory {
32    /// Generic error.
33    Generic,
34    /// Missing field (parameter / config) error.
35    MissingField,
36}
37
38/// Low-level deserialization error.
39#[derive(Debug)]
40#[non_exhaustive]
41pub enum LowLevelError {
42    /// Error coming from JSON deserialization logic.
43    Json {
44        err: serde_json::Error,
45        category: ParseErrorCategory,
46    },
47    #[doc(hidden)] // implementation detail
48    InvalidArray,
49    #[doc(hidden)] // implementation detail
50    InvalidObject,
51    #[doc(hidden)] // implementation detail
52    Validation,
53}
54
55impl From<serde_json::Error> for LowLevelError {
56    fn from(err: serde_json::Error) -> Self {
57        Self::Json {
58            err,
59            category: ParseErrorCategory::Generic,
60        }
61    }
62}
63
64impl fmt::Display for LowLevelError {
65    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Self::Json { err, .. } => fmt::Display::fmt(err, formatter),
68            Self::InvalidArray => formatter.write_str("error(s) deserializing array items"),
69            Self::InvalidObject => formatter.write_str("error(s) deserializing object entries"),
70            Self::Validation => formatter.write_str("validation failed"),
71        }
72    }
73}
74
75/// Error together with its origin.
76pub type ErrorWithOrigin = WithOrigin<LowLevelError>;
77
78impl ErrorWithOrigin {
79    pub(crate) fn json(err: serde_json::Error, origin: Arc<ValueOrigin>) -> Self {
80        Self::new(err.into(), origin)
81    }
82
83    /// Creates a custom error.
84    pub fn custom(message: impl fmt::Display) -> Self {
85        Self::json(de::Error::custom(message), Arc::default())
86    }
87}
88
89impl de::Error for ErrorWithOrigin {
90    fn custom<T: fmt::Display>(msg: T) -> Self {
91        Self::json(de::Error::custom(msg), Arc::default())
92    }
93
94    fn missing_field(field: &'static str) -> Self {
95        let err = LowLevelError::Json {
96            err: de::Error::missing_field(field),
97            category: ParseErrorCategory::MissingField,
98        };
99        Self::new(err, Arc::default())
100    }
101}
102
103impl fmt::Display for ErrorWithOrigin {
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(formatter, "[{}]: {}", self.origin, self.inner)
106    }
107}
108
109impl std::error::Error for ErrorWithOrigin {
110    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111        match &self.inner {
112            LowLevelError::Json { err, .. } => Some(err),
113            LowLevelError::InvalidArray
114            | LowLevelError::InvalidObject
115            | LowLevelError::Validation => None,
116        }
117    }
118}
119
120/// Config parameter deserialization errors.
121pub struct ParseError {
122    pub(crate) inner: serde_json::Error,
123    pub(crate) category: ParseErrorCategory,
124    pub(crate) path: String,
125    pub(crate) origin: Arc<ValueOrigin>,
126    pub(crate) config: &'static ConfigMetadata,
127    pub(crate) location_in_config: Option<LocationInConfig>,
128    pub(crate) validation: Option<String>,
129}
130
131impl fmt::Debug for ParseError {
132    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133        formatter
134            .debug_struct("ParseError")
135            .field("inner", &self.inner)
136            .field("origin", &self.origin)
137            .field("path", &self.path)
138            .field("config.ty", &self.config.ty)
139            .field("location_in_config", &self.location_in_config)
140            .field("validation", &self.validation)
141            .finish_non_exhaustive()
142    }
143}
144
145impl fmt::Display for ParseError {
146    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147        let field = self.location_in_config.and_then(|location| {
148            Some(match location {
149                LocationInConfig::Param(idx) => {
150                    let param = self.config.params.get(idx)?;
151                    format!("param `{}` in ", param.name)
152                }
153            })
154        });
155        let field = field.as_deref().unwrap_or("");
156
157        let origin = if matches!(self.origin(), ValueOrigin::Unknown) {
158            String::new()
159        } else {
160            format!(" [origin: {}]", self.origin)
161        };
162
163        let failed_action = if let Some(validation) = &self.validation {
164            format!("validating '{validation}' for")
165        } else {
166            "parsing".to_owned()
167        };
168
169        write!(
170            formatter,
171            "error {failed_action} {field}`{config}` at `{path}`{origin}: {err}",
172            err = self.inner,
173            config = self.config.ty.name_in_code(),
174            path = self.path
175        )
176    }
177}
178
179impl std::error::Error for ParseError {
180    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
181        Some(&self.inner)
182    }
183}
184
185impl ParseError {
186    pub(crate) fn generic(path: String, config: &'static ConfigMetadata) -> Self {
187        Self {
188            inner: serde_json::Error::custom("unspecified error deserializing configuration"),
189            category: ParseErrorCategory::Generic,
190            path,
191            origin: Arc::default(),
192            config,
193            location_in_config: None,
194            validation: None,
195        }
196    }
197
198    /// Returns the wrapped error.
199    pub fn inner(&self) -> &serde_json::Error {
200        &self.inner
201    }
202
203    #[doc(hidden)]
204    pub fn category(&self) -> ParseErrorCategory {
205        self.category
206    }
207
208    /// Returns an absolute path on which this error has occurred.
209    pub fn path(&self) -> &str {
210        &self.path
211    }
212
213    /// Returns an origin of the value deserialization of which failed.
214    pub fn origin(&self) -> &ValueOrigin {
215        &self.origin
216    }
217
218    /// Returns human-readable description of the failed validation, if the error is caused by one.
219    pub fn validation(&self) -> Option<&str> {
220        self.validation.as_deref()
221    }
222
223    /// Returns metadata for the failing config.
224    pub fn config(&self) -> &'static ConfigMetadata {
225        self.config
226    }
227
228    /// Returns metadata for the failing parameter if this error concerns a parameter. The parameter
229    /// is guaranteed to be contained in [`Self::config()`].
230    pub fn param(&self) -> Option<&'static ParamMetadata> {
231        let LocationInConfig::Param(idx) = self.location_in_config?;
232        self.config.params.get(idx)
233    }
234}
235
236/// Collection of [`ParseError`]s returned from [`ConfigParser::parse()`](crate::ConfigParser::parse()).
237#[derive(Debug, Default)]
238pub struct ParseErrors {
239    errors: Vec<ParseError>,
240}
241
242impl ParseErrors {
243    pub(crate) fn push(&mut self, err: ParseError) {
244        self.errors.push(err);
245    }
246
247    /// Iterates over the contained errors.
248    pub fn iter(&self) -> impl Iterator<Item = &ParseError> + '_ {
249        self.errors.iter()
250    }
251
252    /// Returns the number of contained errors.
253    #[allow(clippy::len_without_is_empty)] // is_empty should always return false
254    pub fn len(&self) -> usize {
255        self.errors.len()
256    }
257
258    /// Returns a reference to the first error.
259    #[allow(clippy::missing_panics_doc)] // false positive
260    pub fn first(&self) -> &ParseError {
261        self.errors.first().expect("no errors")
262    }
263
264    pub(crate) fn truncate(&mut self, len: usize) {
265        self.errors.truncate(len);
266    }
267}
268
269impl IntoIterator for ParseErrors {
270    type Item = ParseError;
271    type IntoIter = std::vec::IntoIter<ParseError>;
272
273    fn into_iter(self) -> Self::IntoIter {
274        self.errors.into_iter()
275    }
276}
277
278impl fmt::Display for ParseErrors {
279    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
280        for err in &self.errors {
281            writeln!(formatter, "{err}")?;
282        }
283        Ok(())
284    }
285}
286
287impl std::error::Error for ParseErrors {}
288
289impl FromIterator<ParseError> for Result<(), ParseErrors> {
290    fn from_iter<I: IntoIterator<Item = ParseError>>(iter: I) -> Self {
291        let errors: Vec<_> = iter.into_iter().collect();
292        if errors.is_empty() {
293            Ok(())
294        } else {
295            Err(ParseErrors { errors })
296        }
297    }
298}