use deserr::{
DeserializeError, Deserr, ErrorKind, IntoValue, MergeWithError, Sequence, ValueKind,
ValuePointer, ValuePointerRef,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
use std::num::ParseIntError;
use std::ops::ControlFlow;
use std::str::FromStr;
#[derive(Debug, PartialEq, Eq)]
pub struct DefaultError {
pub location: ValuePointer,
pub content: DefaultErrorContent,
}
#[derive(Debug, PartialEq, Eq)]
pub enum DefaultErrorContent {
Unexpected(String),
MissingField(String),
IncorrectValueKind {
accepted: Vec<ValueKind>,
},
UnknownKey {
key: String,
accepted: Vec<String>,
},
UnknownValue {
value: String,
accepted: Vec<String>,
},
CustomMissingField(usize),
BadSequenceLen {
actual: usize,
expected: usize,
},
Validation,
}
impl MergeWithError<DefaultError> for DefaultError {
fn merge(
_self_: Option<Self>,
other: DefaultError,
_merge_location: ValuePointerRef,
) -> ControlFlow<Self, Self> {
ControlFlow::Break(other)
}
}
impl DeserializeError for DefaultError {
fn error<V: IntoValue>(
_self_: Option<Self>,
error: ErrorKind<V>,
location: ValuePointerRef,
) -> ControlFlow<Self, Self> {
let content = match error {
ErrorKind::IncorrectValueKind {
actual: _,
accepted,
} => DefaultErrorContent::IncorrectValueKind {
accepted: accepted.to_vec(),
},
ErrorKind::MissingField { field } => {
DefaultErrorContent::MissingField(field.to_string())
}
ErrorKind::UnknownKey { key, accepted } => DefaultErrorContent::UnknownKey {
key: key.to_string(),
accepted: accepted
.iter()
.map(|accepted| accepted.to_string())
.collect(),
},
ErrorKind::UnknownValue { value, accepted } => DefaultErrorContent::UnknownValue {
value: value.to_string(),
accepted: accepted
.iter()
.map(|accepted| accepted.to_string())
.collect(),
},
ErrorKind::BadSequenceLen { actual, expected } => DefaultErrorContent::BadSequenceLen {
actual: actual.len(),
expected,
},
ErrorKind::Unexpected { msg } => DefaultErrorContent::Unexpected(msg),
};
ControlFlow::Break(Self {
location: location.to_owned(),
content,
})
}
}
impl std::convert::From<ParseIntError> for DefaultError {
fn from(value: ParseIntError) -> Self {
Self {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::Unexpected(value.to_string()),
}
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[serde(tag = "sometag")]
#[deserr(tag = "sometag")]
enum Tag {
A,
B,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
enum Untagged {
A,
B,
}
fn unknown_field_error_gen<E>(k: &str, accepted: &[&str], location: deserr::ValuePointerRef) -> E
where
E: DeserializeError,
{
match E::error::<serde_json::Value>(None, ErrorKind::UnknownKey { key: k, accepted }, location)
{
ControlFlow::Continue(e) => e,
ControlFlow::Break(e) => e,
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(deny_unknown_fields = unknown_field_error_gen)]
struct Example {
x: String,
t1: Tag,
t2: Box<Tag>,
ut1: Untagged,
ut2: Box<Untagged>,
n: Box<Nested>,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
struct Nested {
#[deserr(default)]
y: Option<Vec<String>>,
#[deserr(default)]
z: Option<String>,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError)]
struct StructWithDefaultAttr {
x: bool,
#[serde(default = "create_default_u8")]
#[deserr(default = create_default_u8())]
y: u8,
#[serde(default = "create_default_option_string")]
#[deserr(default = create_default_option_string())]
z: Option<String>,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError)]
struct StructWithTraitDefaultAttr {
#[serde(default)]
#[deserr(default)]
y: u8,
}
fn create_default_u8() -> u8 {
152
}
fn create_default_option_string() -> Option<String> {
Some("hello".to_owned())
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[serde(tag = "t")]
#[deserr(error = DefaultError, tag = "t")]
enum EnumWithOptionData {
A {
#[deserr(default)]
x: Option<u8>,
},
B {
#[serde(default = "create_default_option_string")]
#[deserr(default = create_default_option_string())]
x: Option<String>,
#[serde(default = "create_default_u8")]
#[deserr(default = create_default_u8())]
y: u8,
},
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, rename_all = camelCase)]
#[serde(rename_all = "camelCase")]
struct RenamedAllCamelCaseStruct {
renamed_field: bool,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, rename_all = lowercase)]
#[serde(rename_all = "lowercase")]
struct RenamedAllLowerCaseStruct {
renamed_field: bool,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t", rename_all = camelCase)]
#[serde(tag = "t")]
#[serde(rename_all = "camelCase")]
enum RenamedAllCamelCaseEnum {
SomeField { my_field: bool },
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t")]
#[serde(tag = "t")]
enum RenamedAllFieldsCamelCaseEnum {
#[deserr(rename_all = camelCase)]
#[serde(rename_all = "camelCase")]
SomeField { my_field: bool },
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError)]
struct StructWithRenamedField {
#[deserr(rename = "renamed_field")]
#[serde(rename = "renamed_field")]
x: bool,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, rename_all = camelCase)]
struct StructWithRenamedFieldAndRenameAll {
#[deserr(rename = "renamed_field")]
#[serde(rename = "renamed_field")]
x: bool,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, deny_unknown_fields)]
#[serde(deny_unknown_fields)]
struct StructDenyUnknownFields {
x: bool,
}
fn unknown_field_error(k: &str, _accepted: &[&str], location: ValuePointerRef) -> DefaultError {
DefaultError {
location: location.to_owned(),
content: DefaultErrorContent::UnknownKey {
key: k.to_owned(),
accepted: vec!["don't know".to_string()],
},
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, deny_unknown_fields = unknown_field_error)]
#[serde(deny_unknown_fields)]
struct StructDenyUnknownFieldsCustom {
x: bool,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t", deny_unknown_fields)]
#[serde(tag = "t", deny_unknown_fields)]
enum EnumDenyUnknownFields {
SomeField { my_field: bool },
Other { my_field: bool, y: u8 },
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t", deny_unknown_fields = unknown_field_error)]
#[serde(tag = "t", deny_unknown_fields)]
enum EnumDenyUnknownFieldsCustom {
SomeField { my_field: bool },
Other { my_field: bool, y: u8 },
}
fn missing_x_field(_field_name: &str, location: ValuePointerRef) -> DefaultError {
DefaultError {
location: location.to_owned(),
content: DefaultErrorContent::MissingField("lol".to_string()),
}
}
fn custom_mising_field(_field_name: &str, location: ValuePointerRef) -> DefaultError {
DefaultError {
location: location.to_owned(),
content: DefaultErrorContent::CustomMissingField(1),
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError)]
struct StructMissingFieldError {
#[deserr(missing_field_error = missing_x_field)]
x: bool,
#[deserr(missing_field_error = custom_mising_field)]
y: bool,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t")]
enum EnumMissingFieldError {
A {
#[deserr(missing_field_error = custom_mising_field)]
x: bool,
},
B {
x: bool,
},
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t")]
#[serde(tag = "t")]
enum EnumRenamedVariant {
#[serde(rename = "Apple")]
#[deserr(rename = "Apple")]
A { x: bool },
#[serde(rename = "Beta")]
#[deserr(rename = "Beta")]
B,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t")]
#[serde(tag = "t")]
enum EnumRenamedField {
A {
#[deserr(rename = "Xylem")]
#[serde(rename = "Xylem")]
x: bool,
},
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError, tag = "t")]
#[serde(tag = "t")]
enum EnumRenamedAllVariant {
#[deserr(rename_all = camelCase)]
#[serde(rename_all = "camelCase")]
P { water_potential: bool },
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(error = DefaultError)]
struct Generic<A> {
some_field: A,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(where_predicate = __Deserr_E: MergeWithError<DefaultError>, where_predicate = A: Deserr<DefaultError>)]
struct Generic2<A> {
#[deserr(error = DefaultError, default)]
some_field: Option<A>,
}
fn map_option(x: Option<u8>) -> Option<u8> {
match x {
Some(0) => None,
Some(x) => Some(x),
None => Some(1),
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
struct FieldMap {
#[deserr(default, map = map_option)]
some_field: Option<u8>,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Deserr)]
#[deserr(where_predicate = Option<u8> : Deserr<__Deserr_E>)]
struct FieldConditions {
#[deserr(default)]
some_field: Option<u8>,
}
pub enum NeverError {}
fn parse_hello(b: bool) -> Result<Hello, NeverError> {
match b {
true => Ok(Hello::A),
false => Ok(Hello::B),
}
}
fn parse_hello2(b: bool) -> Result<Hello2, NeverError> {
match b {
true => Ok(Hello2::A),
false => Ok(Hello2::B),
}
}
fn parse_hello3(b: &str) -> Result<Hello3, DefaultError> {
match b {
"A" => Ok(Hello3::A),
"B" => Ok(Hello3::B),
_ => Err(DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::Unexpected("Hello3 from error".to_string()),
}),
}
}
#[derive(Debug, PartialEq, Deserr)]
#[deserr(try_from(bool) = parse_hello -> NeverError)]
enum Hello {
A,
B,
}
#[derive(Debug, PartialEq, Deserr)]
#[deserr(error = DefaultError, try_from(bool) = parse_hello2 -> NeverError)]
enum Hello2 {
A,
B,
}
#[derive(Debug, PartialEq, Deserr)]
#[deserr(try_from(& String) = parse_hello3 -> DefaultError)]
enum Hello3 {
A,
B,
}
#[derive(Debug, PartialEq, Deserr)]
#[deserr(where_predicate = Hello: Deserr<__Deserr_E>)]
struct ContainsHello {
_x: Hello,
}
#[derive(Debug, PartialEq, Deserr)]
#[deserr(error = DefaultError)]
struct ContainsHello2 {
_x: Hello,
}
#[derive(Debug, PartialEq, Deserr)]
struct ContainsHello3 {
#[deserr(needs_predicate)]
_x: Hello,
}
struct MyValidationError;
impl MergeWithError<MyValidationError> for DefaultError {
fn merge(
_self_: Option<Self>,
_other: MyValidationError,
merge_location: ValuePointerRef,
) -> ControlFlow<Self, Self> {
ControlFlow::Break(DefaultError {
location: merge_location.to_owned(),
content: DefaultErrorContent::Validation,
})
}
}
fn validate_it(x: Validated, _location: ValuePointerRef) -> Result<Validated, MyValidationError> {
if x.x as u16 > x.y {
Err(MyValidationError)
} else {
Ok(x)
}
}
fn validate_it2(
x: Validated2,
_location: ValuePointerRef,
) -> Result<Validated2, MyValidationError> {
if x.x as u16 > x.y {
Err(MyValidationError)
} else {
Ok(x)
}
}
#[derive(Debug, Deserr)]
#[deserr(validate = validate_it -> MyValidationError)]
struct Validated {
x: u8,
y: u16,
}
#[derive(Debug, Deserr)]
#[deserr(error = DefaultError, validate = validate_it2 -> MyValidationError)]
struct Validated2 {
x: u8,
y: u16,
}
#[derive(Debug, PartialEq, Eq, Deserr)]
pub struct From {
#[deserr(try_from(&String) = u8_from_str -> MyParseIntError)]
x: u8,
y: u16,
}
pub struct MyParseIntError(std::num::ParseIntError);
fn usize_from_str(x: &str) -> Result<usize, MyParseIntError> {
usize::from_str(x).map_err(MyParseIntError)
}
fn u8_from_str(x: &str) -> Result<u8, MyParseIntError> {
u8::from_str(x).map_err(MyParseIntError)
}
impl MergeWithError<MyParseIntError> for DefaultError {
fn merge(
_self_: Option<Self>,
other: MyParseIntError,
merge_location: ValuePointerRef,
) -> ControlFlow<Self, Self> {
ControlFlow::Break(DefaultError {
location: merge_location.to_owned(),
content: DefaultErrorContent::Unexpected(other.0.to_string()),
})
}
}
#[derive(Debug, PartialEq, Eq, Deserr)]
pub struct From2 {
#[deserr(try_from(&String) = u8_from_str -> MyParseIntError)]
x: u8,
#[deserr(default = 3, try_from(&String) = usize_from_str -> MyParseIntError)]
y: usize,
}
impl MergeWithError<NeverError> for DefaultError {
fn merge(
_self_: Option<Self>,
_other: NeverError,
_merge_location: ValuePointerRef,
) -> ControlFlow<Self, Self> {
unreachable!()
}
}
#[derive(Debug, PartialEq, Eq, Deserialize, Deserr)]
pub struct Skipped1 {
#[deserr(skip)]
#[serde(skip)]
x: u8,
#[deserr(skip)]
#[serde(skip)]
y: Option<u8>,
z: bool,
}
fn default_skipped_x() -> u8 {
1
}
fn default_skipped_y() -> Option<u8> {
Some(3)
}
#[derive(Debug, PartialEq, Eq, Deserialize, Deserr)]
#[deserr(deny_unknown_fields)]
pub struct Skipped2 {
#[deserr(default = default_skipped_x(), skip)]
#[serde(default = "default_skipped_x", skip)]
x: u8,
#[deserr(skip, default = default_skipped_y())]
#[serde(skip, default = "default_skipped_y")]
y: Option<u8>,
z: bool,
}
#[track_caller]
fn compare_with_serde_roundtrip<T>(x: T)
where
T: Serialize + Deserr<DefaultError> + PartialEq + std::fmt::Debug,
{
let json = serde_json::to_value(&x).unwrap();
let result: T = deserr::deserialize(json).unwrap();
assert_eq!(result, x);
}
#[track_caller]
fn compare_with_serde<T>(j: &str)
where
T: DeserializeOwned + Deserr<DefaultError> + PartialEq + std::fmt::Debug,
{
let json: Value = serde_json::from_str(j).unwrap();
let actual_serde: Result<T, _> = serde_json::from_str(j);
let actual_deserr: Result<T, _> = deserr::deserialize(json);
match (actual_serde, actual_deserr) {
(Ok(actual_serde), Ok(actual_deserr)) => {
assert_eq!(actual_deserr, actual_serde);
}
(Err(_), Err(_)) => {}
(Ok(_), Err(e)) => {
panic!("deserr fails to deserialize but serde does not with error: {e:?}")
}
(Err(e), Ok(_)) => {
panic!("serde fails to deserialize but deserr does not with error: {e:?}")
}
}
}
#[track_caller]
fn assert_error_matches<T, E>(j: &str, expected: E)
where
E: DeserializeError + PartialEq + std::fmt::Debug,
T: Deserr<E> + std::fmt::Debug,
{
let json: Value = serde_json::from_str(j).unwrap();
let actual: E = deserr::deserialize::<T, _, _>(json).unwrap_err();
assert_eq!(actual, expected);
}
#[track_caller]
fn assert_ok_matches<T, E>(j: &str, expected: T)
where
E: DeserializeError + PartialEq + std::fmt::Debug,
T: Deserr<E> + std::fmt::Debug + PartialEq,
{
let json: Value = serde_json::from_str(j).unwrap();
let actual: T = deserr::deserialize::<T, _, E>(json).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn test_de() {
compare_with_serde_roundtrip(Example {
x: "X".to_owned(),
t1: Tag::A,
t2: Box::new(Tag::B),
ut1: Untagged::A,
ut2: Box::new(Untagged::B),
n: Box::new(Nested {
y: Some(vec!["Y".to_owned(), "Y".to_owned()]),
z: None,
}),
});
compare_with_serde_roundtrip(RenamedAllCamelCaseStruct {
renamed_field: true,
});
compare_with_serde_roundtrip(RenamedAllLowerCaseStruct {
renamed_field: true,
});
compare_with_serde_roundtrip(RenamedAllCamelCaseEnum::SomeField { my_field: true });
compare_with_serde_roundtrip(RenamedAllFieldsCamelCaseEnum::SomeField { my_field: true });
compare_with_serde_roundtrip(StructWithDefaultAttr {
x: true,
y: 1,
z: None,
});
compare_with_serde::<StructWithDefaultAttr>(
r#"{
"x": true,
"y": 10
}
"#,
);
compare_with_serde::<StructWithTraitDefaultAttr>(r#"{ }"#);
compare_with_serde_roundtrip(EnumWithOptionData::A { x: None });
compare_with_serde::<EnumWithOptionData>(r#"{ "t": "A" }"#);
compare_with_serde::<EnumWithOptionData>(r#"{ "t": "B" }"#);
compare_with_serde::<EnumWithOptionData>(
r#"{
"t": "B",
"x": null,
"y": 10
}
"#,
);
compare_with_serde_roundtrip(StructWithRenamedField { x: true });
compare_with_serde_roundtrip(StructWithRenamedFieldAndRenameAll { x: true });
assert_ok_matches(
r#"{ "renamed_field": true }"#,
StructWithRenamedFieldAndRenameAll { x: true },
);
compare_with_serde::<StructDenyUnknownFields>(
r#"{
"x": true,
"y": 8
}
"#,
);
compare_with_serde_roundtrip(StructDenyUnknownFields { x: true });
compare_with_serde::<EnumDenyUnknownFields>(
r#"{
"t": "SomeField",
"my_field": true,
"other": true
}
"#,
);
compare_with_serde::<EnumDenyUnknownFields>(
r#"{
"my_field": true,
"other": true
}
"#,
);
compare_with_serde_roundtrip(EnumDenyUnknownFields::SomeField { my_field: true });
compare_with_serde_roundtrip(EnumDenyUnknownFields::Other {
my_field: true,
y: 8,
});
compare_with_serde::<StructDenyUnknownFieldsCustom>(
r#"{
"x": true,
"y": 8
}
"#,
);
assert_error_matches::<StructDenyUnknownFieldsCustom, DefaultError>(
r#"{
"x": true,
"y": 8
}
"#,
unknown_field_error("y", &[], ValuePointerRef::Origin),
);
compare_with_serde::<EnumDenyUnknownFieldsCustom>(
r#"{
"t": "SomeField",
"my_field": true,
"other": true
}
"#,
);
assert_error_matches::<EnumDenyUnknownFieldsCustom, DefaultError>(
r#"{
"t": "SomeField",
"my_field": true,
"other": true
}
"#,
unknown_field_error("other", &[], ValuePointerRef::Origin),
);
assert_error_matches::<StructMissingFieldError, DefaultError>(
r#"{
"y": true
}
"#,
DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::MissingField("lol".to_string()),
},
);
assert_error_matches::<StructMissingFieldError, DefaultError>(
r#"{
"x": true
}
"#,
DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::CustomMissingField(1),
},
);
assert_error_matches::<EnumMissingFieldError, DefaultError>(
r#"{
"t": "A"
}
"#,
DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::CustomMissingField(1),
},
);
assert_error_matches::<EnumMissingFieldError, DefaultError>(
r#"{
"t": "B"
}
"#,
DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::MissingField("x".to_owned()),
},
);
compare_with_serde_roundtrip(EnumRenamedVariant::A { x: true });
compare_with_serde_roundtrip(EnumRenamedVariant::B);
compare_with_serde_roundtrip(EnumRenamedField::A { x: true });
compare_with_serde_roundtrip(EnumRenamedAllVariant::P {
water_potential: true,
});
compare_with_serde_roundtrip(Generic::<EnumRenamedAllVariant> {
some_field: EnumRenamedAllVariant::P {
water_potential: true,
},
});
assert_error_matches::<EnumDenyUnknownFieldsCustom, DefaultError>(
r#"{
"t": "SomeField",
"my_field": true,
"other": true
}
"#,
unknown_field_error("other", &[], ValuePointerRef::Origin),
);
assert_ok_matches::<Hello, DefaultError>("true", Hello::A);
assert_error_matches::<Validated, DefaultError>(
r#"{
"x": 2,
"y": 1
}
"#,
DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::Validation,
},
);
assert_ok_matches::<FieldMap, DefaultError>(
r#"{ "some_field": null }"#,
FieldMap {
some_field: Some(1),
},
);
assert_ok_matches::<FieldMap, DefaultError>(
r#"{ }"#,
FieldMap {
some_field: Some(1),
},
);
assert_ok_matches::<FieldMap, DefaultError>(
r#"{ "some_field": 0 }"#,
FieldMap { some_field: None },
);
assert_ok_matches::<FieldMap, DefaultError>(
r#"{ "some_field": 2 }"#,
FieldMap {
some_field: Some(2),
},
);
assert_ok_matches::<From, DefaultError>(r#"{ "x": "2", "y": 14 }"#, From { x: 2, y: 14 });
assert_error_matches::<From, DefaultError>(
r#"{ "x": "hello", "y": 14 }"#,
DefaultError {
location: ValuePointerRef::Origin.push_key("x").to_owned(),
content: DefaultErrorContent::Unexpected(String::from("invalid digit found in string")),
},
);
assert_error_matches::<From, DefaultError>(
r#"{ "x": 2, "y": 14 }"#,
DefaultError {
location: ValuePointerRef::Origin.push_key("x").to_owned(),
content: DefaultErrorContent::IncorrectValueKind {
accepted: vec![ValueKind::String],
},
},
);
assert_ok_matches::<From2, DefaultError>(r#"{ "x": "2", "y": "14" }"#, From2 { x: 2, y: 14 });
assert_ok_matches::<From2, DefaultError>(r#"{ "x": "2" }"#, From2 { x: 2, y: 3 });
compare_with_serde::<Skipped1>(
r#"{
"x": 89,
"z": true
}
"#,
);
compare_with_serde::<Skipped2>(
r#"{
"z": true
}
"#,
);
assert_error_matches::<Skipped2, DefaultError>(
r#"{ "x": 78, "z": true }"#,
DefaultError {
location: ValuePointerRef::Origin.to_owned(),
content: DefaultErrorContent::UnknownKey {
key: "x".to_owned(),
accepted: vec!["z".to_owned()],
},
},
);
}