1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-18 02:42:05 +03:00

wezterm: improve config deserialization error messages

This is a bit fiddly because the serde library doesn't let us know
eg: which key in a map had an error, so we have to show the information
from the value and hope that there is enough context for the user.

This commit adds our own Debug impl for ValueWrapper as that one
provided by mlua isn't useful for tables; our impl will iterate
the pairs in the table and pretty print them.

The struct error message has been adjusted to include the lua
value that failed to deserialize in addition to the type name.
Because the config structs can get quite large, this commit adjusts
the ordering of the components in the error message; we now print
the outermost error first and leave the most specific portion at
the bottom.  This is so that the error window is more likely to
show the most useful information at the bottom.

I've added some similar improvements when processing enums.
It's not perfect but hopefully more useful when figuring out
a config problem.

This invalid config:

```lua
local wezterm = require 'wezterm';
return {
  keys = {
    {key="{", mods="SHIFT|ALT", action="Search"},
  }
}
```

yields:

```
While (re)loading configuration: Error converting lua value returned by script /tmp/issue-201.lua to Config struct: while processing a struct of type `Config` with value:
{
    "keys": [
        {
            "mods": "SHIFT|ALT",
            "key": "{",
            "action": "Search",
        },
    ],
}
while processing a struct of type `Key` with value:
{
    "mods": "SHIFT|ALT",
    "key": "{",
    "action": "Search",
}
while processing an enum of type `KeyAssignment` and value "Search"
which has allowed variants `ActivateCopyMode`, `ActivateTab`, `ActivateTabRelative`, `ClearScrollback`, `CloseCurrentTab`, `CompleteSelection`, `CompleteSelectionOrOpenLinkAtMouseCursor`, `Copy`, `DecreaseFontSize`, `DisableDefaultAssignment`, `ExtendSelectionToMouseCursor`, `Hide`, `HideApplication`, `IncreaseFontSize`, `MoveTab`, `MoveTabRelative`, `Nop`, `OpenLinkAtMouseCursor`, `Paste`, `PastePrimarySelection`, `QuitApplication`, `ReloadConfiguration`, `ResetFontSize`, `ScrollByPage`, `Search`, `SelectTextAtMouseCursor`, `SendString`, `Show`, `ShowLauncher`, `ShowTabNavigator`, `SpawnCommandInNewTab`, `SpawnCommandInNewWindow`, `SpawnTab`, `SpawnWindow`, `ToggleFullScreen`
Expected a variant with parameters but got a unit variant instead
```

refs: https://github.com/wez/wezterm/issues/201
This commit is contained in:
Wez Furlong 2020-05-31 17:56:39 -07:00
parent ccf9fc141b
commit d0095f6bba

View File

