1use std::cell::RefCell;
2
3use serde::Deserialize;
4
5use crate::ParseStatus;
6
7thread_local! {
8 static ERRORS: RefCell<Option<Vec<anyhow::Error>>> = const { RefCell::new(None) };
9}
10
11pub(crate) fn parse_json<'de, T>(json: &'de str) -> (Option<T>, ParseStatus)
12where
13 T: Deserialize<'de>,
14{
15 ERRORS.with_borrow_mut(|errors| {
16 errors.replace(Vec::default());
17 });
18
19 let mut deserializer = serde_json_lenient::Deserializer::from_str(json);
20 let value = T::deserialize(&mut deserializer);
21 let value = match value {
22 Ok(value) => value,
23 Err(error) => {
24 return (
25 None,
26 ParseStatus::Failed {
27 error: error.to_string(),
28 },
29 );
30 }
31 };
32
33 if let Some(errors) = ERRORS.with_borrow_mut(|errors| errors.take().filter(|e| !e.is_empty())) {
34 let error = errors
35 .into_iter()
36 .map(|e| e.to_string())
37 .flat_map(|e| ["\n".to_owned(), e])
38 .skip(1)
39 .collect::<String>();
40 return (Some(value), ParseStatus::Failed { error });
41 }
42
43 (Some(value), ParseStatus::Success)
44}
45
46pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
47where
48 D: serde::Deserializer<'de>,
49 T: serde::Deserialize<'de> + FallibleOption,
50{
51 match T::deserialize(deserializer) {
52 Ok(value) => Ok(value),
53 Err(e) => ERRORS.with_borrow_mut(|errors| {
54 if let Some(errors) = errors {
55 errors.push(anyhow::anyhow!("{}", e));
56 Ok(Default::default())
57 } else {
58 Err(e)
59 }
60 }),
61 }
62}
63
64pub trait FallibleOption: Default {}
65impl<T> FallibleOption for Option<T> {}
66
67#[cfg(test)]
68mod tests {
69 use serde::Deserialize;
70 use settings_macros::with_fallible_options;
71
72 use crate::ParseStatus;
73
74 #[with_fallible_options]
75 #[derive(Deserialize, Debug, PartialEq)]
76 struct Foo {
77 foo: Option<String>,
78 bar: Option<usize>,
79 baz: Option<bool>,
80 }
81
82 #[test]
83 fn test_fallible() {
84 let input = r#"
85 {"foo": "bar",
86 "bar": "foo",
87 "baz": 3,
88 }
89 "#;
90
91 let (settings, result) = crate::fallible_options::parse_json::<Foo>(&input);
92 assert_eq!(
93 settings.unwrap(),
94 Foo {
95 foo: Some("bar".into()),
96 bar: None,
97 baz: None,
98 }
99 );
100
101 assert!(crate::parse_json_with_comments::<Foo>(&input).is_err());
102
103 let ParseStatus::Failed { error } = result else {
104 panic!("Expected parse to fail")
105 };
106
107 assert_eq!(
108 error,
109 "invalid type: string \"foo\", expected usize at line 3 column 24\ninvalid type: integer `3`, expected a boolean at line 4 column 20".to_string()
110 )
111 }
112}