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:
parent
c7387490a6
commit
42a7c1d481
@ -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))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user