#[cfg(test)]
mod derive_rename_enum_tests {
use rstructor::{Instructor, SchemaType};
use serde::{Deserialize, Serialize};
use serde_json::Value;
fn external_variant<'a>(schema: &'a Value, variant_key: &str) -> &'a Value {
schema["anyOf"]
.as_array()
.expect("complex enum schema should be anyOf")
.iter()
.find(|m| {
m.get("properties")
.and_then(|p| p.get(variant_key))
.is_some()
})
.unwrap_or_else(|| panic!("no anyOf member with variant key {variant_key}"))
}
#[derive(Instructor, Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
enum ExternalPayment {
CreditCard { card_number: String },
BankTransfer { account_id: String },
}
#[test]
fn external_tagged_rename_all_renames_variant_not_inner_field() {
let schema_json = ExternalPayment::schema().to_json();
let member = external_variant(&schema_json, "creditCard");
let inner = &member["properties"]["creditCard"];
let inner_props = inner["properties"]
.as_object()
.expect("variant should carry an object with properties");
assert!(
inner_props.contains_key("card_number"),
"inner field must remain 'card_number' to match serde; got keys {:?}",
inner_props.keys().collect::<Vec<_>>()
);
assert!(
!inner_props.contains_key("cardNumber"),
"inner field must NOT be camelCased (would mismatch serde)"
);
let required = inner["required"].as_array().unwrap();
assert!(required.iter().any(|v| v == "card_number"));
}
#[test]
fn external_tagged_schema_keys_match_serde_serialization() {
let value = ExternalPayment::CreditCard {
card_number: "4111".into(),
};
let serialized = serde_json::to_value(&value).unwrap();
assert_eq!(serialized["creditCard"]["card_number"], Value::from("4111"));
assert!(serialized["creditCard"].get("cardNumber").is_none());
let back: ExternalPayment = serde_json::from_value(serialized).unwrap();
assert_eq!(back, value);
let schema_json = ExternalPayment::schema().to_json();
let inner_props = external_variant(&schema_json, "creditCard")["properties"]["creditCard"]
["properties"]
.as_object()
.unwrap();
assert!(inner_props.contains_key("card_number"));
}
#[derive(Instructor, Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(tag = "kind", rename_all = "camelCase")]
enum InternalPayment {
CreditCard { card_number: String },
BankTransfer { account_id: String },
}
#[test]
fn internal_tagged_rename_all_renames_tag_value_not_inner_field() {
let schema_json = InternalPayment::schema().to_json();
let members = schema_json["anyOf"].as_array().unwrap();
let member = members
.iter()
.find(|m| m["properties"]["kind"]["enum"][0] == "creditCard")
.expect("should find creditCard tag member");
let props = member["properties"].as_object().unwrap();
assert!(props.contains_key("kind"));
assert!(
props.contains_key("card_number"),
"flattened inner field must remain 'card_number'; got {:?}",
props.keys().collect::<Vec<_>>()
);
assert!(!props.contains_key("cardNumber"));
let required = member["required"].as_array().unwrap();
assert!(required.iter().any(|v| v == "kind"));
assert!(required.iter().any(|v| v == "card_number"));
}
#[test]
fn internal_tagged_schema_keys_match_serde_serialization() {
let value = InternalPayment::CreditCard {
card_number: "4111".into(),
};
let serialized = serde_json::to_value(&value).unwrap();
assert_eq!(serialized["kind"], Value::from("creditCard"));
assert_eq!(serialized["card_number"], Value::from("4111"));
assert!(serialized.get("cardNumber").is_none());
let back: InternalPayment = serde_json::from_value(serialized).unwrap();
assert_eq!(back, value);
}
#[derive(Instructor, Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(tag = "t", content = "c", rename_all = "camelCase")]
enum AdjacentPayment {
CreditCard { card_number: String },
}
#[test]
fn adjacent_tagged_rename_all_renames_tag_value_not_inner_field() {
let schema_json = AdjacentPayment::schema().to_json();
let members = schema_json["anyOf"].as_array().unwrap();
let member = members
.iter()
.find(|m| m["properties"]["t"]["enum"][0] == "creditCard")
.expect("should find creditCard adjacently-tagged member");
let content_props = member["properties"]["c"]["properties"]
.as_object()
.expect("adjacent content should be an object with properties");
assert!(
content_props.contains_key("card_number"),
"content inner field must remain 'card_number'; got {:?}",
content_props.keys().collect::<Vec<_>>()
);
assert!(!content_props.contains_key("cardNumber"));
}
#[test]
fn adjacent_tagged_schema_keys_match_serde_serialization() {
let value = AdjacentPayment::CreditCard {
card_number: "4111".into(),
};
let serialized = serde_json::to_value(&value).unwrap();
assert_eq!(serialized["t"], Value::from("creditCard"));
assert_eq!(serialized["c"]["card_number"], Value::from("4111"));
assert!(serialized["c"].get("cardNumber").is_none());
let back: AdjacentPayment = serde_json::from_value(serialized).unwrap();
assert_eq!(back, value);
}
#[derive(Instructor, Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
enum FieldRenameVariant {
CreditCard {
#[serde(rename = "ccNum")]
card_number: String,
holder_name: String,
},
}
#[test]
fn field_level_rename_inside_variant_is_applied() {
let schema_json = FieldRenameVariant::schema().to_json();
let inner_props = external_variant(&schema_json, "creditCard")["properties"]["creditCard"]
["properties"]
.as_object()
.unwrap();
assert!(
inner_props.contains_key("ccNum"),
"field-level #[serde(rename)] should produce 'ccNum'; got {:?}",
inner_props.keys().collect::<Vec<_>>()
);
assert!(!inner_props.contains_key("card_number"));
assert!(
inner_props.contains_key("holder_name"),
"non-renamed inner field must remain 'holder_name'"
);
assert!(!inner_props.contains_key("holderName"));
}
#[test]
fn field_level_rename_inside_variant_matches_serde() {
let value = FieldRenameVariant::CreditCard {
card_number: "4111".into(),
holder_name: "Ada".into(),
};
let serialized = serde_json::to_value(&value).unwrap();
assert_eq!(serialized["creditCard"]["ccNum"], Value::from("4111"));
assert_eq!(serialized["creditCard"]["holder_name"], Value::from("Ada"));
assert!(serialized["creditCard"].get("card_number").is_none());
let back: FieldRenameVariant = serde_json::from_value(serialized).unwrap();
assert_eq!(back, value);
}
}