use crate::backends::swift::gen_bindings::trait_bridge::gen_trait_bridge_files;
use crate::core::config::TraitBridgeConfig;
use crate::core::ir::{ApiSurface, EnumDef, EnumVariant, MethodDef, ParamDef, PrimitiveType, TypeDef, TypeRef};
use std::collections::HashSet;
fn param(name: &str, ty: TypeRef) -> ParamDef {
ParamDef {
name: name.to_string(),
ty,
..Default::default()
}
}
fn method(name: &str, params: Vec<ParamDef>, return_type: TypeRef, error_type: Option<&str>) -> MethodDef {
MethodDef {
name: name.to_string(),
params,
return_type,
error_type: error_type.map(|name| name.to_string()),
..Default::default()
}
}
fn make_trait(name: &str, methods: Vec<MethodDef>) -> TypeDef {
TypeDef {
name: name.to_string(),
rust_path: format!("testcrate::{name}"),
is_trait: true,
methods,
..Default::default()
}
}
fn bridge_source(trait_def: &TypeDef) -> String {
bridge_source_with_api(trait_def, &ApiSurface::default())
}
fn bridge_source_with_api(trait_def: &TypeDef, api: &ApiSurface) -> String {
let bridge_cfg = TraitBridgeConfig {
trait_name: trait_def.name.clone(),
register_fn: Some(format!("register{}", trait_def.name)),
..Default::default()
};
let bridges = vec![(trait_def.name.clone(), &bridge_cfg, trait_def)];
let files = gen_trait_bridge_files(&bridges, &HashSet::new(), &HashSet::new(), api);
let wanted = format!("Swift{}Bridge.swift", trait_def.name);
files
.into_iter()
.find(|(name, _)| *name == wanted)
.unwrap_or_else(|| panic!("expected {wanted} among generated trait bridge files"))
.1
}
#[test]
fn adapter_emits_a_call_method_for_every_protocol_method() {
let source = bridge_source(&make_trait(
"TextBackend",
vec![
method(
"find_all",
vec![param("text", TypeRef::String)],
TypeRef::Vec(Box::new(TypeRef::String)),
Some("BackendError"),
),
method("scan", vec![], TypeRef::Primitive(PrimitiveType::Bool), None),
],
));
assert!(
source.contains("func findAll(text: String) throws -> [String]"),
"protocol must declare findAll: {source}"
);
assert!(
source.contains("func findAllCall(text: String) throws -> String {"),
"adapter must register findAllCall: {source}"
);
assert!(
source.contains("func scan() -> Bool"),
"protocol must declare scan: {source}"
);
assert!(
source.contains("func scanCall() -> Bool {"),
"adapter must register scanCall: {source}"
);
}
#[test]
fn adapter_converts_string_return_to_native_string() {
let source = bridge_source(&make_trait(
"TextBackend",
vec![method("extract_text", vec![], TypeRef::String, Some("BackendError"))],
));
assert!(
source.contains("return marshal_ok_result(String(result))"),
"String returns must be wrapped via String(result): {source}"
);
}
#[test]
fn adapter_converts_vec_string_return_element_wise() {
let source = bridge_source(&make_trait(
"TextBackend",
vec![method(
"find_all",
vec![],
TypeRef::Vec(Box::new(TypeRef::String)),
Some("BackendError"),
)],
));
assert!(
source.contains("return marshal_ok_result(result.map { String($0) })"),
"Vec<String> returns must be converted element-wise: {source}"
);
}
#[test]
fn default_method_for_enum_return_constructs_a_real_enum_case() {
let enum_def = EnumDef {
name: "ConfidenceLevel".to_string(),
rust_path: "testcrate::ConfidenceLevel".to_string(),
variants: vec![
EnumVariant {
name: "High".to_string(),
..Default::default()
},
EnumVariant {
name: "Low".to_string(),
..Default::default()
},
],
has_serde: true,
..Default::default()
};
let api = ApiSurface {
enums: vec![enum_def],
..Default::default()
};
let mut confidence_method = method(
"confidence_level",
vec![],
TypeRef::Named("ConfidenceLevel".to_string()),
None,
);
confidence_method.has_default_impl = true;
let source = bridge_source_with_api(&make_trait("TextBackend", vec![confidence_method]), &api);
assert!(
source.contains("return .high"),
"default stub must construct a real ConfidenceLevel case, got:\n{source}"
);
assert!(
!source.contains("return \"{}\""),
"default stub must not return a bare string literal for an enum-typed return, got:\n{source}"
);
}