#![cfg(feature = "serde")]
use core::marker::PhantomData;
use serde::{Deserialize, Serialize};
use structural_typing::{select, structural};
#[structural]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct TestUser {
name: String,
email: String,
id: u64,
}
#[structural]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct UserWithRename {
#[serde(rename = "user_name")]
name: String,
email: String,
id: u64,
}
#[structural]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct UserWithAlias {
name: String,
#[serde(alias = "mail")]
email: String,
id: u64,
}
#[structural]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UserWithRenameAll {
user_name: String,
email_address: String,
user_id: u64,
}
#[test]
fn deserialize_full() {
let json = r#"{"name":"Alice","email":"alice@test.com","id":123}"#;
let user: TestUser<test_user::with::all> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "alice@test.com");
assert_eq!(user.id, 123);
}
#[test]
fn serialize_full() {
let user = test_user::empty()
.name("Charlie".to_owned())
.email("charlie@test.com".to_owned())
.id(789);
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains(r#""name":"Charlie"#));
assert!(json.contains(r#""email":"charlie@test.com"#));
}
#[test]
fn deserialize_partial() {
let json_missing_email = r#"{"name":"Dave","id":999}"#;
let user: TestUser<select!(test_user: name, id)> =
serde_json::from_str(json_missing_email).unwrap();
assert_eq!(user.name, "Dave");
assert_eq!(user.id, 999);
}
#[test]
fn deserialize_missing_required_field_error() {
let json_missing_name = r#"{"email":"test@test.com","id":1}"#;
let result: Result<TestUser<select!(test_user: name, id)>, _> =
serde_json::from_str(json_missing_name);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("missing field"));
}
#[test]
fn deserialize_with_rename_attr() {
let json = r#"{"user_name":"Alice","email":"alice@test.com","id":123}"#;
let user: UserWithRename<user_with_rename::with::all> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.email, "alice@test.com");
assert_eq!(user.id, 123);
}
#[test]
fn serialize_with_rename_attr() {
let user = user_with_rename::empty()
.name("Charlie".to_owned())
.email("charlie@test.com".to_owned())
.id(789);
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains(r#""user_name":"Charlie"#));
assert!(!json.contains(r#""name":"#));
}
#[test]
fn deserialize_with_alias_primary_name() {
let json = r#"{"name":"Dave","email":"dave@test.com","id":123}"#;
let user: UserWithAlias<user_with_alias::with::all> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Dave");
assert_eq!(user.email, "dave@test.com");
}
#[test]
fn deserialize_with_alias_alternate_name() {
let json = r#"{"name":"Eve","mail":"eve@test.com","id":456}"#;
let user: UserWithAlias<user_with_alias::with::all> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Eve");
assert_eq!(user.email, "eve@test.com");
}
#[test]
fn deserialize_optional_field() {
let json_with_email = r#"{"name":"Alice","email":"alice@test.com","id":123}"#;
let user: TestUser<select!(test_user: name, email?, id)> =
serde_json::from_str(json_with_email).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.email, Some("alice@test.com".to_owned()));
assert_eq!(user.id, 123);
let json_without_email = r#"{"name":"Bob","id":456}"#;
let user2: TestUser<select!(test_user: name, email?, id)> =
serde_json::from_str(json_without_email).unwrap();
assert_eq!(user2.name, "Bob");
assert_eq!(user2.email, None);
assert_eq!(user2.id, 456);
}
#[test]
fn deserialize_mixed_presence_types() {
let json = r#"{"name":"Alice","id":123}"#;
let user: TestUser<select!(test_user: name, email?, id)> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.email, None);
assert_eq!(user.id, 123);
}
#[test]
fn deserialize_extra_fields_with_absent() {
let json = r#"{"name":"Alice","email":"alice@test.com","id":123,"extra":"ignored"}"#;
let user: TestUser<select!(test_user: name, id)> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.id, 123);
}
#[test]
fn serialize_partial_omits_absent() {
let user = test_user::empty().name("Alice".to_owned()).id(123);
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains(r#""name":"Alice"#));
assert!(json.contains(r#""id":123"#));
assert!(!json.contains("email"));
}
#[test]
fn deserialize_with_rename_all() {
let json = r#"{"userName":"Alice","emailAddress":"alice@test.com","userId":123}"#;
let user: UserWithRenameAll<user_with_rename_all::with::all> =
serde_json::from_str(json).unwrap();
assert_eq!(user.user_name, "Alice");
assert_eq!(user.email_address, "alice@test.com");
assert_eq!(user.user_id, 123);
}
#[test]
fn serialize_with_rename_all() {
let user = user_with_rename_all::empty()
.user_name("Bob".to_owned())
.email_address("bob@test.com".to_owned())
.user_id(456);
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains(r#""userName":"Bob"#));
assert!(json.contains(r#""emailAddress":"bob@test.com"#));
assert!(json.contains(r#""userId":456"#));
assert!(!json.contains("user_name"));
assert!(!json.contains("email_address"));
assert!(!json.contains("user_id"));
}
#[test]
fn serialize_with_alias() {
let user = user_with_alias::empty()
.name("Charlie".to_owned())
.email("charlie@test.com".to_owned())
.id(789);
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains(r#""name":"Charlie"#));
assert!(json.contains(r#""email":"charlie@test.com"#));
assert!(json.contains(r#""email":"#));
assert!(!json.contains(r#""mail":"#));
}
#[test]
fn deserialize_null_for_present_field_errors() {
let json = r#"{"name":null,"email":"alice@test.com","id":123}"#;
let result: Result<TestUser<test_user::with::all>, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn deserialize_null_for_optional_field() {
let json = r#"{"name":"Alice","email":null,"id":123}"#;
let user: TestUser<select!(test_user: name, email?, id)> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.email, None);
assert_eq!(user.id, 123);
}
#[test]
fn deserialize_null_for_absent_field_ignored() {
let json = r#"{"name":"Alice","email":null,"id":123}"#;
let user: TestUser<select!(test_user: name, id)> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Alice");
assert_eq!(user.id, 123);
}
#[test]
fn roundtrip_full_struct() {
let original = test_user::empty()
.name("Alice".to_owned())
.email("alice@test.com".to_owned())
.id(123);
let json = serde_json::to_string(&original).unwrap();
let deserialized: TestUser<test_user::with::all> = serde_json::from_str(&json).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn roundtrip_partial_struct() {
let original = test_user::empty().name("Bob".to_owned()).id(456);
let json = serde_json::to_string(&original).unwrap();
let deserialized: TestUser<select!(test_user: name, id)> = serde_json::from_str(&json).unwrap();
assert_eq!(original.name, deserialized.name);
assert_eq!(original.id, deserialized.id);
}
#[structural]
#[derive(Deserialize)]
struct DeserializeOnly {
value: String,
}
#[test]
fn deserialize_only_no_other_derives() {
let json = r#"{"value":"test"}"#;
let result: DeserializeOnly<deserialize_only::with::all> = serde_json::from_str(json).unwrap();
assert_eq!(result.value, "test");
}
#[test]
fn roundtrip_optional_fields() {
let with_email = test_user::empty()
.name("Charlie".to_owned())
.email(Some("charlie@test.com".to_owned()))
.id(789);
let json = serde_json::to_string(&with_email).unwrap();
let deserialized: TestUser<select!(test_user: name, email?, id)> =
serde_json::from_str(&json).unwrap();
assert_eq!(with_email.name, deserialized.name);
assert_eq!(with_email.email, deserialized.email);
assert_eq!(with_email.id, deserialized.id);
let without_email = test_user::empty()
.name("Dave".to_owned())
.email(None)
.id(101);
let json = serde_json::to_string(&without_email).unwrap();
let deserialized: TestUser<select!(test_user: name, email?, id)> =
serde_json::from_str(&json).unwrap();
assert_eq!(without_email.name, deserialized.name);
assert_eq!(without_email.email, deserialized.email);
assert_eq!(without_email.id, deserialized.id);
}
#[test]
fn error_message_includes_field_name() {
let json_missing_name = r#"{"email":"test@test.com","id":1}"#;
let result: Result<TestUser<test_user::with::all>, _> = serde_json::from_str(json_missing_name);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("name") || err_msg.contains("missing"));
}
#[test]
fn error_message_for_multiple_missing_fields() {
let json = r#"{"email":"test@test.com"}"#;
let result: Result<TestUser<test_user::with::all>, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[structural]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Address {
city: String,
country: String,
}
#[structural]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct UserWithAddress<A: address::Fields = select!(address: all-)> {
name: String,
address: Address<A>,
}
#[test]
fn nested_structural_types() {
let user = user_with_address::empty().name("Alice".to_owned()).address(
address::empty()
.city("Seattle".to_owned())
.country("USA".to_owned()),
);
let json = serde_json::to_string(&user).unwrap();
let deserialized: UserWithAddress<user_with_address::with::all, address::with::all> =
serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "Alice");
assert_eq!(deserialized.address.city, "Seattle");
assert_eq!(deserialized.address.country, "USA");
}
#[test]
fn nested_with_partial_address_city_only() {
type AddressCityOnly = select!(address: city);
let user = user_with_address::empty()
.name("Bob".to_owned())
.address(address::empty().city("London".to_owned()));
let json = serde_json::to_string(&user).unwrap();
let deserialized: UserWithAddress<user_with_address::with::all, AddressCityOnly> =
serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "Bob");
assert_eq!(deserialized.address.city, "London");
}
#[test]
fn nested_with_optional_address_fields() {
type AddressOptional = select!(address: city, country?);
let user_with_country = user_with_address::empty()
.name("Charlie".to_owned())
.address(
address::empty()
.city("Paris".to_owned())
.country(Some("France".to_owned())),
);
let json = serde_json::to_string(&user_with_country).unwrap();
let deserialized: UserWithAddress<user_with_address::with::all, AddressOptional> =
serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "Charlie");
assert_eq!(deserialized.address.city, "Paris");
assert_eq!(deserialized.address.country, Some("France".to_owned()));
let user_without_country = user_with_address::empty()
.name("Dave".to_owned())
.address(address::empty().city("Berlin".to_owned()).country(None));
let json = serde_json::to_string(&user_without_country).unwrap();
let deserialized: UserWithAddress<user_with_address::with::all, AddressOptional> =
serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.name, "Dave");
assert_eq!(deserialized.address.city, "Berlin");
assert_eq!(deserialized.address.country, None);
}
#[test]
fn nested_with_absent_user_fields() {
type UserNameOnly = select!(user_with_address: name);
let user: UserWithAddress<UserNameOnly, select!(address: all-)> =
user_with_address::empty().name("Eve".to_owned());
let json = serde_json::to_string(&user).unwrap();
assert!(json.contains(r#""name":"Eve"#));
assert!(!json.contains("address"));
}
#[test]
fn nested_merge_addresses() {
let city_part = address::empty().city("Tokyo".to_owned());
let country_part = address::empty().country("Japan".to_owned());
let merged = city_part.merge(country_part);
let user = user_with_address::empty()
.name("Frank".to_owned())
.address(merged);
assert_eq!(user.name, "Frank");
assert_eq!(user.address.city, "Tokyo");
assert_eq!(user.address.country, "Japan");
}
#[test]
fn nested_extract_address_fields() {
let full_address = address::empty()
.city("Rome".to_owned())
.country("Italy".to_owned());
type AddressCityOnly = select!(address: city);
let (city_only, remainder): (Address<AddressCityOnly>, _) = full_address.extract();
assert_eq!(city_only.city, "Rome");
assert_eq!(remainder.country, "Italy");
let user_with_city = user_with_address::empty()
.name("Grace".to_owned())
.address(city_only);
assert_eq!(user_with_city.name, "Grace");
assert_eq!(user_with_city.address.city, "Rome");
}
#[test]
fn serde_with_all_optional() {
let json = r#"{"name":"Alice","email":"alice@test.com"}"#;
let user: TestUser<select!(test_user: all?)> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, Some("Alice".to_owned()));
assert_eq!(user.email, Some("alice@test.com".to_owned()));
assert_eq!(user.id, None);
}
#[test]
fn serde_with_absent_field() {
let json = r#"{"name":"Bob","id":123,"email":"ignored@test.com"}"#;
let user: TestUser<select!(test_user: name, id, email-)> = serde_json::from_str(json).unwrap();
assert_eq!(user.name, "Bob");
assert_eq!(user.id, 123);
assert_eq!(user.email, PhantomData);
}