use alef::backends::java::JavaBackend;
use alef::core::backend::Backend;
use alef::core::config::NewAlefConfig;
use alef::core::ir::{ApiSurface, CoreWrapper, FieldDef, PrimitiveType, TypeDef, TypeRef};
fn resolved_one(toml: &str) -> alef::core::config::ResolvedCrateConfig {
let cfg: NewAlefConfig = toml::from_str(toml).unwrap();
cfg.resolve().unwrap().remove(0)
}
fn make_field(name: &str, ty: TypeRef, optional: bool, default: Option<String>) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
optional,
default,
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_type(name: &str, fields: Vec<FieldDef>) -> TypeDef {
TypeDef {
name: name.to_string(),
rust_path: format!("demo::{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: true, super_traits: vec![],
binding_excluded: false,
binding_exclusion_reason: None,
is_variant_wrapper: false,
has_lifetime_params: false,
}
}
#[test]
fn test_java_serde_default_tuple_field_uses_nullable_type_and_null_default() {
let backend = JavaBackend;
let tuple_field = make_field(
"ngram_range",
TypeRef::Vec(Box::new(TypeRef::Primitive(PrimitiveType::U64))),
false, Some("/* serde(default) */".to_string()), );
let typ = make_type("KeywordConfig", vec![tuple_field]);
let api = ApiSurface {
crate_name: "demo".to_string(),
version: "1.0.0".to_string(),
types: vec![typ],
functions: vec![],
enums: vec![],
errors: vec![],
excluded_type_paths: ::std::collections::HashMap::new(),
excluded_trait_names: ::std::collections::HashSet::new(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: Vec::new(),
};
let config = resolved_one(
r#"
[workspace]
languages = ["java", "ffi"]
[[crates]]
name = "demo"
sources = ["src/lib.rs"]
[crates.ffi]
prefix = "demo"
[crates.java]
package = "dev.demo"
"#,
);
let result = backend.generate_bindings(&api, &config);
assert!(result.is_ok(), "generation failed: {:?}", result.err());
let files = result.unwrap();
let type_file = files
.iter()
.find(|f| f.path.to_string_lossy().contains("KeywordConfig.java"))
.expect("KeywordConfig.java not generated");
let content = &type_file.content;
assert!(
content.contains("private List<Long> ngramRange = null;"),
"Builder field should be nullable and initialized to null, but got:\n{content}"
);
assert!(
!content.contains("private List<Long> ngramRange = List.of();"),
"Builder field should not initialize Vec fields to List.of() when they have #[serde(default)], but got:\n{content}"
);
assert!(
content.contains("withNgramRange"),
"Builder should have withNgramRange setter, but got:\n{content}"
);
}