Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions libraries/core/src/descriptor/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ pub fn check_module_file(module_path: &Path) -> eyre::Result<()> {
//
// Load nested module to collect its declared outputs.
let nested_module = load_module_file(&nested_canonical)?;
check_nested_module_required_inputs(
&module_file.module.name,
&node.id,
&nested_module.module,
&node.inputs,
)?;
for output in &nested_module.module.outputs {
inner_outputs.insert(output.to_string());
}
Expand Down Expand Up @@ -284,6 +290,27 @@ fn reject_duplicate_ports(module_name: &str, field: &str, ports: &[DataId]) -> e
Ok(())
}

fn check_nested_module_required_inputs(
module_name: &str,
node_id: &NodeId,
nested_module: &ModuleHeader,
node_inputs: &BTreeMap<DataId, Input>,
) -> eyre::Result<()> {
for declared_input in &nested_module.inputs {
if !node_inputs.contains_key(declared_input) {
bail!(
"module `{}`: nested module `{}` declares required input `{}` \
but node `{}` does not provide it",
module_name,
nested_module.name,
declared_input,
node_id,
);
}
}
Ok(())
}

/// Check that every `_mod/X` reference in `inputs` points to a declared
/// module input (or optional input). Shared by the node-level `inputs`
/// check and the operator/custom `config.inputs` / `run_config.inputs`
Expand Down Expand Up @@ -2231,6 +2258,52 @@ nodes:
assert!(result.unwrap_err().to_string().contains("missing"));
}

#[test]
fn check_module_file_rejects_nested_module_missing_required_input() {
let tmp = TempDir::new().unwrap();

write_file(
tmp.path(),
"leaf.yml",
r#"
module:
name: leaf
inputs: [data]
outputs: [out]

nodes:
- id: worker
path: worker.py
inputs:
x: _mod/data
outputs:
- out
"#,
);

let path = write_file(
tmp.path(),
"outer.yml",
r#"
module:
name: outer
inputs: []
outputs: [out]

nodes:
- id: nested
module: leaf.yml
"#,
);

let result = check_module_file(&path);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(msg.contains("leaf"), "got: {msg}");
assert!(msg.contains("data"), "got: {msg}");
assert!(msg.contains("nested"), "got: {msg}");
}

/// Regression test for #2851: `check_module_file` must accept a nested
/// module reference that points to a sibling directory inside the same
/// project (e.g. `../shared/base.yml`). The real expansion path
Expand Down