use super::errors::gen_init_py;
use super::types::{gen_options_py, options_dataclass_type_names};
use crate::core::config::DtoConfig;
use crate::core::ir::{ApiSurface, FieldDef, TypeDef, TypeRef};
const LLM_CONFIG: &str = "LlmConfig";
const CAPTIONING_CONFIG: &str = "CaptioningConfig";
fn captioning_config_api() -> ApiSurface {
ApiSurface {
types: vec![
TypeDef {
name: LLM_CONFIG.to_owned(),
rust_path: format!("sample_core::{LLM_CONFIG}"),
has_default: true,
fields: vec![FieldDef {
name: "model".to_owned(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
},
TypeDef {
name: CAPTIONING_CONFIG.to_owned(),
rust_path: format!("sample_core::{CAPTIONING_CONFIG}"),
has_default: false,
fields: vec![
FieldDef {
name: "llm".to_owned(),
ty: TypeRef::Named(LLM_CONFIG.to_owned()),
optional: false,
..FieldDef::default()
},
FieldDef {
name: "prompt".to_owned(),
ty: TypeRef::Optional(Box::new(TypeRef::String)),
optional: true,
..FieldDef::default()
},
],
..TypeDef::default()
},
],
..ApiSurface::default()
}
}
#[test]
fn options_dataclass_type_names_includes_a_required_field_of_a_dataclass_type() {
let api = captioning_config_api();
let names = options_dataclass_type_names(&api, &[]);
assert!(names.contains(LLM_CONFIG), "the has_default seed must still be present");
assert!(
names.contains(CAPTIONING_CONFIG),
"a native type with no Default of its own, but a required field whose type IS in the \
dataclass set, must join the set too -- otherwise its constructor demands a native \
instance of a type whose public name now resolves to the dataclass twin"
);
}
#[test]
fn gen_options_py_emits_captioning_config_with_a_required_llm_field() {
let api = captioning_config_api();
let options_py = gen_options_py(&api, "_rust", &DtoConfig::default(), &[]);
assert!(
options_py.contains("class CaptioningConfig:"),
"options.py must define the CaptioningConfig dataclass:\n{options_py}"
);
assert!(
options_py.contains(" llm: LlmConfig\n"),
"the required `llm` field must have no default (bare `name: Type`, no `= ...`):\n{options_py}"
);
assert!(
!options_py.contains("llm: LlmConfig ="),
"the required `llm` field must not be given a fabricated default:\n{options_py}"
);
assert!(
!options_py.contains("llm: LlmConfig | None"),
"the required `llm` field must not be widened to Optional:\n{options_py}"
);
}
#[test]
fn gen_init_py_routes_captioning_config_to_options_not_native() {
let api = captioning_config_api();
let init_py = gen_init_py(
&api,
"_rust",
"0.0.0",
&DtoConfig::default(),
&[],
&[],
&std::collections::BTreeMap::new(),
&std::collections::HashMap::new(),
&[],
&std::collections::HashMap::new(),
&ahash::AHashSet::new(),
);
let options_import_start = init_py
.find("from .options import")
.unwrap_or_else(|| panic!("expected a `from .options import ...` statement:\n{init_py}"));
let options_import_block = init_py[options_import_start..]
.split("\n\n")
.next()
.unwrap_or(&init_py[options_import_start..]);
assert!(
options_import_block.contains(CAPTIONING_CONFIG),
"CaptioningConfig must be imported from .options: {options_import_block}"
);
if let Some(native_import_start) = init_py.find("from ._rust import") {
let native_import_block = init_py[native_import_start..]
.split("\n\n")
.next()
.unwrap_or(&init_py[native_import_start..]);
assert!(
!native_import_block.contains(CAPTIONING_CONFIG),
"CaptioningConfig must NOT also be imported from the native module: {native_import_block}"
);
}
}