use super::types::gen_options_py;
use crate::core::config::DtoConfig;
use crate::core::ir::{ApiSurface, FieldDef, TypeDef, TypeRef};
const THRESHOLDS: &str = "Thresholds";
const PIPELINE: &str = "Pipeline";
const DEFAULTED_PIPELINE: &str = "DefaultedPipeline";
fn bare_serde_default_field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_owned(),
ty,
optional: false,
default: Some("/* serde(default) */".to_owned()),
typed_default: None,
..FieldDef::default()
}
}
fn api_surface() -> ApiSurface {
let thresholds = TypeDef {
name: THRESHOLDS.to_owned(),
rust_path: format!("sample_core::{THRESHOLDS}"),
has_default: true,
fields: vec![FieldDef {
name: "min_quality".to_owned(),
ty: TypeRef::Primitive(crate::core::ir::PrimitiveType::F64),
..FieldDef::default()
}],
..TypeDef::default()
};
let pipeline = TypeDef {
name: PIPELINE.to_owned(),
rust_path: format!("sample_core::{PIPELINE}"),
has_default: false,
fields: vec![
FieldDef {
name: "stages".to_owned(),
ty: TypeRef::Vec(Box::new(TypeRef::String)),
optional: false,
..FieldDef::default()
},
bare_serde_default_field("thresholds", TypeRef::Named(THRESHOLDS.to_owned())),
],
..TypeDef::default()
};
let defaulted_pipeline = TypeDef {
name: DEFAULTED_PIPELINE.to_owned(),
rust_path: format!("sample_core::{DEFAULTED_PIPELINE}"),
has_default: true,
fields: vec![bare_serde_default_field(
"thresholds",
TypeRef::Named(THRESHOLDS.to_owned()),
)],
..TypeDef::default()
};
ApiSurface {
types: vec![thresholds, pipeline, defaulted_pipeline],
..ApiSurface::default()
}
}
fn render() -> String {
gen_options_py(&api_surface(), "_internal_bindings", &DtoConfig::default(), &[])
}
#[test]
fn bare_serde_default_field_on_closure_only_type_is_required() {
let options_py = render();
assert!(
options_py.contains("class Pipeline:"),
"options.py must define the Pipeline dataclass:\n{options_py}"
);
let pipeline_block = {
let start = options_py
.find("class Pipeline:")
.expect("Pipeline dataclass must be rendered");
let rest = &options_py[start..];
let end = rest[1..].find("@dataclass").map_or(rest.len(), |offset| offset + 1);
&rest[..end]
};
assert!(
pipeline_block.contains(" thresholds: Thresholds\n"),
"the field must render with no default (bare `name: Type`, no `= ...`):\n{pipeline_block}"
);
assert!(
!pipeline_block.contains("thresholds: Thresholds | None"),
"the field must not be widened to Optional -- the native constructor requires a real \
instance:\n{pipeline_block}"
);
assert!(
!pipeline_block.contains("thresholds: Thresholds = None"),
"the field must not be given a fabricated `None` default:\n{pipeline_block}"
);
}
#[test]
fn bare_serde_default_field_on_has_default_type_stays_optional() {
let options_py = render();
assert!(
options_py.contains("class DefaultedPipeline:"),
"options.py must define the DefaultedPipeline dataclass:\n{options_py}"
);
assert!(
options_py.contains("thresholds: Thresholds | None = None"),
"on a `has_default` parent, the field must keep its `None`-fallback default:\n{options_py}"
);
}