use std::collections::HashMap;
use super::gen_api_py;
use crate::core::config::DtoConfig;
use crate::core::ir::{ApiSurface, FieldDef, FunctionDef, ParamDef, TypeDef, TypeRef};
const MODULE_NAME: &str = "_internal_bindings";
const PACKAGE_NAME: &str = "sample_pkg";
const RUN_CONFIG: &str = "RunConfig";
const CHUNK_PLAN: &str = "ChunkPlan";
fn api_surface() -> ApiSurface {
let run_config = TypeDef {
name: RUN_CONFIG.to_owned(),
rust_path: format!("sample_core::{RUN_CONFIG}"),
has_default: true,
fields: vec![FieldDef {
name: "label".to_owned(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
};
let chunk_plan = TypeDef {
name: CHUNK_PLAN.to_owned(),
rust_path: format!("sample_core::{CHUNK_PLAN}"),
has_default: false,
fields: vec![FieldDef {
name: "seed".to_owned(),
ty: TypeRef::Named(RUN_CONFIG.to_owned()),
optional: false,
..FieldDef::default()
}],
..TypeDef::default()
};
let configure = FunctionDef {
name: "configure".to_owned(),
rust_path: "sample_core::configure".to_owned(),
params: vec![ParamDef {
name: "config".to_owned(),
ty: TypeRef::Named(RUN_CONFIG.to_owned()),
optional: false,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
..FunctionDef::default()
};
let plan_chunks = FunctionDef {
name: "plan_chunks".to_owned(),
rust_path: "sample_core::plan_chunks".to_owned(),
params: vec![ParamDef {
name: "plan".to_owned(),
ty: TypeRef::Named(CHUNK_PLAN.to_owned()),
optional: false,
..ParamDef::default()
}],
return_type: TypeRef::Unit,
..FunctionDef::default()
};
ApiSurface {
types: vec![run_config, chunk_plan],
functions: vec![configure, plan_chunks],
..ApiSurface::default()
}
}
fn render(api: &ApiSurface) -> String {
gen_api_py(
api,
MODULE_NAME,
PACKAGE_NAME,
&[],
&DtoConfig::default(),
&HashMap::new(),
&HashMap::new(),
&[],
&[],
&ahash::AHashSet::new(),
&crate::core::config::ResolvedCrateConfig::default(),
)
}
#[test]
fn closure_only_param_is_not_granted_a_facade_default() {
let api_py = render(&api_surface());
assert!(
api_py.contains("def plan_chunks(plan: ChunkPlan)"),
"a `ChunkPlan` param must render as required (no `| None = None`), matching the native \
constructor's real required fields:\n{api_py}"
);
}
#[test]
fn closure_only_param_never_calls_the_bare_native_constructor() {
let api_py = render(&api_surface());
assert!(
!api_py.contains("_rust.ChunkPlan()"),
"no code path may call the bare, argument-less native constructor for a type with \
required fields:\n{api_py}"
);
}
#[test]
fn closure_only_param_converts_unconditionally() {
let api_py = render(&api_surface());
assert!(
api_py.contains("_rust_plan = _to_rust_chunk_plan(plan)\n"),
"the required param must convert straight through with no ternary/None-guard:\n{api_py}"
);
}
#[test]
fn has_default_param_keeps_its_facade_default_and_bare_fallback() {
let api_py = render(&api_surface());
assert!(
api_py.contains("def configure(config: RunConfig | None = None)"),
"a `has_default` param must keep its facade `= None` convenience default:\n{api_py}"
);
assert!(
api_py.contains("_rust_config = _to_rust_run_config(config) if config is not None else _rust.RunConfig()"),
"a `has_default` param must keep the bare-constructor fallback -- it is safe here \
because `RunConfig` has no required fields:\n{api_py}"
);
}