fix: surface rejected configuration in a floating startup diagnostic
This commit is contained in:
+70
-20
@@ -95,7 +95,8 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
|
||||
});
|
||||
}
|
||||
config.bindings = bindings;
|
||||
config.lsp = lsp;
|
||||
config.lsp =
|
||||
crate::lsp_config::resolve(lsp).map_err(|error| format!("Ruby config: {error}"))?;
|
||||
config.leader = leader;
|
||||
config.file_explorer_side = file_explorer_side;
|
||||
config.file_picker_view = file_picker_view;
|
||||
@@ -379,8 +380,9 @@ enum Response {
|
||||
Config {
|
||||
version: u8,
|
||||
plugins: Vec<PluginConfig>,
|
||||
#[serde(deserialize_with = "crate::lsp_config::deserialize")]
|
||||
lsp: Vec<crate::lsp::ServerConfig>,
|
||||
// Keep this wire field required; resolve its configuration after decoding.
|
||||
#[serde(deserialize_with = "serde_json::Value::deserialize")]
|
||||
lsp: serde_json::Value,
|
||||
bindings: Vec<Binding>,
|
||||
leader: String,
|
||||
file_explorer_side: String,
|
||||
@@ -1085,33 +1087,40 @@ end"#,
|
||||
}
|
||||
let path = fixture.file(
|
||||
"invalid.rb",
|
||||
r#"Vamp.configure { |config| config.lsp["unlisted"] = true }"#,
|
||||
r#"Vamp.configure { |config| config.lsp["ruby"] = true }"#,
|
||||
);
|
||||
let error = load_config(&path).unwrap_err();
|
||||
assert!(!error.contains("Malformed Ruby protocol frame"), "{error}");
|
||||
assert!(
|
||||
error.contains("unlisted") && error.contains("preset"),
|
||||
error.contains("\"ruby\"") && error.contains("command") && error.contains("extensions"),
|
||||
"{error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_wire_configuration_is_validated_before_being_consumed() {
|
||||
fn lsp_wire_configuration_is_resolved_after_decoding() {
|
||||
let mut frame = serde_json::json!({
|
||||
"version": 1, "type": "config", "plugins": [], "lsp": {},
|
||||
"bindings": [], "leader": "space", "file_explorer_side": "right",
|
||||
"file_picker_view": "tree", "file_explorer_view": "tree",
|
||||
"clipboard_mode": "system"
|
||||
});
|
||||
assert!(matches!(
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap(),
|
||||
Response::Config { lsp, .. } if lsp.is_empty()
|
||||
));
|
||||
let Response::Config { lsp, .. } =
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap()
|
||||
else {
|
||||
panic!("expected config frame");
|
||||
};
|
||||
assert!(crate::lsp_config::resolve(lsp).unwrap().is_empty());
|
||||
frame["lsp"] = serde_json::json!({"rust": true});
|
||||
assert!(matches!(
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap(),
|
||||
Response::Config { lsp, .. }
|
||||
if lsp.len() == 1 && lsp[0].command == ["rust-analyzer"]
|
||||
));
|
||||
let Response::Config { lsp, .. } =
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap()
|
||||
else {
|
||||
panic!("expected config frame");
|
||||
};
|
||||
assert_eq!(
|
||||
crate::lsp_config::resolve(lsp).unwrap()[0].command,
|
||||
["rust-analyzer"]
|
||||
);
|
||||
for value in [
|
||||
serde_json::json!(null),
|
||||
serde_json::json!([]),
|
||||
@@ -1127,10 +1136,12 @@ end"#,
|
||||
serde_json::json!({"rust": {"unexpected": true}}),
|
||||
] {
|
||||
frame["lsp"] = value;
|
||||
assert!(
|
||||
serde_json::from_value::<Response>(frame.clone()).is_err(),
|
||||
"{frame}"
|
||||
);
|
||||
let Response::Config { lsp, .. } =
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap()
|
||||
else {
|
||||
panic!("expected config frame");
|
||||
};
|
||||
assert!(crate::lsp_config::resolve(lsp).is_err(), "{frame}");
|
||||
}
|
||||
let languages: serde_json::Map<String, serde_json::Value> = (0..17)
|
||||
.map(|index| {
|
||||
@@ -1141,11 +1152,50 @@ end"#,
|
||||
})
|
||||
.collect();
|
||||
frame["lsp"] = serde_json::Value::Object(languages);
|
||||
assert!(serde_json::from_value::<Response>(frame.clone()).is_err());
|
||||
let Response::Config { lsp, .. } =
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap()
|
||||
else {
|
||||
panic!("expected config frame");
|
||||
};
|
||||
assert!(crate::lsp_config::resolve(lsp).is_err());
|
||||
frame.as_object_mut().unwrap().remove("lsp");
|
||||
assert!(serde_json::from_value::<Response>(frame).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_config_wire_remains_a_protocol_error() {
|
||||
let fixture = Fixture::new();
|
||||
for mutation in [
|
||||
"return '['",
|
||||
"frame.delete('lsp')",
|
||||
"frame['leader'] = 7",
|
||||
"frame['unexpected'] = true",
|
||||
] {
|
||||
let source = format!(
|
||||
r#"
|
||||
Vamp.configure {{ |config| config.lsp["ruby"] = true }}
|
||||
module JSON
|
||||
class << self
|
||||
alias_method :original_generate, :generate
|
||||
def generate(frame)
|
||||
if frame['type'] == 'config'
|
||||
{mutation}
|
||||
end
|
||||
original_generate(frame)
|
||||
end
|
||||
end
|
||||
end
|
||||
"#
|
||||
);
|
||||
let path = fixture.file("config.rb", &source);
|
||||
let error = load_config(&path).unwrap_err();
|
||||
assert!(
|
||||
error.starts_with("Malformed Ruby protocol frame: "),
|
||||
"{mutation}: {error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crash_hang_callback_failure_and_fresh_host_recovery() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
Reference in New Issue
Block a user