use std::collections::HashSet;
use crate::core::ir::{EnumDef, EnumVariant, FieldDef, TypeDef, TypeRef};
use crate::e2e::field_access::FieldResolver;
use super::ir_enum::{build_ir_enum_map, is_enum_path};
use super::types::IrEnumMap;
fn field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
..FieldDef::default()
}
}
fn type_def(name: &str, fields: Vec<FieldDef>) -> TypeDef {
TypeDef {
name: name.to_string(),
fields,
..TypeDef::default()
}
}
fn enum_def(name: &str) -> EnumDef {
EnumDef {
name: name.to_string(),
..EnumDef::default()
}
}
fn ambiguous_kind_type_defs() -> Vec<TypeDef> {
vec![
type_def(
"DataNode",
vec![field("kind", TypeRef::Named("DataNodeKind".to_string()))],
),
type_def("PlainNode", vec![field("kind", TypeRef::String)]),
]
}
fn ambiguous_kind_enums() -> Vec<EnumDef> {
vec![enum_def("DataNodeKind")]
}
#[test]
fn a_field_whose_declared_type_is_a_real_enum_is_derived_as_enum() {
let map = build_ir_enum_map(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
let map = IrEnumMap {
root_type: Some("DataNode".to_string()),
..map
};
assert!(is_enum_path(&map, "kind"), "DataNode.kind is DataNodeKind, a real enum");
}
#[test]
fn a_field_with_the_same_name_but_a_string_type_on_a_different_owner_is_not_enum() {
let map = build_ir_enum_map(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
let map = IrEnumMap {
root_type: Some("PlainNode".to_string()),
..map
};
assert!(
!is_enum_path(&map, "kind"),
"PlainNode.kind is String — the bare name 'kind' must not decide this"
);
}
#[test]
fn an_option_wrapped_enum_field_is_derived_as_enum() {
let type_defs = vec![type_def(
"Response",
vec![field(
"status",
TypeRef::Optional(Box::new(TypeRef::Named("Status".to_string()))),
)],
)];
let enums = vec![enum_def("Status")];
let map = build_ir_enum_map(&type_defs, &enums);
let map = IrEnumMap {
root_type: Some("Response".to_string()),
..map
};
assert!(is_enum_path(&map, "status"), "Option<Status> must unwrap to the enum");
}
#[test]
fn a_vec_wrapped_element_field_reached_via_wildcard_traversal_is_derived_as_enum() {
let type_defs = vec![
type_def(
"Result",
vec![field(
"links",
TypeRef::Vec(Box::new(TypeRef::Named("Link".to_string()))),
)],
),
type_def("Link", vec![field("link_type", TypeRef::Named("LinkType".to_string()))]),
];
let enums = vec![enum_def("LinkType")];
let map = build_ir_enum_map(&type_defs, &enums);
let map = IrEnumMap {
root_type: Some("Result".to_string()),
..map
};
assert!(
is_enum_path(&map, "links[].link_type"),
"Vec<Link>.link_type must be reached through the wildcard array segment"
);
assert!(
!is_enum_path(&map, "link_type"),
"a bare leaf name must not resolve against the wrong owner type"
);
}
#[test]
fn a_nested_indexed_path_is_derived_as_enum() {
let type_defs = vec![
type_def(
"Response",
vec![field(
"choices",
TypeRef::Vec(Box::new(TypeRef::Named("Choice".to_string()))),
)],
),
type_def(
"Choice",
vec![field("finish_reason", TypeRef::Named("FinishReason".to_string()))],
),
];
let enums = vec![enum_def("FinishReason")];
let map = build_ir_enum_map(&type_defs, &enums);
let map = IrEnumMap {
root_type: Some("Response".to_string()),
..map
};
assert!(is_enum_path(&map, "choices[0].finish_reason"));
}
#[test]
fn a_missing_root_type_answers_false_rather_than_guessing() {
let map = build_ir_enum_map(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
assert!(!is_enum_path(&map, "kind"));
}
#[test]
fn a_path_through_an_unknown_field_answers_false() {
let map = build_ir_enum_map(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
let map = IrEnumMap {
root_type: Some("DataNode".to_string()),
..map
};
assert!(!is_enum_path(&map, "nonexistent_field"));
assert!(!is_enum_path(&map, "nonexistent_parent.kind"));
}
#[test]
fn field_resolver_is_enum_consults_the_ir_fallback_when_config_is_silent() {
let map = FieldResolver::ir_enum_fields(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
let resolver = FieldResolver::new(
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
)
.with_ir_enum_map(map, Some("DataNode".to_string()));
assert!(
resolver.is_enum("kind"),
"fields_enum was never configured; the IR alone must answer this"
);
}
#[test]
fn field_resolver_is_enum_does_not_misclassify_the_same_name_on_a_different_owner() {
let map = FieldResolver::ir_enum_fields(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
let resolver = FieldResolver::new(
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
)
.with_ir_enum_map(map, Some("PlainNode".to_string()));
assert!(!resolver.is_enum("kind"));
}
#[test]
fn an_explicit_fields_enum_entry_wins_even_when_the_ir_disagrees() {
let map = FieldResolver::ir_enum_fields(&ambiguous_kind_type_defs(), &ambiguous_kind_enums());
let resolver = FieldResolver::new(
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
)
.with_enum_fields(HashSet::from(["kind".to_string()]))
.with_ir_enum_map(map, Some("PlainNode".to_string()));
assert!(
resolver.is_enum("kind"),
"an explicit fields_enum entry must win over an IR disagreement"
);
}
#[test]
fn a_resolver_with_no_ir_enum_map_wired_in_behaves_exactly_as_before() {
let resolver = FieldResolver::new(
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
);
assert!(!resolver.is_enum("kind"));
let resolver = resolver.with_enum_fields(HashSet::from(["kind".to_string()]));
assert!(resolver.is_enum("kind"));
}
#[test]
fn variant_payload_is_collection_distinguishes_a_direct_vec_payload_from_a_wrapping_struct() {
let enums = vec![EnumDef {
name: "Outcome".to_string(),
variants: vec![
EnumVariant {
name: "Found".to_string(),
fields: vec![field("_0", TypeRef::Vec(Box::new(TypeRef::Named("Entry".to_string()))))],
..EnumVariant::default()
},
EnumVariant {
name: "Wrapped".to_string(),
fields: vec![field("payload", TypeRef::Named("Payload".to_string()))],
..EnumVariant::default()
},
EnumVariant {
name: "Empty".to_string(),
..EnumVariant::default()
},
],
..EnumDef::default()
}];
let map = build_ir_enum_map(&[], &enums);
assert!(
map.variant_payload_is_collection
.get("Outcome")
.is_some_and(|variants| variants.contains("Found")),
"Found(Vec<Entry>) wraps a collection directly"
);
assert!(
!map.variant_payload_is_collection
.get("Outcome")
.is_some_and(|variants| variants.contains("Wrapped")),
"Wrapped(Payload) wraps a struct, not a collection"
);
assert!(
!map.variant_payload_is_collection
.get("Outcome")
.is_some_and(|variants| variants.contains("Empty")),
"a fieldless variant has no payload to classify"
);
}
#[test]
fn resolver_union_variant_payload_is_collection_matches_the_ir() {
let enums = vec![EnumDef {
name: "Outcome".to_string(),
variants: vec![
EnumVariant {
name: "Found".to_string(),
fields: vec![field("_0", TypeRef::Vec(Box::new(TypeRef::Named("Entry".to_string()))))],
..EnumVariant::default()
},
EnumVariant {
name: "Wrapped".to_string(),
fields: vec![field("payload", TypeRef::Named("Payload".to_string()))],
..EnumVariant::default()
},
],
..EnumDef::default()
}];
let resolver = FieldResolver::new(
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
)
.with_ir_enum_map(FieldResolver::ir_enum_fields(&[], &enums), None);
assert!(resolver.union_variant_payload_is_collection("Outcome", "Found"));
assert!(!resolver.union_variant_payload_is_collection("Outcome", "Wrapped"));
assert!(!resolver.union_variant_payload_is_collection("Outcome", "Missing"));
assert!(!resolver.union_variant_payload_is_collection("UnknownUnion", "Found"));
}
#[test]
fn variant_payload_is_collection_covers_nested_vec_and_optional_vec_payloads() {
let enums = vec![EnumDef {
name: "Outcome".to_string(),
variants: vec![
EnumVariant {
name: "NestedVec".to_string(),
fields: vec![field(
"_0",
TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::Named("Entry".to_string()))))),
)],
..EnumVariant::default()
},
EnumVariant {
name: "OptionalVec".to_string(),
fields: vec![field(
"_0",
TypeRef::Optional(Box::new(TypeRef::Vec(Box::new(TypeRef::Named("Entry".to_string()))))),
)],
..EnumVariant::default()
},
],
..EnumDef::default()
}];
let map = build_ir_enum_map(&[], &enums);
assert!(
map.variant_payload_is_collection
.get("Outcome")
.is_some_and(|variants| variants.contains("NestedVec")),
"Vec<Vec<Entry>> classifies as a collection payload via the outer Vec"
);
assert_eq!(
map.variant_payload_types
.get("Outcome")
.and_then(|v| v.get("NestedVec")),
Some(&("_0".to_string(), "Entry".to_string())),
"named_type recurses through both Vec layers to the innermost named element"
);
assert!(
map.variant_payload_is_collection
.get("Outcome")
.is_some_and(|variants| variants.contains("OptionalVec")),
"Option<Vec<Entry>> classifies as a collection payload via is_vec_type's Optional unwrap"
);
assert_eq!(
map.variant_payload_types
.get("Outcome")
.and_then(|v| v.get("OptionalVec")),
Some(&("_0".to_string(), "Entry".to_string())),
"named_type unwraps Option then Vec to the same named element"
);
}
fn variant(name: &str, serde_rename: Option<&str>) -> EnumVariant {
EnumVariant {
name: name.to_string(),
serde_rename: serde_rename.map(str::to_string),
..EnumVariant::default()
}
}
fn wire_variant_enum(rename_all: Option<&str>, variants: Vec<EnumVariant>) -> EnumDef {
EnumDef {
name: "Kind".to_string(),
serde_rename_all: rename_all.map(str::to_string),
variants,
..EnumDef::default()
}
}
#[test]
fn enum_wire_variants_records_only_unambiguous_renames() {
struct Case {
name: &'static str,
rename_all: Option<&'static str>,
variants: Vec<EnumVariant>,
lookup: &'static str,
expected: Option<&'static str>,
}
let cases = vec![
Case {
name: "explicit serde(rename) maps the wire value back to the identifier",
rename_all: None,
variants: vec![variant("KeyValue", Some("key-value"))],
lookup: "key-value",
expected: Some("KeyValue"),
},
Case {
name: "rename_all alone is enough to separate the two spellings",
rename_all: Some("kebab-case"),
variants: vec![variant("KeyValue", None)],
lookup: "key-value",
expected: Some("KeyValue"),
},
Case {
name: "serde(rename) wins over rename_all",
rename_all: Some("kebab-case"),
variants: vec![variant("KeyValue", Some("kv"))],
lookup: "kv",
expected: Some("KeyValue"),
},
Case {
name: "the rename_all spelling is NOT recorded when serde(rename) overrode it",
rename_all: Some("kebab-case"),
variants: vec![variant("KeyValue", Some("kv"))],
lookup: "key-value",
expected: None,
},
Case {
name: "an unrenamed variant has nothing to reconcile and is absent",
rename_all: None,
variants: vec![variant("Plain", None)],
lookup: "Plain",
expected: None,
},
Case {
name: "a rename_all that is a no-op for this identifier is absent",
rename_all: Some("PascalCase"),
variants: vec![variant("Plain", None)],
lookup: "Plain",
expected: None,
},
Case {
name: "two variants renamed onto one wire value are ambiguous and dropped",
rename_all: None,
variants: vec![variant("First", Some("shared")), variant("Second", Some("shared"))],
lookup: "shared",
expected: None,
},
Case {
name: "a wire value that is another variant's identifier is valid on both surfaces and dropped",
rename_all: None,
variants: vec![variant("Alpha", Some("Beta")), variant("Beta", None)],
lookup: "Beta",
expected: None,
},
];
for case in cases {
let enums = vec![wire_variant_enum(case.rename_all, case.variants)];
let map = build_ir_enum_map(&[], &enums);
let got = map
.enum_wire_variants
.get("Kind")
.and_then(|by_wire| by_wire.get(case.lookup))
.map(String::as_str);
assert_eq!(got, case.expected, "case '{}'", case.name);
}
}