use core::marker::PhantomData;
use structural_typing::{presence::Present, select, structural};
#[structural]
#[derive(Debug, PartialEq)]
struct Address {
street: String,
city: String,
zip: String,
}
#[structural]
#[derive(Debug, PartialEq)]
struct Person<A: address::Fields = select!(address: all-)> {
name: String,
#[nested(address: street, city, zip)]
address: Address<A>,
}
#[test]
fn basic_nested_setter() {
let person = person::empty()
.name("Alice".to_owned())
.address(address::empty())
.address_city("Seattle".to_owned());
assert_eq!(person.name, "Alice");
assert_eq!(person.address.city, "Seattle");
}
#[test]
fn multiple_nested_setters() {
let person = person::empty()
.address(address::empty())
.address_street("123 Main St".to_owned())
.address_city("Portland".to_owned())
.address_zip("97201".to_owned());
assert_eq!(person.address.street, "123 Main St");
assert_eq!(person.address.city, "Portland");
assert_eq!(person.address.zip, "97201");
}
#[test]
fn chaining_nested_and_regular_setters() {
let person = person::empty()
.name("Bob".to_owned())
.address(address::empty())
.address_city("Austin".to_owned())
.address_street("456 Oak Ave".to_owned());
assert_eq!(person.name, "Bob");
assert_eq!(person.address.city, "Austin");
assert_eq!(person.address.street, "456 Oak Ave");
}
#[test]
fn nested_setter_preserves_field_states() {
let person = person::empty()
.name("Charlie".to_owned())
.address(address::empty())
.address_city("Boston".to_owned());
fn requires_name<A: address::Fields, F: person::Fields<name = Present>>(
p: &Person<F, A>,
) -> &str {
&p.name
}
assert_eq!(requires_name(&person), "Charlie");
}
#[test]
fn nested_setter_with_optional() {
let person = person::empty()
.address(address::empty())
.address_city(Some("Paris".to_owned()));
assert_eq!(person.address.city, Some("Paris".to_owned()));
}
#[test]
fn nested_setter_other_fields_absent() {
let person = person::empty()
.address(address::empty())
.address_city("London".to_owned());
assert_eq!(person.address.street, PhantomData);
assert_eq!(person.address.zip, PhantomData);
}
#[structural]
struct Contact {
email: String,
phone: String,
}
#[structural]
struct Employee<
A: address::Fields = select!(address: all-),
C: contact::Fields = select!(contact: all-),
> {
#[nested(address: street, city)]
address: Address<A>,
#[nested(contact: email)]
contact: Contact<C>,
}
#[test]
fn multiple_nested_struct_fields() {
let emp = employee::empty()
.address(address::empty())
.contact(contact::empty())
.address_city("NYC".to_owned())
.contact_email("test@example.com".to_owned());
assert_eq!(emp.address.city, "NYC");
assert_eq!(emp.contact.email, "test@example.com");
}
#[test]
fn nested_setters_independent() {
let emp = employee::empty()
.address(address::empty())
.contact(contact::empty())
.address_city("SF".to_owned());
assert_eq!(emp.address.city, "SF");
}
#[test]
fn nested_setter_then_extract() {
let person = person::empty()
.name("Frank".to_owned())
.address(address::empty())
.address_city("Vienna".to_owned())
.address_street("Main St".to_owned());
type PersonWithAddress = select!(person: address);
let (extracted, remainder): (Person<PersonWithAddress, _>, _) = person.extract();
assert_eq!(extracted.address.city, "Vienna");
assert_eq!(remainder.name, "Frank");
}
#[structural]
struct WithLifetime<'a> {
data: &'a str,
label: String,
}
#[structural]
struct Container<'a, W: with_lifetime::Fields = select!(with_lifetime: all-)> {
id: u64,
#[nested(with_lifetime: data)]
inner: WithLifetime<'a, W>,
}
#[test]
fn nested_setter_with_lifetime() {
let s = String::from("hello");
let container = container::empty()
.id(1)
.inner(with_lifetime::empty())
.inner_data(s.as_str());
assert_eq!(container.inner.data, "hello");
}
#[structural]
struct DeepInner {
value: i32,
label: String,
}
#[structural]
struct DeepMiddle<I: deep_inner::Fields = select!(deep_inner: all-)> {
name: String,
#[nested(deep_inner: value, label)]
inner: DeepInner<I>,
}
#[test]
fn deep_nesting_three_levels() {
let middle = deep_middle::empty()
.name("middle".to_owned())
.inner(deep_inner::empty())
.inner_value(42)
.inner_label("answer".to_owned());
assert_eq!(middle.name, "middle");
assert_eq!(middle.inner.value, 42);
assert_eq!(middle.inner.label, "answer");
let updated = middle.inner_value(100);
assert_eq!(updated.inner.value, 100);
assert_eq!(updated.name, "middle");
}
#[test]
fn nested_setter_value_override() {
let person = person::empty()
.name("Alice".to_owned())
.address(address::empty())
.address_city("Seattle".to_owned())
.address_city("Portland".to_owned());
assert_eq!(person.address.city, "Portland");
}
#[cfg(feature = "serde")]
mod serde_integration {
use super::*;
use serde::{Deserialize, Serialize};
#[structural]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Account {
balance: f64,
currency: String,
}
#[structural]
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Customer<A: account::Fields = select!(account: all-)> {
name: String,
#[nested(account: balance, currency)]
account: Account<A>,
}
#[test]
fn nested_setter_with_serde_roundtrip() {
let customer = customer::empty()
.name("Dave".to_owned())
.account(account::empty())
.account_balance(1000.0)
.account_currency("USD".to_owned());
let json = serde_json::to_string(&customer).unwrap();
assert!(json.contains("Dave"));
assert!(json.contains("1000"));
assert!(json.contains("USD"));
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(value["name"], "Dave");
assert_eq!(value["account"]["balance"], 1000.0);
assert_eq!(value["account"]["currency"], "USD");
}
}