1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//! Config deserialization errors.

use std::{fmt, sync::Arc};

use serde::{de, de::Error};

use crate::{
    metadata::{ConfigMetadata, ParamMetadata},
    value::{ValueOrigin, WithOrigin},
};

/// Marker error for [`DeserializeConfig`](crate::DeserializeConfig) operations. The error info os stored
/// in [`DeserializeContext`](crate::de::DeserializeContext) as [`ParseErrors`].
#[derive(Debug)]
pub struct DeserializeConfigError(());

impl DeserializeConfigError {
    pub(crate) fn new() -> Self {
        Self(())
    }
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum LocationInConfig {
    Param(usize),
}

#[doc(hidden)] // variants not stabilized yet
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum ParseErrorCategory {
    /// Generic error.
    Generic,
    /// Missing field (parameter / config) error.
    MissingField,
}

/// Low-level deserialization error.
#[derive(Debug)]
#[non_exhaustive]
pub enum LowLevelError {
    /// Error coming from JSON deserialization logic.
    Json {
        err: serde_json::Error,
        category: ParseErrorCategory,
    },
    #[doc(hidden)] // implementation detail
    InvalidArray,
    #[doc(hidden)] // implementation detail
    InvalidObject,
    #[doc(hidden)] // implementation detail
    Validation,
}

impl From<serde_json::Error> for LowLevelError {
    fn from(err: serde_json::Error) -> Self {
        Self::Json {
            err,
            category: ParseErrorCategory::Generic,
        }
    }
}

impl fmt::Display for LowLevelError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Json { err, .. } => fmt::Display::fmt(err, formatter),
            Self::InvalidArray => formatter.write_str("error(s) deserializing array items"),
            Self::InvalidObject => formatter.write_str("error(s) deserializing object entries"),
            Self::Validation => formatter.write_str("validation failed"),
        }
    }
}

/// Error together with its origin.
pub type ErrorWithOrigin = WithOrigin<LowLevelError>;

impl ErrorWithOrigin {
    pub(crate) fn json(err: serde_json::Error, origin: Arc<ValueOrigin>) -> Self {
        Self::new(err.into(), origin)
    }

    /// Creates a custom error.
    pub fn custom(message: impl fmt::Display) -> Self {
        Self::json(de::Error::custom(message), Arc::default())
    }
}

impl de::Error for ErrorWithOrigin {
    fn custom<T: fmt::Display>(msg: T) -> Self {
        Self::json(de::Error::custom(msg), Arc::default())
    }

    fn missing_field(field: &'static str) -> Self {
        let err = LowLevelError::Json {
            err: de::Error::missing_field(field),
            category: ParseErrorCategory::MissingField,
        };
        Self::new(err, Arc::default())
    }
}

impl fmt::Display for ErrorWithOrigin {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "[{}]: {}", self.origin, self.inner)
    }
}

impl std::error::Error for ErrorWithOrigin {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.inner {
            LowLevelError::Json { err, .. } => Some(err),
            LowLevelError::InvalidArray
            | LowLevelError::InvalidObject
            | LowLevelError::Validation => None,
        }
    }
}

/// Config parameter deserialization errors.
pub struct ParseError {
    pub(crate) inner: serde_json::Error,
    pub(crate) category: ParseErrorCategory,
    pub(crate) path: String,
    pub(crate) origin: Arc<ValueOrigin>,
    pub(crate) config: &'static ConfigMetadata,
    pub(crate) location_in_config: Option<LocationInConfig>,
    pub(crate) validation: Option<String>,
}

impl fmt::Debug for ParseError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("ParseError")
            .field("inner", &self.inner)
            .field("origin", &self.origin)
            .field("path", &self.path)
            .field("config.ty", &self.config.ty)
            .field("location_in_config", &self.location_in_config)
            .field("validation", &self.validation)
            .finish_non_exhaustive()
    }
}

