1use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction};
2use crate::object::Object;
3use crate::value::Value;
4use std::cell::RefCell;
5use std::rc::Rc;
6use thiserror::Error;
7
8pub trait WarningCollector {
9 fn warn(&self, message: String);
10}
11
12thread_local! {
13 static WARNING_COLLECTOR: RefCell<Option<Box<dyn WarningCollector>>> = RefCell::new(None);
14}
15
16#[derive(Error, Debug)]
17#[non_exhaustive]
18pub enum Error {
19 #[error("`{}` is not a valid {} variant. {}", .variant_name, .type_name, Self::possible_matches(.variant_name, &.possible))]
20 InvalidVariantForType {
21 variant_name: String,
22 type_name: &'static str,
23 possible: &'static [&'static str],
24 },
25 #[error("`{}` is not a valid {} field. {}", .field_name, .type_name, Self::possible_matches(.field_name, &.possible))]
26 UnknownFieldForStruct {
27 field_name: String,
28 type_name: &'static str,
29 possible: &'static [&'static str],
30 },
31 #[error("{}", .0)]
32 Message(String),
33 #[error("Cannot coerce vec of size {} to array of size {}", .vec_size, .array_size)]
34 ArraySizeMismatch { vec_size: usize, array_size: usize },
35 #[error("Cannot convert `{}` to `{}`", .source_type, .dest_type)]
36 NoConversion {
37 source_type: String,
38 dest_type: &'static str,
39 },
40 #[error("Expected char to be a string with a single character")]
41 CharFromWrongSizedString,
42 #[error("Expected a valid `{}` variant name as single key in object, but there are {} keys", .type_name, .num_keys)]
43 IncorrectNumberOfEnumKeys {
44 type_name: &'static str,
45 num_keys: usize,
46 },
47 #[error("Error processing {}::{}: {:#}", .type_name, .field_name, .error)]
48 ErrorInField {
49 type_name: &'static str,
50 field_name: &'static str,
51 error: String,
52 },
53 #[error("Error processing {} (types: {}) {:#}", .field_name.join("."), .type_name.join(", "), .error)]
54 ErrorInNestedField {
55 type_name: Vec<&'static str>,
56 field_name: Vec<&'static str>,
57 error: String,
58 },
59 #[error("`{}` is not a valid type to use as a field name in `{}`", .key_type, .type_name)]
60 InvalidFieldType {
61 type_name: &'static str,
62 key_type: String,
63 },
64 #[error("{}::{} is deprecated: {}", .type_name, .field_name, .reason)]
65 DeprecatedField {
66 type_name: &'static str,
67 field_name: &'static str,
68 reason: &'static str,
69 },
70}
71
72impl Error {
73 pub fn warn(message: String) {
76 WARNING_COLLECTOR.with(|collector| {
77 let collector = collector.borrow();
78 if let Some(collector) = collector.as_ref() {
79 collector.warn(message);
80 } else {
81 log::warn!("{message}");
82 }
83 });
84 }
85
86 pub fn capture_warnings<F: FnOnce() -> T, T>(f: F) -> (T, Vec<String>) {
87 let warnings = Rc::new(RefCell::new(vec![]));
88
89 struct Collector {
90 warnings: Rc<RefCell<Vec<String>>>,
91 }
92
93 impl WarningCollector for Collector {
94 fn warn(&self, message: String) {
95 self.warnings.borrow_mut().push(message);
96 }
97 }
98
99 Self::set_warning_collector(Collector {
100 warnings: Rc::clone(&warnings),
101 });
102 let result = f();
103 Self::clear_warning_collector();
104 let warnings = match Rc::try_unwrap(warnings) {
105 Ok(warnings) => warnings.into_inner(),
106 Err(warnings) => (*warnings).clone().into_inner(),
107 };
108 (result, warnings)
109 }
110
111 fn set_warning_collector<T: WarningCollector + 'static>(c: T) {
113 WARNING_COLLECTOR.with(|collector| {
114 collector.borrow_mut().replace(Box::new(c));
115 });
116 }
117
118 fn clear_warning_collector() {
120 WARNING_COLLECTOR.with(|collector| {
121 collector.borrow_mut().take();
122 });
123 }
124
125 fn compute_unknown_fields(
126 type_name: &'static str,
127 object: &crate::Object,
128 possible: &'static [&'static str],
129 ) -> Vec<Self> {
130 let mut errors = vec![];
131
132 for key in object.keys() {
133 match key {
134 Value::String(s) => {
135 if !possible.contains(&s.as_str()) {
136 errors.push(Self::UnknownFieldForStruct {
137 field_name: s.to_string(),
138 type_name,
139 possible,
140 });
141 }
142 }
143 other => {
144 errors.push(Self::InvalidFieldType {
145 type_name,
146 key_type: other.variant_name().to_string(),
147 });
148 }
149 }
150 }
151
152 errors
153 }
154
155 pub fn raise_deprecated_fields(
156 options: FromDynamicOptions,
157 type_name: &'static str,
158 field_name: &'static str,
159 reason: &'static str,
160 ) -> Result<(), Self> {
161 if options.deprecated_fields == UnknownFieldAction::Ignore {
162 return Ok(());
163 }
164 let err = Self::DeprecatedField {
165 type_name,
166 field_name,
167 reason,
168 };
169
170 match options.deprecated_fields {
171 UnknownFieldAction::Deny => Err(err),
172 UnknownFieldAction::Warn => {
173 Self::warn(format!("{:#}", err));
174 Ok(())
175 }
176 UnknownFieldAction::Ignore => unreachable!(),
177 }
178 }
179
180 pub fn raise_unknown_fields(
181 options: FromDynamicOptions,
182 type_name: &'static str,
183 object: &crate::Object,
184 possible: &'static [&'static str],
185 ) -> Result<(), Self> {
186 if options.unknown_fields == UnknownFieldAction::Ignore {
187 return Ok(());
188 }
189
190 let errors = Self::compute_unknown_fields(type_name, object, possible);
191 if errors.is_empty() {
192 return Ok(());
193 }
194
195 let show_warning = options.unknown_fields == UnknownFieldAction::Warn || errors.len() > 1;
196
197 if show_warning {
198 for err in &errors {
199 Self::warn(format!("{:#}", err));
200 }
201 }
202
203 if options.unknown_fields == UnknownFieldAction::Deny {
204 for err in errors {
205 return Err(err);
206 }
207 }
208
209 Ok(())
210 }
211
212 fn possible_matches(used: &str, possible: &'static [&'static str]) -> String {
213 let mut candidates: Vec<(f64, &str)> = possible
215 .iter()
216 .map(|&name| (strsim::jaro_winkler(used, name), name))
217 .filter(|(confidence, _)| *confidence > 0.8)
218 .collect();
219 candidates.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
220 let suggestions: Vec<&str> = candidates.into_iter().map(|(_, name)| name).collect();
221
222 let mut fields: Vec<&str> = possible
225 .iter()
226 .filter(|&name| !suggestions.iter().any(|candidate| candidate == name))
227 .copied()
228 .collect();
229 fields.sort_unstable();
230
231 let mut message = String::new();
232
233 match suggestions.len() {
234 0 => {}
235 1 => message.push_str(&format!("Did you mean `{}`?", suggestions[0])),
236 _ => {
237 message.push_str("Did you mean one of ");
238 for (idx, candidate) in suggestions.iter().enumerate() {
239 if idx > 0 {
240 message.push_str(", ");
241 }
242 message.push('`');
243 message.push_str(candidate);
244 message.push('`');
245 }
246 message.push('?');
247 }
248 }
249 if !fields.is_empty() {
250 let limit = 5;
251 if fields.len() > limit {
252 message.push_str(
253 " There are too many alternatives to list here; consult the documentation!",
254 );
255 } else {
256 if suggestions.is_empty() {
257 message.push_str("Possible alternatives are ");
258 } else if suggestions.len() == 1 {
259 message.push_str(" The other option is ");
260 } else {
261 message.push_str(" Other alternatives are ");
262 }
263 for (idx, candidate) in fields.iter().enumerate() {
264 if idx > 0 {
265 message.push_str(", ");
266 }
267 message.push('`');
268 message.push_str(candidate);
269 message.push('`');
270 }
271 }
272 }
273
274 message
275 }
276
277 pub fn field_context(
278 self,
279 type_name: &'static str,
280 field_name: &'static str,
281 obj: &Object,
282 ) -> Self {
283 let is_leaf = !matches!(self, Self::ErrorInField { .. });
284 fn add_obj_context(is_leaf: bool, obj: &Object, message: String) -> String {
285 if is_leaf {
286 let obj_str = format!("{:#?}", obj);
291 if obj_str.len() > 128 || obj_str.lines().count() > 10 {
292 message
293 } else {
294 format!("{}.\n{}", message, obj_str)
295 }
296 } else {
297 message
298 }
299 }
300
301 match self {
302 Self::NoConversion { source_type, .. } if source_type == "Null" => Self::ErrorInField {
303 type_name,
304 field_name,
305 error: add_obj_context(is_leaf, obj, format!("missing field `{}`", field_name)),
306 },
307 Self::ErrorInField {
308 type_name: child_type,
309 field_name: child_field,
310 error,
311 } => Self::ErrorInNestedField {
312 type_name: vec![type_name, child_type],
313 field_name: vec![field_name, child_field],
314 error,
315 },
316 Self::ErrorInNestedField {
317 type_name: mut child_type,
318 field_name: mut child_field,
319 error,
320 } => Self::ErrorInNestedField {
321 type_name: {
322 child_type.insert(0, type_name);
323 child_type
324 },
325 field_name: {
326 child_field.insert(0, field_name);
327 child_field
328 },
329 error,
330 },
331 _ => Self::ErrorInField {
332 type_name,
333 field_name,
334 error: add_obj_context(is_leaf, obj, format!("{:#}", self)),
335 },
336 }
337 }
338}
339
340impl From<String> for Error {
341 fn from(s: String) -> Error {
342 Error::Message(s)
343 }
344}