1 | use std::fmt; |
2 | |
3 | use crate::error::Error; |
4 | |
5 | type DeriveInputShape = String; |
6 | type FieldName = String; |
7 | type MetaFormat = String; |
8 | |
9 | #[derive (Debug, Clone)] |
10 | // Don't want to publicly commit to ErrorKind supporting equality yet, but |
11 | // not having it makes testing very difficult. |
12 | #[cfg_attr (test, derive(PartialEq, Eq))] |
13 | pub(in crate::error) enum ErrorKind { |
14 | /// An arbitrary error message. |
15 | Custom(String), |
16 | DuplicateField(FieldName), |
17 | MissingField(FieldName), |
18 | UnsupportedShape { |
19 | observed: DeriveInputShape, |
20 | expected: Option<String>, |
21 | }, |
22 | UnknownField(ErrorUnknownField), |
23 | UnexpectedFormat(MetaFormat), |
24 | UnexpectedType(String), |
25 | UnknownValue(String), |
26 | TooFewItems(usize), |
27 | TooManyItems(usize), |
28 | /// A set of errors. |
29 | Multiple(Vec<Error>), |
30 | |
31 | // TODO make this variant take `!` so it can't exist |
32 | #[doc (hidden)] |
33 | __NonExhaustive, |
34 | } |
35 | |
36 | impl ErrorKind { |
37 | pub fn description(&self) -> &str { |
38 | use self::ErrorKind::*; |
39 | |
40 | match *self { |
41 | Custom(ref s) => s, |
42 | DuplicateField(_) => "Duplicate field" , |
43 | MissingField(_) => "Missing field" , |
44 | UnknownField(_) => "Unexpected field" , |
45 | UnsupportedShape { .. } => "Unsupported shape" , |
46 | UnexpectedFormat(_) => "Unexpected meta-item format" , |
47 | UnexpectedType(_) => "Unexpected literal type" , |
48 | UnknownValue(_) => "Unknown literal value" , |
49 | TooFewItems(_) => "Too few items" , |
50 | TooManyItems(_) => "Too many items" , |
51 | Multiple(_) => "Multiple errors" , |
52 | __NonExhaustive => unreachable!(), |
53 | } |
54 | } |
55 | |
56 | /// Deeply counts the number of errors this item represents. |
57 | pub fn len(&self) -> usize { |
58 | if let ErrorKind::Multiple(ref items) = *self { |
59 | items.iter().map(Error::len).sum() |
60 | } else { |
61 | 1 |
62 | } |
63 | } |
64 | } |
65 | |
66 | impl fmt::Display for ErrorKind { |
67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
68 | use self::ErrorKind::*; |
69 | |
70 | match *self { |
71 | Custom(ref s) => s.fmt(f), |
72 | DuplicateField(ref field) => write!(f, "Duplicate field ` {}`" , field), |
73 | MissingField(ref field) => write!(f, "Missing field ` {}`" , field), |
74 | UnknownField(ref field) => field.fmt(f), |
75 | UnsupportedShape { |
76 | ref observed, |
77 | ref expected, |
78 | } => { |
79 | write!(f, "Unsupported shape ` {}`" , observed)?; |
80 | if let Some(expected) = &expected { |
81 | write!(f, ". Expected {}." , expected)?; |
82 | } |
83 | |
84 | Ok(()) |
85 | } |
86 | UnexpectedFormat(ref format) => write!(f, "Unexpected meta-item format ` {}`" , format), |
87 | UnexpectedType(ref ty) => write!(f, "Unexpected literal type ` {}`" , ty), |
88 | UnknownValue(ref val) => write!(f, "Unknown literal value ` {}`" , val), |
89 | TooFewItems(ref min) => write!(f, "Too few items: Expected at least {}" , min), |
90 | TooManyItems(ref max) => write!(f, "Too many items: Expected no more than {}" , max), |
91 | Multiple(ref items) if items.len() == 1 => items[0].fmt(f), |
92 | Multiple(ref items) => { |
93 | write!(f, "Multiple errors: (" )?; |
94 | let mut first = true; |
95 | for item in items { |
96 | if !first { |
97 | write!(f, ", " )?; |
98 | } else { |
99 | first = false; |
100 | } |
101 | |
102 | item.fmt(f)?; |
103 | } |
104 | |
105 | write!(f, ")" ) |
106 | } |
107 | __NonExhaustive => unreachable!(), |
108 | } |
109 | } |
110 | } |
111 | |
112 | impl From<ErrorUnknownField> for ErrorKind { |
113 | fn from(err: ErrorUnknownField) -> Self { |
114 | ErrorKind::UnknownField(err) |
115 | } |
116 | } |
117 | |
118 | /// An error for an unknown field, with a possible "did-you-mean" suggestion to get |
119 | /// the user back on the right track. |
120 | #[derive (Clone, Debug)] |
121 | // Don't want to publicly commit to ErrorKind supporting equality yet, but |
122 | // not having it makes testing very difficult. |
123 | #[cfg_attr (test, derive(PartialEq, Eq))] |
124 | pub(in crate::error) struct ErrorUnknownField { |
125 | name: String, |
126 | did_you_mean: Option<String>, |
127 | } |
128 | |
129 | impl ErrorUnknownField { |
130 | pub fn new<I: Into<String>>(name: I, did_you_mean: Option<String>) -> Self { |
131 | ErrorUnknownField { |
132 | name: name.into(), |
133 | did_you_mean, |
134 | } |
135 | } |
136 | |
137 | pub fn with_alts<'a, T, I>(field: &str, alternates: I) -> Self |
138 | where |
139 | T: AsRef<str> + 'a, |
140 | I: IntoIterator<Item = &'a T>, |
141 | { |
142 | ErrorUnknownField::new(field, did_you_mean(field, alternates)) |
143 | } |
144 | |
145 | #[cfg (feature = "diagnostics" )] |
146 | pub fn into_diagnostic(self, span: Option<::proc_macro2::Span>) -> ::proc_macro::Diagnostic { |
147 | let base = span |
148 | .unwrap_or_else(::proc_macro2::Span::call_site) |
149 | .unwrap() |
150 | .error(self.top_line()); |
151 | match self.did_you_mean { |
152 | Some(alt_name) => base.help(format!("did you mean ` {}`?" , alt_name)), |
153 | None => base, |
154 | } |
155 | } |
156 | |
157 | #[cfg (feature = "diagnostics" )] |
158 | fn top_line(&self) -> String { |
159 | format!("Unknown field: ` {}`" , self.name) |
160 | } |
161 | } |
162 | |
163 | impl From<String> for ErrorUnknownField { |
164 | fn from(name: String) -> Self { |
165 | ErrorUnknownField::new(name, did_you_mean:None) |
166 | } |
167 | } |
168 | |
169 | impl<'a> From<&'a str> for ErrorUnknownField { |
170 | fn from(name: &'a str) -> Self { |
171 | ErrorUnknownField::new(name, did_you_mean:None) |
172 | } |
173 | } |
174 | |
175 | impl fmt::Display for ErrorUnknownField { |
176 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
177 | write!(f, "Unknown field: ` {}`" , self.name)?; |
178 | |
179 | if let Some(ref did_you_mean: &String) = self.did_you_mean { |
180 | write!(f, ". Did you mean ` {}`?" , did_you_mean)?; |
181 | } |
182 | |
183 | Ok(()) |
184 | } |
185 | } |
186 | |
187 | #[cfg (feature = "suggestions" )] |
188 | fn did_you_mean<'a, T, I>(field: &str, alternates: I) -> Option<String> |
189 | where |
190 | T: AsRef<str> + 'a, |
191 | I: IntoIterator<Item = &'a T>, |
192 | { |
193 | let mut candidate: Option<(f64, &str)> = None; |
194 | for pv: &T in alternates { |
195 | let confidence: f64 = ::strsim::jaro_winkler(a:field, b:pv.as_ref()); |
196 | if confidence > 0.8 && (candidate.is_none() || (candidate.as_ref().unwrap().0 < confidence)) |
197 | { |
198 | candidate = Some((confidence, pv.as_ref())); |
199 | } |
200 | } |
201 | candidate.map(|(_, candidate: &str)| candidate.into()) |
202 | } |
203 | |
204 | #[cfg (not(feature = "suggestions" ))] |
205 | fn did_you_mean<'a, T, I>(_field: &str, _alternates: I) -> Option<String> |
206 | where |
207 | T: AsRef<str> + 'a, |
208 | I: IntoIterator<Item = &'a T>, |
209 | { |
210 | None |
211 | } |
212 | |