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

dynamic: improve error messaging

This commit is contained in:
Wez Furlong 2022-05-18 13:39:51 -07:00
parent c7387490a6
commit 42a7c1d481
4 changed files with 154 additions and 69 deletions

View File

@ -106,11 +106,10 @@ impl<'a> FieldInfo<'a> {
quote!(
#ident:
<#ty>::from_dynamic(value, options)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?,
.map_err(|source| source.field_context(
#struct_name,
#name,
obj))?,
)
} else if let Some(try_from) = &self.try_from {
match &self.allow_default {
@ -120,16 +119,16 @@ impl<'a> FieldInfo<'a> {
Some(v) => {
use std::convert::TryFrom;
let target = <#try_from>::from_dynamic(v, options)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?;
.map_err(|source| source.field_context(
#struct_name,
#name,
obj,
))?;
<#ty>::try_from(target)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
.map_err(|source| wezterm_dynamic::Error::ErrorInField{
type_name:#struct_name,
field_name:#name,
error: format!("{:#}", source)
})?
}
None => {
@ -144,16 +143,16 @@ impl<'a> FieldInfo<'a> {
Some(v) => {
use std::convert::TryFrom;
let target = <#try_from>::from_dynamic(v, options)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?;
.map_err(|source| source.field_context(
#struct_name,
#name,
obj,
))?;
<#ty>::try_from(target)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
.map_err(|source| wezterm_dynamic::Error::ErrorInField{
type_name:#struct_name,
field_name:#name,
error: format!("{:#}", source),
})?
}
None => {
@ -167,16 +166,16 @@ impl<'a> FieldInfo<'a> {
#ident: {
use std::convert::TryFrom;
let target = <#try_from>::from_dynamic(obj.get_by_str(#name).unwrap_or(&Value::Null), options)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?;
.map_err(|source| source.field_context(
#struct_name,
#name,
obj,
))?;
<#ty>::try_from(target)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
.map_err(|source| wezterm_dynamic::Error::ErrorInField{
type_name:#struct_name,
field_name:#name,
error: format!("{:#}", source),
})?
},
)
@ -189,11 +188,11 @@ impl<'a> FieldInfo<'a> {
#ident: match obj.get_by_str(#name) {
Some(v) => {
<#ty>::from_dynamic(v, options)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?
.map_err(|source| source.field_context(
#struct_name,
#name,
obj,
))?
}
None => {
<#ty>::default()
@ -206,11 +205,11 @@ impl<'a> FieldInfo<'a> {
#ident: match obj.get_by_str(#name) {
Some(v) => {
<#ty>::from_dynamic(v, options)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?
.map_err(|source| source.field_context(
#struct_name,
#name,
obj,
))?
}
None => {
#default()
@ -225,11 +224,7 @@ impl<'a> FieldInfo<'a> {
obj.get_by_str(#name).unwrap_or(&Value::Null),
options
)
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
type_name: #struct_name,
field_name: #name,
error: source.to_string(),
})?,
.map_err(|source| source.field_context(#struct_name, #name, obj))?,
)
}
}

View File

@ -1,4 +1,5 @@
use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction};
use crate::object::Object;
use crate::value::Value;
use thiserror::Error;
@ -33,12 +34,18 @@ pub enum Error {
type_name: &'static str,
num_keys: usize,
},
#[error("Error in {}::{}: {:#}", .type_name, .field_name, .error)]
#[error("Error processing {}::{}: {:#}", .type_name, .field_name, .error)]
ErrorInField {
type_name: &'static str,
field_name: &'static str,
error: String,
},
#[error("Error processing {} (types: {}) {:#}", .field_name.join("."), .type_name.join(", "), .error)]
ErrorInNestedField {
type_name: Vec<&'static str>,
field_name: Vec<&'static str>,
error: String,
},
#[error("`{}` is not a valid type to use as a field name in `{}`", .key_type, .type_name)]
InvalidFieldType {
type_name: &'static str,
@ -147,32 +154,94 @@ impl Error {
}
}
if !fields.is_empty() {
if suggestions.is_empty() {
message.push_str("Possible items are ");
} else {
message.push_str(" Other possible items are ");
}
let limit = 5;
for (idx, candidate) in fields.iter().enumerate() {
if idx > 0 {
message.push_str(", ");
}
message.push('`');
message.push_str(candidate);
message.push('`');
if idx > limit {
break;
}
}
if fields.len() > limit {
message.push_str(&format!(" and {} others", fields.len() - limit));
message.push_str(
" There are too many alternatives to list here; consult the documentation!",
);
} else {
if suggestions.is_empty() {
message.push_str("Possible alternatives are ");
} else if suggestions.len() == 1 {
message.push_str(" The other option is ");
} else {
message.push_str(" Other alternatives are ");
}
for (idx, candidate) in fields.iter().enumerate() {
if idx > 0 {
message.push_str(", ");
}
message.push('`');
message.push_str(candidate);
message.push('`');
}
}
message.push('.');
}
message
}
pub fn field_context(
self,
type_name: &'static str,
field_name: &'static str,
obj: &Object,
) -> Self {
let is_leaf = !matches!(self, Self::ErrorInField { .. });
fn add_obj_context(is_leaf: bool, obj: &Object, message: String) -> String {
if is_leaf {
// Show the object as context.
// However, some objects, like the main config, are very large and
// it isn't helpful to show that, so only include it when the context
// is more reasonable.
let obj_str = format!("{:#?}", obj);
if obj_str.len() > 128 || obj_str.lines().count() > 10 {
message
} else {
format!("{}.\n{}", message, obj_str)
}
} else {
message
}
}
match self {
Self::NoConversion { source_type, .. } if source_type == "Null" => Self::ErrorInField {
type_name,
field_name,
error: add_obj_context(is_leaf, obj, format!("missing field `{}`", field_name)),
},
Self::ErrorInField {
type_name: child_type,
field_name: child_field,
error,
} => Self::ErrorInNestedField {
type_name: vec![type_name, child_type],
field_name: vec![field_name, child_field],
error,
},
Self::ErrorInNestedField {
type_name: mut child_type,
field_name: mut child_field,
error,
} => Self::ErrorInNestedField {
type_name: {
child_type.insert(0, type_name);
child_type
},
field_name: {
child_field.insert(0, field_name);
child_field
},
error,
},
_ => Self::ErrorInField {
type_name,
field_name,
error: add_obj_context(is_leaf, obj, format!("{:#}", self)),
},
}
}
}
impl From<String> for Error {

View File

@ -65,7 +65,7 @@ impl<'a> std::hash::Hash for (dyn ObjectKeyTrait + 'a) {
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[derive(Clone, Default, PartialEq, Eq, Hash)]
pub struct Object {
inner: BTreeMap<Value, Value>,
}
@ -77,6 +77,12 @@ impl Object {
}
}
impl std::fmt::Debug for Object {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
self.inner.fmt(fmt)
}
}
impl Ord for Object {
fn cmp(&self, other: &Self) -> Ordering {
let self_ptr = self as *const Self;

View File

@ -6,7 +6,7 @@ use ordered_float::OrderedFloat;
/// Value is intended to be convertible to the same set
/// of types as Lua and is a superset of the types possible
/// in TOML and JSON.
#[derive(Clone, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)]
#[derive(Clone, PartialEq, Hash, Eq, Ord, PartialOrd)]
pub enum Value {
Null,
Bool(bool),
@ -24,6 +24,21 @@ impl Default for Value {
}
}
impl std::fmt::Debug for Value {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::String(s) => fmt.write_fmt(format_args!("{:?}", s)),
Self::Null => fmt.write_str("nil"),
Self::Bool(i) => i.fmt(fmt),
Self::I64(i) => i.fmt(fmt),
Self::U64(i) => i.fmt(fmt),
Self::F64(i) => i.fmt(fmt),
Self::Array(a) => a.fmt(fmt),
Self::Object(o) => o.fmt(fmt),
}
}
}
impl Value {
pub fn variant_name(&self) -> &str {
match self {