@ -64,8 +64,58 @@ impl From<Error> for mlua::Error {
} }
} }
#[derive(Debug)] pub struct ValueWrapper<'lua>(Value<'lua>);
struct ValueWrapper<'lua>(Value<'lua>);
impl<'lua> std::fmt::Debug for ValueWrapper<'lua> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
match &self.0 {
Value::Nil => fmt.write_str("nil"),
Value::Boolean(b) => fmt.write_str(if *b { "true" } else { "false" }),
Value::Integer(i) => fmt.write_fmt(format_args!("{}", i)),
Value::Number(i) => fmt.write_fmt(format_args!("{}", i)),
Value::String(s) => match s.to_str() {
Ok(s) => fmt.write_fmt(format_args!("{:?}", s)),
Err(_) => fmt.write_fmt(format_args!("{:?}", s.as_bytes())),
},
Value::Table(t) => {
if let Ok(true) = t.contains_key(1) {
// Treat as list
let mut list = fmt.debug_list();
for value in t.clone().sequence_values() {
match value {
Ok(value) => {
list.entry(&ValueWrapper(value));
}
Err(err) => {
list.entry(&err);
}
}
}
list.finish()
} else {
// Treat as map
let mut map = fmt.debug_map();
for pair in t.clone().pairs::<Value, Value>() {
match pair {
Ok(pair) => {
map.entry(&ValueWrapper(pair.0), &ValueWrapper(pair.1));
}
Err(err) => {
log::error!("error while retrieving map entry: {}", err);
break;
}
}
}
map.finish()
}
}
Value::UserData(_) | Value::LightUserData(_) => fmt.write_str("userdata"),
Value::Thread(_) => fmt.write_str("thread"),
Value::Function(_) => fmt.write_str("function"),
Value::Error(e) => fmt.write_fmt(format_args!("error {}", e)),
}
}
}
impl<'de, 'lua> IntoDeserializer<'de, Error> for ValueWrapper<'lua> { impl<'de, 'lua> IntoDeserializer<'de, Error> for ValueWrapper<'lua> {
type Deserializer = Self; type Deserializer = Self;
@ -466,13 +516,14 @@ impl<'de, 'lua> Deserializer<'de> for ValueWrapper<'lua> {
fn deserialize_enum<V>( fn deserialize_enum<V>(
self, self,
_name: &str, name: &str,
_variants: &'static [&'static str], variants: &'static [&'static str],
visitor: V, visitor: V,
) -> Result<V::Value, Error> ) -> Result<V::Value, Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
let saved_value = self.0.clone();
let (variant, value) = match self.0 { let (variant, value) = match self.0 {
Value::Table(t) => { Value::Table(t) => {
let mut iter = t.pairs::<String, Value>(); let mut iter = t.pairs::<String, Value>();
@ -519,7 +570,29 @@ impl<'de, 'lua> Deserializer<'de> for ValueWrapper<'lua> {
} }
}; };
visitor.visit_enum(EnumDeserializer { variant, value }) visitor
.visit_enum(EnumDeserializer { variant, value })
.map_err(|err| {
let mut variants = variants.to_vec();
variants.sort();
let mut allowed = String::new();
for varname in variants.into_iter() {
if !allowed.is_empty() {
allowed.push_str(", ");
}
allowed.push('`');
allowed.push_str(varname);
allowed.push('`');
}
Error::custom(format!(
"while processing an enum of type `{}` and \
value {:?}\nwhich has allowed variants {}\n{}",
name,
ValueWrapper(saved_value),
allowed,
err
))
})
} }
fn deserialize_map<V>(self, v: V) -> Result<V::Value, Self::Error> fn deserialize_map<V>(self, v: V) -> Result<V::Value, Self::Error>
@ -545,13 +618,18 @@ impl<'de, 'lua> Deserializer<'de> for ValueWrapper<'lua> {
V: Visitor<'de>, V: Visitor<'de>,
{ {
match self.0 { match self.0 {
Value::Table(t) => match visit_table(t, v, Some(struct_name), Some(fields)) { Value::Table(t) => {
let table = t.clone();
match visit_table(t, v, Some(struct_name), Some(fields)) {
Ok(v) => Ok(v), Ok(v) => Ok(v),
Err(err) => Err(Error::custom(format!( Err(err) => Err(Error::custom(format!(
"{} (while processing a struct of type `{}`)", "while processing a struct of type `{}` with value:\n{:#?}\n{}",
err, struct_name struct_name,
ValueWrapper(Value::Table(table)),
err,
))), ))),
}, }
}
_ => Err(serde::de::Error::invalid_type( _ => Err(serde::de::Error::invalid_type(
unexpected(&self.0), unexpected(&self.0),
&"a map", &"a map",
@ -599,10 +677,9 @@ impl<'de, 'lua> VariantAccess<'de> for VariantDeserializer<'lua> {
{ {
match self.value { match self.value {
Some(value) => seed.deserialize(ValueWrapper(value)), Some(value) => seed.deserialize(ValueWrapper(value)),
None => Err(serde::de::Error::invalid_type( None => Err(Error::custom(format!(
Unexpected::UnitVariant, "Expected a variant with parameters but got a unit variant instead"
&"newtype variant", ))),
)),
} }
} }