use alef::backends::swift::SwiftBackend;
use alef::core::backend::Backend;
use alef::core::config::{ResolvedCrateConfig, new_config::NewAlefConfig};
use alef::core::ir::{ApiSurface, CoreWrapper, FieldDef, FunctionDef, ParamDef, PrimitiveType, TypeDef, TypeRef};
fn make_field(name: &str, ty: TypeRef, optional: bool) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
optional,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
serde_rename: None,
serde_flatten: false,
binding_excluded: false,
binding_exclusion_reason: None,
original_type: None,
}
}
fn make_param(name: &str, ty: TypeRef) -> ParamDef {
ParamDef {
name: name.to_string(),
ty,
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: false,
is_mut: false,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: false,
map_is_btree: false,
core_wrapper: alef::core::ir::CoreWrapper::None,
}
}
fn make_type(name: &str, fields: Vec<FieldDef>, has_serde: bool) -> TypeDef {
TypeDef {
name: name.to_string(),
rust_path: format!("mylib::{name}"),
original_rust_path: String::new(),
fields,
methods: vec![],
is_opaque: false,
is_clone: true,
is_copy: false,
doc: String::new(),
cfg: None,
is_trait: false,
has_default: false,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde,
super_traits: vec![],
binding_excluded: false,
binding_exclusion_reason: None,
is_variant_wrapper: false,
has_lifetime_params: false,
}
}
fn make_function(name: &str, params: Vec<ParamDef>, return_type: TypeRef) -> FunctionDef {
FunctionDef {
name: name.to_string(),
rust_path: format!("mylib::{name}"),
original_rust_path: String::new(),
params,
return_type,
is_async: false,
error_type: Some("MyError".to_string()),
doc: String::new(),
cfg: None,
sanitized: false,
return_sanitized: false,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
binding_excluded: false,
binding_exclusion_reason: None,
}
}
fn make_config() -> ResolvedCrateConfig {
let toml = r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "mylib"
sources = ["src/lib.rs"]
"#;
let cfg: NewAlefConfig = toml::from_str(toml).expect("test config must parse");
cfg.resolve().expect("test config must resolve").remove(0)
}
#[test]
fn json_string_overloads_emitted_for_serde_config() {
let config_type = make_type(
"ProcessConfig",
vec![make_field("timeout_ms", TypeRef::Primitive(PrimitiveType::U64), false)],
true, );
let func = make_function(
"process_data",
vec![
make_param("input", TypeRef::String),
make_param("config", TypeRef::Named("ProcessConfig".to_string())),
],
TypeRef::Named("ProcessResult".to_string()),
);
let api = ApiSurface {
crate_name: "mylib".to_string(),
version: "0.1.0".to_string(),
types: vec![config_type],
functions: vec![func],
enums: vec![],
errors: vec![],
excluded_type_paths: Default::default(),
excluded_trait_names: Default::default(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: Vec::new(),
};
let config = make_config();
let swift = SwiftBackend;
let files = swift
.generate_bindings(&api, &config)
.expect("binding generation must succeed");
let content = files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("Mylib.swift"))
.expect("Mylib.swift must be generated")
.content
.clone();
assert!(
content.contains("public func processData(_ input: String, _ configJson: String)"),
"must emit positional-arg JSON-string overload with configJson parameter. Content:\n{content}"
);
assert!(
content.contains("processConfigFromJson(configJson)"),
"must decode JSON config via fromJson helper using configJson parameter. Content:\n{content}"
);
assert!(
content.contains("return try processData(input: input, config: config)"),
"must delegate to typed base function. Content:\n{content}"
);
}
#[test]
fn load_bytes_from_path_or_utf8_helper_emitted() {
let config_type = make_type(
"ProcessConfig",
vec![make_field("timeout_ms", TypeRef::Primitive(PrimitiveType::U64), false)],
true,
);
let func = make_function(
"process_data",
vec![
make_param("input", TypeRef::String),
make_param("config", TypeRef::Named("ProcessConfig".to_string())),
],
TypeRef::Named("ProcessResult".to_string()),
);
let api = ApiSurface {
crate_name: "mylib".to_string(),
version: "0.1.0".to_string(),
types: vec![config_type],
functions: vec![func],
enums: vec![],
errors: vec![],
excluded_type_paths: Default::default(),
excluded_trait_names: Default::default(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: Vec::new(),
};
let config = make_config();
let swift = SwiftBackend;
let files = swift
.generate_bindings(&api, &config)
.expect("binding generation must succeed");
let content = files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("Mylib.swift"))
.expect("Mylib.swift must be generated")
.content
.clone();
assert!(
content.contains("private func _loadBytesFromPathOrUtf8"),
"must emit _loadBytesFromPathOrUtf8 helper. Content:\n{content}"
);
assert!(
content.contains("ALEF_TEST_DOCUMENTS_DIR"),
"must check ALEF_TEST_DOCUMENTS_DIR env var. Content:\n{content}"
);
assert!(
content.contains("pathOrContent.utf8"),
"must fallback to UTF-8 if file not found. Content:\n{content}"
);
}
#[test]
fn json_string_overloads_emitted_for_async_and_sync_functions() {
let config_type = make_type(
"ProcessConfig",
vec![make_field("timeout_ms", TypeRef::Primitive(PrimitiveType::U64), false)],
true,
);
let sync_func = make_function(
"process_data",
vec![make_param("config", TypeRef::Named("ProcessConfig".to_string()))],
TypeRef::Named("ProcessResult".to_string()),
);
let mut async_func = make_function(
"process_data",
vec![make_param("config", TypeRef::Named("ProcessConfig".to_string()))],
TypeRef::Named("ProcessResult".to_string()),
);
async_func.is_async = true;
let api = ApiSurface {
crate_name: "mylib".to_string(),
version: "0.1.0".to_string(),
types: vec![config_type],
functions: vec![sync_func, async_func],
enums: vec![],
errors: vec![],
excluded_type_paths: Default::default(),
excluded_trait_names: Default::default(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: Vec::new(),
};
let config = make_config();
let swift = SwiftBackend;
let files = swift
.generate_bindings(&api, &config)
.expect("binding generation must succeed");
let content = files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("Mylib.swift"))
.expect("Mylib.swift must be generated")
.content
.clone();
assert!(
content.contains("public func processData(_ configJson: String)"),
"must emit JSON-string overload for both async and sync functions with configJson parameter. Content:\n{content}"
);
}