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#[cfg_attr(test, derive(PartialEq))]
16pub(in crate::error) enum ErrorKind {
17 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 Multiple(Vec<Error>),
33
34 #[doc(hidden)]
36 __NonExhaustive,
37}
38
39impl ErrorKind {
40 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#[derive(Clone, Debug)]
119#[cfg_attr(test, derive(PartialEq))]
122pub(in crate::error) struct ErrorUnknownValue {
123 noun: UnknownValuePosition,
125
126 value: String,
127 did_you_mean: Option<(f64, String)>,
131 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 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 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 #[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}