use serde_json::json;
#[test]
fn test_basic() {
#[allow(dead_code)]
#[derive(serde_implicit_proc::Deserialize, Debug)]
enum MultiTypeTag {
StringVariant {
#[serde_implicit(tag)]
string_tag: String,
value: u32,
},
NumberVariant {
#[serde_implicit(tag)]
number_tag: u64,
value: String,
unique_field: String,
},
BoolVariant {
#[serde_implicit(tag)]
bool_tag: bool,
value: Vec<String>,
},
}
let res: Result<MultiTypeTag, _> =
serde_json::from_value(json!({ "string_tag": "", "value": 0 }));
assert!(res.is_ok());
let res: Result<MultiTypeTag, _> =
serde_json::from_value(json!({ "string_tag": "", "value": 0, "extra_field": "1234" }));
assert!(res.is_ok());
let res: Result<MultiTypeTag, _> =
serde_json::from_value(json!({ "string_tag": "", "value": "straing" }));
let err = res.unwrap_err();
assert!(
matches!(
&*err.to_string(),
r#"invalid type: string "straing", expected u32"#
),
"{err}",
);
let res: Result<MultiTypeTag, _> = serde_json::from_value(json!({ "string_tag": "" }));
let err = res.unwrap_err();
assert!(
matches!(&*err.to_string(), r#"missing field `value`"#),
"{err}",
);
}
#[test]
fn tuple_basic() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TupleEnum {
Case1(bool, u32),
Case2(u32),
}
let res: Result<TupleEnum, _> = serde_json::from_value(json!([true, 0]));
assert!(res.is_ok());
let res: Result<TupleEnum, _> = serde_json::from_value(json!([0]));
assert_eq!(res.unwrap(), TupleEnum::Case2(0));
}
#[test]
fn tuple_overlap() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TupleEnum {
Case1(bool, u32),
Case2(bool, bool),
}
let res: Result<TupleEnum, _> = serde_json::from_value(json!([true, true]));
let err = res.unwrap_err();
assert!(
matches!(
&*err.to_string(),
r#"TupleEnum::Case1: invalid type: boolean `true`, expected u32"#
),
"{err}",
);
}
#[test]
fn fallthrough_basic() {
#[allow(dead_code)]
#[derive(serde_implicit_proc::Deserialize, Debug)]
enum EnumWithFallThrough<T> {
Multiple {
#[serde_implicit(tag)]
variants: Vec<u32>,
},
Single {
one: T,
},
}
let res: Result<EnumWithFallThrough<u32>, _> = serde_json::from_value(json!(42));
res.unwrap();
let res: Result<EnumWithFallThrough<u32>, _> = serde_json::from_value(json!(42.5));
let err = res.unwrap_err();
assert!(
matches!(
&*err.to_string(),
r#"invalid type: floating point `42.5`, expected EnumWithFallThrough"#
),
"{err}",
);
let res: Result<EnumWithFallThrough<u32>, _> =
serde_json::from_value(json!({"variants": [32]}));
res.unwrap();
}
#[test]
fn tuple_custom_tag_position_middle() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TupleEnum {
Case1(bool, #[serde_implicit(tag)] String, u32),
Case2(u32, #[serde_implicit(tag)] bool),
}
let res: Result<TupleEnum, _> = serde_json::from_value(json!([false, "hello", 42]));
assert!(res.is_ok());
assert_eq!(
res.unwrap(),
TupleEnum::Case1(false, "hello".to_string(), 42)
);
let res: Result<TupleEnum, _> = serde_json::from_value(json!([99, true]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), TupleEnum::Case2(99, true));
}
#[test]
fn tuple_custom_tag_position_last() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TupleEnum {
Case1(u32, bool, #[serde_implicit(tag)] String),
Case2(#[serde_implicit(tag)] u64),
}
let res: Result<TupleEnum, _> = serde_json::from_value(json!([42, true, "tag"]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), TupleEnum::Case1(42, true, "tag".to_string()));
let res: Result<TupleEnum, _> = serde_json::from_value(json!([999]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), TupleEnum::Case2(999));
}
#[test]
fn tuple_mixed_tag_positions() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum MixedEnum {
First(String, u32),
Second(bool, #[serde_implicit(tag)] u32, String),
Third(u32, bool, #[serde_implicit(tag)] String),
}
let res: Result<MixedEnum, _> = serde_json::from_value(json!(["hello", 42]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), MixedEnum::First("hello".to_string(), 42));
let res: Result<MixedEnum, _> = serde_json::from_value(json!([true, 123, "world"]));
assert!(res.is_ok());
assert_eq!(
res.unwrap(),
MixedEnum::Second(true, 123, "world".to_string())
);
let res: Result<MixedEnum, _> = serde_json::from_value(json!([99, false, "tag"]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), MixedEnum::Third(99, false, "tag".to_string()));
}
#[test]
fn tuple_custom_tag_no_match() {
#[derive(serde_implicit::Deserialize, Debug)]
enum TupleEnum {
Case1(#[serde_implicit(tag)] String, u32),
Case2(bool, #[serde_implicit(tag)] u32),
}
let res: Result<TupleEnum, _> = serde_json::from_value(json!([42, "hello"]));
assert!(res.is_err());
}
#[test]
fn tuple_custom_tag_overlapping_resolved() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TupleEnum {
Case1(bool, #[serde_implicit(tag)] u32), Case2(#[serde_implicit(tag)] bool, bool), }
let res: Result<TupleEnum, _> = serde_json::from_value(json!([true, true]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), TupleEnum::Case2(true, true));
let res: Result<TupleEnum, _> = serde_json::from_value(json!([false, 42]));
assert!(res.is_ok());
assert_eq!(res.unwrap(), TupleEnum::Case1(false, 42));
}
#[test]
fn tuple_commit_semantics_verification() {
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TupleEnum {
Case1(bool, u32),
Case2(bool, bool),
}
let res: Result<TupleEnum, _> = serde_json::from_value(json!([false, false]));
let err = res.unwrap_err();
assert!(
err.to_string().contains("expected u32") || err.to_string().contains("invalid type"),
"Expected error about u32, got: {}",
err
);
}
#[test]
fn tuple_flatten_basic() {
#[derive(serde::Deserialize, Debug, PartialEq)]
struct A(String, bool);
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum Implicit {
Normal(bool),
Tagged(u64, #[serde_implicit(tag)] u32),
Nested(#[serde_implicit(flatten)] A),
}
let res: Result<Implicit, _> = serde_json::from_value(json!([false]));
assert_eq!(res.unwrap(), Implicit::Normal(false));
let res: Result<Implicit, _> = serde_json::from_value(json!([42, 99]));
assert_eq!(res.unwrap(), Implicit::Tagged(42, 99));
let res: Result<Implicit, _> = serde_json::from_value(json!(["hello", true]));
assert_eq!(res.unwrap(), Implicit::Nested(A("hello".to_string(), true)));
}
#[test]
fn tuple_flatten_multiple() {
#[derive(serde::Deserialize, Debug, PartialEq)]
struct StringBool(String, bool);
#[derive(serde::Deserialize, Debug, PartialEq)]
struct StringU64(String, u64);
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum Multi {
Normal(bool),
Flatten1(#[serde_implicit(flatten)] StringBool),
Flatten2(#[serde_implicit(flatten)] StringU64),
}
let res: Result<Multi, _> = serde_json::from_value(json!([true]));
assert_eq!(res.unwrap(), Multi::Normal(true));
let res: Result<Multi, _> = serde_json::from_value(json!(["test", false]));
assert_eq!(
res.unwrap(),
Multi::Flatten1(StringBool("test".to_string(), false))
);
let res: Result<Multi, _> = serde_json::from_value(json!(["test", 42]));
assert_eq!(
res.unwrap(),
Multi::Flatten2(StringU64("test".to_string(), 42))
);
}
#[test]
fn tuple_flatten_fallback_only() {
#[derive(serde::Deserialize, Debug, PartialEq)]
struct Fallback(String, String);
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TestEnum {
First(bool),
Second(u64),
Fall(#[serde_implicit(flatten)] Fallback),
}
let res: Result<TestEnum, _> = serde_json::from_value(json!([true]));
assert_eq!(res.unwrap(), TestEnum::First(true));
let res: Result<TestEnum, _> = serde_json::from_value(json!([123]));
assert_eq!(res.unwrap(), TestEnum::Second(123));
let res: Result<TestEnum, _> = serde_json::from_value(json!(["foo", "bar"]));
assert_eq!(
res.unwrap(),
TestEnum::Fall(Fallback("foo".to_string(), "bar".to_string()))
);
}
#[test]
fn tuple_flatten_no_match() {
#[derive(serde::Deserialize, Debug, PartialEq)]
struct OnlyBools(bool, bool);
#[derive(serde_implicit::Deserialize, Debug, PartialEq)]
enum TestEnum {
First(u64),
Second(#[serde_implicit(flatten)] OnlyBools),
}
let res: Result<TestEnum, _> = serde_json::from_value(json!(["string", "string"]));
assert!(res.is_err());
let err = res.unwrap_err();
assert!(err.to_string().contains("data did not match any variant"));
}
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/*.rs");
}
#[test]
fn test_string_key_map_to_integer_key() {
use std::collections::HashMap;
#[derive(serde_implicit_proc::Deserialize, Debug)]
enum WithMap {
Variant {
#[serde_implicit(tag)]
tag: String,
data: HashMap<u32, String>,
},
}
let res: Result<WithMap, _> =
serde_json::from_value(json!({"tag": "hello", "data": {"0": "zero", "1": "one"}}));
let val = res.unwrap();
match val {
WithMap::Variant { data, .. } => {
assert_eq!(data.len(), 2);
assert_eq!(data[&0], "zero");
assert_eq!(data[&1], "one");
}
}
}
#[test]
fn test_newtype_struct_map_key() {
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
struct Id(u64);
#[derive(serde_implicit_proc::Deserialize, serde::Serialize, Debug)]
enum WithNewtypeKey {
Variant {
#[serde_implicit(tag)]
tag: String,
data: HashMap<Id, String>,
},
}
let res: Result<WithNewtypeKey, _> =
serde_json::from_value(json!({"tag": "hello", "data": {"0": "zero", "42": "forty-two"}}));
let val = res.unwrap();
match val {
WithNewtypeKey::Variant { data, .. } => {
assert_eq!(data.len(), 2);
assert_eq!(data[&Id(0)], "zero");
assert_eq!(data[&Id(42)], "forty-two");
}
}
}
#[test]
fn test_null_tag_value_skipped() {
#[allow(dead_code)]
#[derive(serde_implicit_proc::Deserialize, Debug)]
enum Schema {
Old {
#[serde_implicit(tag)]
config: String,
value: u32,
},
New {
#[serde_implicit(tag)]
entries: Vec<String>,
value: u32,
},
}
let res: Result<Schema, _> = serde_json::from_value(json!({
"entries": null,
"config": "default",
"value": 42,
}));
assert!(res.is_ok(), "expected Ok but got: {res:?}");
match res.unwrap() {
Schema::Old { config, value, .. } => {
assert_eq!(config, "default");
assert_eq!(value, 42);
}
other => panic!("expected Old, got {other:?}"),
}
let res: Result<Schema, _> = serde_json::from_value(json!({
"config": null,
"entries": ["a"],
"value": 7,
}));
assert!(res.is_ok(), "expected Ok but got: {res:?}");
match res.unwrap() {
Schema::New { entries, value, .. } => {
assert_eq!(entries, vec!["a"]);
assert_eq!(value, 7);
}
other => panic!("expected New, got {other:?}"),
}
let res: Result<Schema, _> = serde_json::from_value(json!({
"config": null,
"entries": null,
"value": 0,
}));
assert!(res.is_err());
let res: Result<Schema, _> = serde_json::from_value(json!({
"config": "default",
"entries": ["a"],
"value": 0,
}));
let err = res.unwrap_err();
assert!(
err.to_string()
.contains("found multiple implicit tag fields"),
"expected 'found multiple implicit tag fields', got: {err}",
);
}
#[test]
fn test_readme_tuples() {
#[derive(serde_implicit::Deserialize, Debug)]
enum Message {
Literal(u64),
BigOp(Op, Vec<Message>),
}
#[derive(serde::Deserialize, Debug)]
enum Op {
Sum,
}
let res: Result<Op, _> = serde_json::from_value(json!("Sum"));
res.unwrap();
let res: Result<Message, _> = serde_json::from_value(json!(["Sum", 1]));
assert!(res.is_err());
let err = res.unwrap_err();
println!("{err:?}");
assert!(
err.to_string()
.contains("Message::BigOp: invalid type: integer `1`, expected a sequence")
);
}
#[test]
fn test_missing_option_field_defaults_to_none() {
#[allow(dead_code)]
#[derive(serde_implicit_proc::Deserialize, Debug)]
enum Config {
V0 {
#[serde_implicit(tag)]
settings: String,
dist_metric: String,
dimensions: Option<u32>,
},
V1 {
#[serde_implicit(tag)]
indexes: Vec<String>,
kv_store: String,
},
}
let res: Result<Config, _> = serde_json::from_value(json!({
"settings": "default",
"dist_metric": "euclidean"
}));
assert!(res.is_ok(), "expected Ok but got: {res:?}");
match res.unwrap() {
Config::V0 { dimensions, .. } => assert_eq!(dimensions, None),
other => panic!("expected V0, got {other:?}"),
}
}