use crate::backends::swift::gen_bindings::dto::compute_first_class_dto_names;
use crate::core::ir::{ApiSurface, FieldDef, TypeDef, TypeRef};
use crate::e2e::config::{CallConfig, E2eConfig};
use std::collections::HashSet;
fn dto(name: &str, fields: Vec<FieldDef>) -> TypeDef {
TypeDef {
name: name.to_string(),
has_serde: true,
fields,
..TypeDef::default()
}
}
fn field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
..FieldDef::default()
}
}
fn both_classifiers(type_defs: &[TypeDef]) -> (HashSet<String>, HashSet<String>) {
let api = ApiSurface {
types: type_defs.to_vec(),
..ApiSurface::default()
};
let binding_known = compute_first_class_dto_names(&api, &HashSet::new());
let binding_first_class: HashSet<String> = type_defs
.iter()
.filter(|t| binding_known.contains(&t.name))
.map(|t| t.name.clone())
.collect();
let e2e_config = E2eConfig::default();
let call_config = CallConfig::default();
let map = super::values::build_swift_first_class_map(type_defs, &[], &e2e_config, &call_config);
(binding_first_class, map.first_class_types)
}
fn assert_parity(case: &str, type_defs: &[TypeDef]) {
let (binding, e2e) = both_classifiers(type_defs);
assert_eq!(
binding,
e2e,
"{case}: binding and e2e classifiers disagree -- binding only: {:?}, e2e only: {:?}",
binding.difference(&e2e).collect::<Vec<_>>(),
e2e.difference(&binding).collect::<Vec<_>>()
);
}
#[test]
fn plain_scalar_struct_agrees() {
let type_defs = vec![dto("Simple", vec![field("name", TypeRef::String)])];
assert_parity("plain scalar struct", &type_defs);
}
#[test]
fn a_struct_with_an_unsupported_field_stays_opaque_on_both_sides() {
let type_defs = vec![dto("Opaque", vec![field("payload", TypeRef::Bytes)])];
assert_parity("Bytes-field struct", &type_defs);
}
#[test]
fn vec_self_reference_agrees() {
let type_defs = vec![dto(
"DataNode",
vec![
field("kind", TypeRef::String),
field(
"children",
TypeRef::Vec(Box::new(TypeRef::Named("DataNode".to_string()))),
),
],
)];
assert_parity("Vec<Self> self-reference", &type_defs);
}
#[test]
fn map_value_self_reference_agrees() {
let type_defs = vec![dto(
"IndexNode",
vec![field(
"byKey",
TypeRef::Map(
Box::new(TypeRef::String),
Box::new(TypeRef::Named("IndexNode".to_string())),
),
)],
)];
assert_parity("Map<String, Self> self-reference", &type_defs);
}
#[test]
fn a_cascading_container_over_two_self_referential_members_agrees() {
let type_defs = vec![
dto(
"DataNode",
vec![
field("kind", TypeRef::String),
field(
"children",
TypeRef::Vec(Box::new(TypeRef::Named("DataNode".to_string()))),
),
],
),
dto(
"StructureItem",
vec![
field("label", TypeRef::String),
field(
"children",
TypeRef::Vec(Box::new(TypeRef::Named("StructureItem".to_string()))),
),
],
),
dto(
"ProcessResult",
vec![
field(
"structure",
TypeRef::Vec(Box::new(TypeRef::Named("StructureItem".to_string()))),
),
field(
"data",
TypeRef::Optional(Box::new(TypeRef::Named("DataNode".to_string()))),
),
],
),
];
assert_parity("cascading container over two self-referential members", &type_defs);
}
#[test]
fn mutual_indirected_self_reference_agrees() {
let type_defs = vec![
dto(
"Branch",
vec![field(
"leaves",
TypeRef::Vec(Box::new(TypeRef::Named("Leaf".to_string()))),
)],
),
dto(
"Leaf",
vec![field(
"branches",
TypeRef::Vec(Box::new(TypeRef::Named("Branch".to_string()))),
)],
),
];
assert_parity("mutual Vec-indirected self-reference", &type_defs);
}
#[test]
fn bare_optional_self_reference_stays_opaque_on_both_sides() {
let type_defs = vec![dto(
"LinkedNode",
vec![field(
"next",
TypeRef::Optional(Box::new(TypeRef::Named("LinkedNode".to_string()))),
)],
)];
assert_parity("bare Optional<Self> (not indirected)", &type_defs);
}
#[test]
fn a_map_field_struct_with_no_self_reference_agrees() {
let type_defs = vec![dto(
"DownloadedDocument",
vec![
field("url", TypeRef::String),
field("mimeType", TypeRef::String),
FieldDef {
name: "content".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Primitive(crate::core::ir::PrimitiveType::U8))),
binding_excluded: true,
..FieldDef::default()
},
field("size", TypeRef::Primitive(crate::core::ir::PrimitiveType::Usize)),
field("filename", TypeRef::Optional(Box::new(TypeRef::String))),
field("contentHash", TypeRef::String),
field(
"headers",
TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
),
field("truncated", TypeRef::Primitive(crate::core::ir::PrimitiveType::Bool)),
field("contentPath", TypeRef::Optional(Box::new(TypeRef::String))),
],
)];
assert_parity(
"map-field struct, no self-reference (DownloadedDocument shape)",
&type_defs,
);
let (binding, _) = both_classifiers(&type_defs);
assert!(
binding.contains("DownloadedDocument"),
"the map-field shape must be first-class on the binding side, else the parity assertion \
above proves nothing; got: {binding:?}"
);
}