fallible_options.rs

  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}