Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 100 additions & 190 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,6 @@ serde_json_lenient = { version = "0.2", features = [
serde_path_to_error = "0.1.17"
serde_repr = "0.1"
serde_urlencoded = "0.7"
serde_with = "3.4.0"
sha2 = "0.10"
shellexpand = "2.1.0"
shlex = "1.3.0"
Expand Down
1 change: 0 additions & 1 deletion crates/settings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
serde_repr.workspace = true
serde_with.workspace = true
settings_json.workspace = true
settings_macros.workspace = true
smallvec.workspace = true
Expand Down
112 changes: 112 additions & 0 deletions crates/settings/src/fallible_options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use std::cell::RefCell;

use serde::Deserialize;

use crate::ParseStatus;

thread_local! {
static ERRORS: RefCell<Option<Vec<anyhow::Error>>> = const { RefCell::new(None) };
}

pub(crate) fn parse_json<'de, T>(json: &'de str) -> (Option<T>, ParseStatus)
where
T: Deserialize<'de>,
{
ERRORS.with_borrow_mut(|errors| {
errors.replace(Vec::default());
});

let mut deserializer = serde_json_lenient::Deserializer::from_str(json);
let value = T::deserialize(&mut deserializer);
let value = match value {
Ok(value) => value,
Err(error) => {
return (
None,
ParseStatus::Failed {
error: error.to_string(),
},
);
}
};

if let Some(errors) = ERRORS.with_borrow_mut(|errors| errors.take().filter(|e| !e.is_empty())) {
let error = errors
.into_iter()
.map(|e| e.to_string())
.flat_map(|e| ["\n".to_owned(), e])
.skip(1)
.collect::<String>();
return (Some(value), ParseStatus::Failed { error });
}

(Some(value), ParseStatus::Success)
}

pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de> + FallibleOption,
{
match T::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(e) => ERRORS.with_borrow_mut(|errors| {
if let Some(errors) = errors {
errors.push(anyhow::anyhow!("{}", e));
Ok(Default::default())
} else {
Err(e)
}
}),
}
}

pub trait FallibleOption: Default {}
impl<T> FallibleOption for Option<T> {}

#[cfg(test)]
mod tests {
use serde::Deserialize;
use settings_macros::with_fallible_options;

use crate::ParseStatus;

#[with_fallible_options]
#[derive(Deserialize, Debug, PartialEq)]
struct Foo {
foo: Option<String>,
bar: Option<usize>,
baz: Option<bool>,
}

#[test]
fn test_fallible() {
let input = r#"
{"foo": "bar",
"bar": "foo",
"baz": 3,
}
"#;

let (settings, result) = crate::fallible_options::parse_json::<Foo>(&input);
assert_eq!(
settings.unwrap(),
Foo {
foo: Some("bar".into()),
bar: None,
baz: None,
}
);

assert!(crate::parse_json_with_comments::<Foo>(&input).is_err());

let ParseStatus::Failed { error } = result else {
panic!("Expected parse to fail")
};

assert_eq!(
error,
"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()
)
}
}
3 changes: 2 additions & 1 deletion crates/settings/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod base_keymap_setting;
mod editable_setting_control;
mod fallible_options;
mod keymap_file;
pub mod merge_from;
mod serde_helper;
Expand Down Expand Up @@ -33,7 +34,7 @@ pub use settings_file::*;
pub use settings_json::*;
pub use settings_store::{
InvalidSettingsError, LocalSettingsKind, MigrationStatus, ParseStatus, Settings, SettingsFile,
SettingsJsonSchemaParams, SettingsKey, SettingsLocation, SettingsStore,
SettingsJsonSchemaParams, SettingsKey, SettingsLocation, SettingsParseResult, SettingsStore,
};

pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource};
Expand Down
Loading
Loading