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!(
|
quote!(
|
||||||
#ident:
|
#ident:
|
||||||
<#ty>::from_dynamic(value, options)
|
<#ty>::from_dynamic(value, options)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(
|
||||||
type_name: #struct_name,
|
#struct_name,
|
||||||
field_name: #name,
|
#name,
|
||||||
error: source.to_string(),
|
obj))?,
|
||||||
})?,
|
|
||||||
)
|
)
|
||||||
} else if let Some(try_from) = &self.try_from {
|
} else if let Some(try_from) = &self.try_from {
|
||||||
match &self.allow_default {
|
match &self.allow_default {
|
||||||
@ -120,16 +119,16 @@ impl<'a> FieldInfo<'a> {
|
|||||||
Some(v) => {
|
Some(v) => {
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
let target = <#try_from>::from_dynamic(v, options)
|
let target = <#try_from>::from_dynamic(v, options)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(
|
||||||
type_name: #struct_name,
|
#struct_name,
|
||||||
field_name: #name,
|
#name,
|
||||||
error: source.to_string(),
|
obj,
|
||||||
})?;
|
))?;
|
||||||
<#ty>::try_from(target)
|
<#ty>::try_from(target)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| wezterm_dynamic::Error::ErrorInField{
|
||||||
type_name: #struct_name,
|
type_name:#struct_name,
|
||||||
field_name: #name,
|
field_name:#name,
|
||||||
error: source.to_string(),
|
error: format!("{:#}", source)
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -144,16 +143,16 @@ impl<'a> FieldInfo<'a> {
|
|||||||
Some(v) => {
|
Some(v) => {
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
let target = <#try_from>::from_dynamic(v, options)
|
let target = <#try_from>::from_dynamic(v, options)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(
|
||||||
type_name: #struct_name,
|
#struct_name,
|
||||||
field_name: #name,
|
#name,
|
||||||
error: source.to_string(),
|
obj,
|
||||||
})?;
|
))?;
|
||||||
<#ty>::try_from(target)
|
<#ty>::try_from(target)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| wezterm_dynamic::Error::ErrorInField{
|
||||||
type_name: #struct_name,
|
type_name:#struct_name,
|
||||||
field_name: #name,
|
field_name:#name,
|
||||||
error: source.to_string(),
|
error: format!("{:#}", source),
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -167,16 +166,16 @@ impl<'a> FieldInfo<'a> {
|
|||||||
#ident: {
|
#ident: {
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
let target = <#try_from>::from_dynamic(obj.get_by_str(#name).unwrap_or(&Value::Null), options)
|
let target = <#try_from>::from_dynamic(obj.get_by_str(#name).unwrap_or(&Value::Null), options)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(
|
||||||
type_name: #struct_name,
|
#struct_name,
|
||||||
field_name: #name,
|
#name,
|
||||||
error: source.to_string(),
|
obj,
|
||||||
})?;
|
))?;
|
||||||
<#ty>::try_from(target)
|
<#ty>::try_from(target)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| wezterm_dynamic::Error::ErrorInField{
|
||||||
type_name: #struct_name,
|
type_name:#struct_name,
|
||||||
field_name: #name,
|
field_name:#name,
|
||||||
error: source.to_string(),
|
error: format!("{:#}", source),
|
||||||
})?
|
})?
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -189,11 +188,11 @@ impl<'a> FieldInfo<'a> {
|
|||||||
#ident: match obj.get_by_str(#name) {
|
#ident: match obj.get_by_str(#name) {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
<#ty>::from_dynamic(v, options)
|
<#ty>::from_dynamic(v, options)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(
|
||||||
type_name: #struct_name,
|
#struct_name,
|
||||||
field_name: #name,
|
#name,
|
||||||
error: source.to_string(),
|
obj,
|
||||||
})?
|
))?
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
<#ty>::default()
|
<#ty>::default()
|
||||||
@ -206,11 +205,11 @@ impl<'a> FieldInfo<'a> {
|
|||||||
#ident: match obj.get_by_str(#name) {
|
#ident: match obj.get_by_str(#name) {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
<#ty>::from_dynamic(v, options)
|
<#ty>::from_dynamic(v, options)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(
|
||||||
type_name: #struct_name,
|
#struct_name,
|
||||||
field_name: #name,
|
#name,
|
||||||
error: source.to_string(),
|
obj,
|
||||||
})?
|
))?
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
#default()
|
#default()
|
||||||
@ -225,11 +224,7 @@ impl<'a> FieldInfo<'a> {
|
|||||||
obj.get_by_str(#name).unwrap_or(&Value::Null),
|
obj.get_by_str(#name).unwrap_or(&Value::Null),
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
.map_err(|source| wezterm_dynamic::Error::ErrorInField {
|
.map_err(|source| source.field_context(#struct_name, #name, obj))?,
|
||||||
type_name: #struct_name,
|
|
||||||
field_name: #name,
|
|
||||||
error: source.to_string(),
|
|
||||||
})?,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction};
|
use crate::fromdynamic::{FromDynamicOptions, UnknownFieldAction};
|
||||||
|
use crate::object::Object;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@ -33,12 +34,18 @@ pub enum Error {
|
|||||||
type_name: &'static str,
|
type_name: &'static str,
|
||||||
num_keys: usize,
|
num_keys: usize,
|
||||||
},
|
},
|
||||||
#[error("Error in {}::{}: {:#}", .type_name, .field_name, .error)]
|
#[error("Error processing {}::{}: {:#}", .type_name, .field_name, .error)]
|
||||||
ErrorInField {
|
ErrorInField {
|
||||||
type_name: &'static str,
|
type_name: &'static str,
|
||||||
field_name: &'static str,
|
field_name: &'static str,
|
||||||
error: String,
|
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)]
|
#[error("`{}` is not a valid type to use as a field name in `{}`", .key_type, .type_name)]
|
||||||
InvalidFieldType {
|
InvalidFieldType {
|
||||||
type_name: &'static str,
|
type_name: &'static str,
|
||||||
@ -147,32 +154,94 @@ impl Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !fields.is_empty() {
|
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;
|
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 {
|
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
|
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 {
|
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 {
|
pub struct Object {
|
||||||
inner: BTreeMap<Value, Value>,
|
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 {
|
impl Ord for Object {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
let self_ptr = self as *const Self;
|
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
|
/// Value is intended to be convertible to the same set
|
||||||
/// of types as Lua and is a superset of the types possible
|
/// of types as Lua and is a superset of the types possible
|
||||||
/// in TOML and JSON.
|
/// in TOML and JSON.
|
||||||
#[derive(Clone, Debug, PartialEq, Hash, Eq, Ord, PartialOrd)]
|
#[derive(Clone, PartialEq, Hash, Eq, Ord, PartialOrd)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Null,
|
Null,
|
||||||
Bool(bool),
|
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 {
|
impl Value {
|
||||||
pub fn variant_name(&self) -> &str {
|
pub fn variant_name(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
|
Loading…
Reference in New Issue
Block a user