impl fmt::Display for ParseError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let field = self.location_in_config.and_then(|location| {
            Some(match location {
                LocationInConfig::Param(idx) => {
                    let param = self.config.params.get(idx)?;
                    format!("param `{}` in ", param.name)
                }
            })
        });
        let field = field.as_deref().unwrap_or("");

        let origin = if matches!(self.origin(), ValueOrigin::Unknown) {
            String::new()
        } else {
            format!(" [origin: {}]", self.origin)
        };

        let failed_action = if let Some(validation) = &self.validation {
            format!("validating '{validation}' for")
        } else {
            "parsing".to_owned()
        };

        write!(
            formatter,
            "error {failed_action} {field}`{config}` at `{path}`{origin}: {err}",
            err = self.inner,
            config = self.config.ty.name_in_code(),
            path = self.path
        )
    }
}

impl std::error::Error for ParseError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.inner)
    }
}

impl ParseError {
    pub(crate) fn generic(path: String, config: &'static ConfigMetadata) -> Self {
        Self {
            inner: serde_json::Error::custom("unspecified error deserializing configuration"),
            category: ParseErrorCategory::Generic,
            path,
            origin: Arc::default(),
            config,
            location_in_config: None,
            validation: None,
        }
    }

    /// Returns the wrapped error.
    pub fn inner(&self) -> &serde_json::Error {
        &self.inner
    }

    #[doc(hidden)]
    pub fn category(&self) -> ParseErrorCategory {
        self.category
    }

    /// Returns an absolute path on which this error has occurred.
    pub fn path(&self) -> &str {
        &self.path
    }

    /// Returns an origin of the value deserialization of which failed.
    pub fn origin(&self) -> &ValueOrigin {
        &self.origin
    }

    /// Returns human-readable description of the failed validation, if the error is caused by one.
    pub fn validation(&self) -> Option<&str> {
        self.validation.as_deref()
    }

    /// Returns metadata for the failing config.
    pub fn config(&self) -> &'static ConfigMetadata {
        self.config
    }

    /// Returns metadata for the failing parameter if this error concerns a parameter. The parameter
    /// is guaranteed to be contained in [`Self::config()`].
    pub fn param(&self) -> Option<&'static ParamMetadata> {
        let LocationInConfig::Param(idx) = self.location_in_config?;
        self.config.params.get(idx)
    }
}

/// Collection of [`ParseError`]s returned from [`ConfigParser::parse()`](crate::ConfigParser::parse()).
#[derive(Debug, Default)]
pub struct ParseErrors {
    errors: Vec<ParseError>,
}

impl ParseErrors {
    pub(crate) fn push(&mut self, err: ParseError) {
        self.errors.push(err);
    }

    /// Iterates over the contained errors.
    pub fn iter(&self) -> impl Iterator<Item = &ParseError> + '_ {
        self.errors.iter()
    }

    /// Returns the number of contained errors.
    #[allow(clippy::len_without_is_empty)] // is_empty should always return false
    pub fn len(&self) -> usize {
        self.errors.len()
    }

    /// Returns a reference to the first error.
    #[allow(clippy::missing_panics_doc)] // false positive
    pub fn first(&self) -> &ParseError {
        self.errors.first().expect("no errors")
    }

    pub(crate) fn truncate(&mut self, len: usize) {
        self.errors.truncate(len);
    }
}

impl IntoIterator for ParseErrors {
    type Item = ParseError;
    type IntoIter = std::vec::IntoIter<ParseError>;

    fn into_iter(self) -> Self::IntoIter {
        self.errors.into_iter()
    }
}

impl fmt::Display for ParseErrors {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        for err in &self.errors {
            writeln!(formatter, "{err}")?;
        }
        Ok(())
    }
}

impl std::error::Error for ParseErrors {}

impl FromIterator<ParseError> for Result<(), ParseErrors> {
    fn from_iter<I: IntoIterator<Item = ParseError>>(iter: I) -> Self {
        let errors: Vec<_> = iter.into_iter().collect();
        if errors.is_empty() {
            Ok(())
        } else {
            Err(ParseErrors { errors })
        }
    }
}