darling_core/error/
kind.rs

1use std::{collections::BTreeSet, fmt};
2
3use crate::error::{
4    util::{write_delimited, Quoted},
5    Error,
6};
7
8type DeriveInputShape = String;
9type FieldName = String;
10type MetaFormat = String;
11
12#[derive(Debug, Clone)]
13// Don't want to publicly commit to ErrorKind supporting equality yet, but
14// not having it makes testing very difficult.
15#[cfg_attr(test, derive(PartialEq))]
16pub(in crate::error) enum ErrorKind {
17    /// An arbitrary error message.
18    Custom(String),
19    DuplicateField(FieldName),
20    MissingField(FieldName),
21    UnsupportedShape {
22        observed: DeriveInputShape,
23        expected: Option<String>,
24    },
25    UnknownField(Box<ErrorUnknownValue>),
26    UnexpectedFormat(MetaFormat),
27    UnexpectedType(String),
28    UnknownValue(Box<ErrorUnknownValue>),
29    TooFewItems(usize),
30    TooManyItems(usize),
31    /// A set of errors.
32    Multiple(Vec<Error>),
33
34    // TODO make this variant take `!` so it can't exist
35    #[doc(hidden)]
36    __NonExhaustive,
37}
38
39impl ErrorKind {
40    /// Deeply counts the number of errors this item represents.
41    pub fn len(&self) -> usize {
42        if let ErrorKind::Multiple(ref items) = *self {
43            items.iter().map(Error::len).sum()
44        } else {
45            1
46        }
47    }
48}
49
50impl fmt::Display for ErrorKind {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        use self::ErrorKind::*;
53
54        match *self {
55            Custom(ref s) => s.fmt(f),
56            DuplicateField(ref field) => write!(f, "Duplicate field `{}`", field),
57            MissingField(ref field) => write!(f, "Missing field `{}`", field),
58            UnknownField(ref field) => field.fmt(f),
59            UnsupportedShape {
60                ref observed,
61                ref expected,
62            } => {
63                write!(f, "Unsupported shape `{}`", observed)?;
64                if let Some(expected) = &expected {
65                    write!(f, ". Expected {}.", expected)?;
66                }
67
68                Ok(())
69            }
70            UnexpectedFormat(ref format) => write!(f, "Unexpected meta-item format `{}`", format),
71            UnexpectedType(ref ty) => write!(f, "Unexpected type `{}`", ty),
72            UnknownValue(ref val) => val.fmt(f),
73            TooFewItems(ref min) => write!(f, "Too few items: Expected at least {}", min),
74            TooManyItems(ref max) => write!(f, "Too many items: Expected no more than {}", max),
75            Multiple(ref items) if items.len() == 1 => items[0].fmt(f),
76            Multiple(ref items) => {
77                write!(f, "Multiple errors: (")?;
78                write_delimited(f, items, ", ")?;
79                write!(f, ")")
80            }
81            __NonExhaustive => unreachable!(),
82        }
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub(in crate::error) enum UnknownValuePosition {
88    Field,
89    Value,
90}
91
92impl AsRef<str> for UnknownValuePosition {
93    fn as_ref(&self) -> &str {
94        match self {
95            UnknownValuePosition::Field => "field",
96            UnknownValuePosition::Value => "value",
97        }
98    }
99}
100
101impl fmt::Display for UnknownValuePosition {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}", self.as_ref())
104    }
105}
106
107impl From<ErrorUnknownValue> for ErrorKind {
108    fn from(value: ErrorUnknownValue) -> Self {
109        match value.noun {
110            UnknownValuePosition::Field => Self::UnknownField(Box::new(value)),
111            UnknownValuePosition::Value => Self::UnknownValue(Box::new(value)),
112        }
113    }
114}
115
116/// An error where an unknown value was seen in a given position,
117/// with a possible "did-you-mean" suggestion to get the user back on the right track.
118#[derive(Clone, Debug)]
119// Don't want to publicly commit to ErrorKind supporting equality yet, but
120// not having it makes testing very difficult.
121#[cfg_attr(test, derive(PartialEq))]
122pub(in crate::error) struct ErrorUnknownValue {
123    /// The thing whose value is unknown.
124    noun: UnknownValuePosition,
125
126    value: String,
127    /// The best suggestion of what field the caller could have meant, along with
128    /// the similarity score between that best option and the actual caller-provided
129    /// field name.
130    did_you_mean: Option<(f64, String)>,
131    /// Set of all known valid field names.
132    ///
133    /// This is a `BTreeSet` so that names will be displayed in alphabetical order
134    /// without needing display-time sorting.
135    alts: BTreeSet<String>,
136}
137
138impl ErrorUnknownValue {
139    pub fn new<I: Into<String>>(noun: UnknownValuePosition, value: I) -> Self {
140        ErrorUnknownValue {
141            noun,
142            value: value.into(),
143            did_you_mean: None,
144            alts: BTreeSet::new(),
145        }
146    }
147
148    pub fn with_alts<'a, T, I>(noun: UnknownValuePosition, value: &str, alternates: I) -> Self
149    where
150        T: AsRef<str> + 'a,
151        I: IntoIterator<Item = &'a T>,
152    {
153        let alts = alternates
154            .into_iter()
155            .map(|s| s.as_ref().to_string())
156            .collect();
157        Self {
158            noun,
159            value: value.into(),
160            did_you_mean: did_you_mean(value, &alts),
161            alts,
162        }
163    }
164
165    /// Add more alternate values to the error, updating the `did_you_mean` suggestion
166    /// if a closer match to the unknown value is found.
167    pub fn add_alts<'a, T, I>(&mut self, alternates: I)
168    where
169        T: AsRef<str> + 'a,
170        I: IntoIterator<Item = &'a T>,
171    {
172        let alts = alternates.into_iter().collect::<Vec<_>>();
173        self.alts
174            .extend(alts.iter().map(|s| s.as_ref().to_string()));
175        if let Some(bna) = did_you_mean(&self.value, alts) {
176            if let Some(current) = &self.did_you_mean {
177                if bna.0 > current.0 {
178                    self.did_you_mean = Some(bna);
179                }
180            } else {
181                self.did_you_mean = Some(bna);
182            }
183        }
184    }
185
186    #[cfg(feature = "diagnostics")]
187    pub fn into_diagnostic(self, span: Option<::proc_macro2::Span>) -> ::proc_macro::Diagnostic {
188        let mut diag = span
189            .unwrap_or_else(::proc_macro2::Span::call_site)
190            .unwrap()
191            .error(self.top_line());
192
193        if let Some((_, alt_name)) = self.did_you_mean {
194            diag = diag.help(format!("did you mean `{}`?", alt_name));
195        }
196
197        if !self.alts.is_empty() {
198            let mut alts = String::new();
199
200            // Per documentation, formatting is infallible unless underlying stream closes
201            // https://doc.rust-lang.org/std/fmt/struct.Error.html
202            write_delimited(&mut alts, self.alts.iter().map(Quoted::backticks), ", ")
203                .expect("writing to a string never fails");
204            diag = diag.help(format!("available values: {}", alts));
205        }
206
207        diag
208    }
209
210    #[cfg(feature = "diagnostics")]
211    fn top_line(&self) -> String {
212        format!("Unknown {}: `{}`", self.noun, self.value)
213    }
214}
215
216impl fmt::Display for ErrorUnknownValue {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        write!(f, "Unknown {}: `{}`", self.noun, self.value)?;
219
220        if let Some((_, ref did_you_mean)) = self.did_you_mean {
221            write!(f, ". Did you mean `{}`?", did_you_mean)?;
222        } else if !self.alts.is_empty() && self.alts.len() < 10 {
223            write!(f, ". Available values: ")?;
224            write_delimited(f, self.alts.iter().map(Quoted::backticks), ", ")?;
225        }
226
227        Ok(())
228    }
229}
230
231#[cfg(feature = "suggestions")]
232fn did_you_mean<'a, T, I>(field: &str, alternates: I) -> Option<(f64, String)>
233where
234    T: AsRef<str> + 'a,
235    I: IntoIterator<Item = &'a T>,
236{
237    let mut candidate: Option<(f64, &str)> = None;
238    for pv in alternates {
239        let confidence = ::strsim::jaro_winkler(field, pv.as_ref());
240        if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence))
241        {
242            candidate = Some((confidence, pv.as_ref()));
243        }
244    }
245    candidate.map(|(score, candidate)| (score, candidate.into()))
246}
247
248#[cfg(not(feature = "suggestions"))]
249fn did_you_mean<'a, T, I>(_field: &str, _alternates: I) -> Option<(f64, String)>
250where
251    T: AsRef<str> + 'a,
252    I: IntoIterator<Item = &'a T>,
253{
254    None
255}
256
257#[cfg(test)]
258mod tests {
259    use super::{ErrorUnknownValue, UnknownValuePosition};
260
261    /// Make sure that an unknown field error with no alts or suggestions has
262    /// only the relevant information and no fragments of other sentences.
263    #[test]
264    fn present_no_alts() {
265        let err = ErrorUnknownValue::new(UnknownValuePosition::Field, "hello");
266        assert_eq!(&err.to_string(), "Unknown field: `hello`");
267    }
268
269    #[test]
270    fn present_few_alts() {
271        let err = ErrorUnknownValue::with_alts(
272            UnknownValuePosition::Field,
273            "hello",
274            &["world", "friend"],
275        );
276        assert!(err.to_string().contains("`friend`"));
277    }
278}