Skip to content

Commit c7c3eb0

Browse files
committed
feat: Improve errors from try_deserialize
Enables user code to handle missing and invalid fields after calling try_deserialize instead of having to match and search in the string returned in `ConfigError::Message`. Fixes #532
1 parent 69288bd commit c7c3eb0

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

src/error.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,34 @@ impl fmt::Display for Unexpected {
3737
}
3838
}
3939

40+
impl From<de::Unexpected<'_>> for Unexpected {
41+
fn from(value: de::Unexpected<'_>) -> Self {
42+
match value {
43+
de::Unexpected::Bool(b) => Self::Bool(b),
44+
de::Unexpected::Unsigned(u) => Self::U64(u),
45+
de::Unexpected::Signed(s) => Self::I64(s),
46+
de::Unexpected::Float(f) => Self::Float(f),
47+
de::Unexpected::Char(c) => Self::Str(c.into()),
48+
de::Unexpected::Str(s) => Self::Str(s.into()),
49+
de::Unexpected::Unit => Self::Unit,
50+
de::Unexpected::Seq => Self::Seq,
51+
de::Unexpected::Map => Self::Map,
52+
53+
// TODO Maybe add other items to unexpected if needed?
54+
//de::Unexpected::Bytes(items) => todo!(),
55+
//de::Unexpected::Option => todo!(),
56+
//de::Unexpected::NewtypeStruct => todo!(),
57+
//de::Unexpected::Enum => todo!(),
58+
//de::Unexpected::UnitVariant => todo!(),
59+
//de::Unexpected::NewtypeVariant => todo!(),
60+
//de::Unexpected::TupleVariant => todo!(),
61+
//de::Unexpected::StructVariant => todo!(),
62+
//de::Unexpected::Other(_) => todo!(),
63+
_ => Self::Unit,
64+
}
65+
}
66+
}
67+
4068
/// Represents all possible errors that can occur when working with
4169
/// configuration.
4270
#[non_exhaustive]
@@ -289,6 +317,24 @@ impl de::Error for ConfigError {
289317
fn custom<T: fmt::Display>(msg: T) -> Self {
290318
Self::Message(msg.to_string())
291319
}
320+
321+
fn missing_field(field: &'static str) -> Self {
322+
Self::NotFound(field.into())
323+
}
324+
325+
fn invalid_type(unexp: de::Unexpected<'_>, exp: &dyn de::Expected) -> Self {
326+
Self::Type {
327+
origin: None,
328+
unexpected: unexp.into(),
329+
// TODO A better way of doing this? Maybe make "expected" a Cow<str>?
330+
expected: exp.to_string().leak(),
331+
key: None,
332+
}
333+
}
334+
335+
fn invalid_value(unexp: de::Unexpected<'_>, exp: &dyn de::Expected) -> Self {
336+
<Self as de::Error>::invalid_type(unexp, exp)
337+
}
292338
}
293339

294340
impl ser::Error for ConfigError {

tests/testsuite/errors.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ fn test_get_missing_field() {
158158
let res = c.get::<InnerSettings>("inner");
159159
assert_data_eq!(
160160
res.unwrap_err().to_string(),
161-
str!["missing field `value2` for key `inner`"]
161+
str![[r#"configuration property "value2" not found for key `inner`"#]]
162162
);
163163
}
164164

@@ -184,7 +184,7 @@ fn test_get_missing_field_file() {
184184
let res = c.get::<InnerSettings>("inner");
185185
assert_data_eq!(
186186
res.unwrap_err().to_string(),
187-
str!["missing field `value2` for key `inner`"]
187+
str![[r#"configuration property "value2" not found for key `inner`"#]]
188188
);
189189
}
190190

@@ -436,7 +436,7 @@ fn test_deserialize_missing_field() {
436436
let res = c.try_deserialize::<Settings>();
437437
assert_data_eq!(
438438
res.unwrap_err().to_string(),
439-
str!["missing field `value2` for key `inner`"]
439+
str![[r#"configuration property "inner.value2" not found"#]]
440440
);
441441
}
442442

@@ -468,6 +468,6 @@ fn test_deserialize_missing_field_file() {
468468
let res = c.try_deserialize::<Settings>();
469469
assert_data_eq!(
470470
res.unwrap_err().to_string(),
471-
str!["missing field `value2` for key `inner`"]
471+
str![[r#"configuration property "inner.value2" not found"#]]
472472
);
473473
}

tests/testsuite/merge.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,6 @@ Settings {
366366
let res = cfg.try_deserialize::<Settings>();
367367
assert_data_eq!(
368368
res.unwrap_err().to_string(),
369-
str!["invalid type: integer `42`, expected struct Profile for key `profile.int_to_non_empty`"]
369+
str!["invalid type: 64-bit integer `42`, expected struct Profile for key `profile.int_to_non_empty`"]
370370
);
371371
}

0 commit comments

Comments
 (0)