feat(0088): §A shared gate predicates — edge_kind_check, resolution helpers, try_bind
Iteration-1 §A of the construction service (#157): the surface-agnostic shared predicates the eager op-script path and the holistic finalize gates both call — no second validator (C24). - edge_kind_check (blueprint.rs): the per-edge producer/consumer kind check, lifted verbatim out of validate_wiring's edge loop into a pub(crate) fn that validate_wiring now calls — behaviour-preserving (same Bootstrap(KindMismatch) variant; the existing compile-time gate test stays green). - resolve_input_slot / resolve_output_field (builder.rs): the exactly-one-match name→index resolution extracted as pub(crate) fns over (&NodeSchema, &str), so the runtime-String op-script names share GraphBuilder's resolution; GraphBuilder delegates and maps to the unchanged BuildError variants. - try_bind + BindOpError (aura-core node.rs): the fallible Result twin of bind (bind keeps its panic contract and pinned messages verbatim; try_bind is a separate method reporting the same three conditions as values). - check_param_namespace_injective + validate_wiring widened to pub(crate) for the finalize-stage reuse by the upcoming GraphSession::finish (§B). Partial iteration: §B (the GraphSession/Op/replay surface + introspection) and the §A→§B integration land next (plan Tasks 4-8). compile_with_params / check_ports_connected are behaviourally unchanged. Full suite green; clippy clean. Plan correction folded in: Task 3's test originally referenced aura_std::Sma — infeasible inside aura-core (aura-std depends on aura-core; the reverse edge is a dependency cycle). Adapted to a local PrimitiveBuilder probe with identical assertions. refs #157
This commit is contained in:
@@ -162,17 +162,14 @@ impl GraphBuilder {
|
||||
.schemas
|
||||
.get(p.node)
|
||||
.ok_or(BuildError::BadHandle { node: p.node })?;
|
||||
let m: Vec<usize> = schema
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, port)| port.name == p.name)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
match m.as_slice() {
|
||||
[i] => Ok((p.node, *i)),
|
||||
[] => Err(BuildError::UnknownInPort { node: p.node, name: p.name.to_string() }),
|
||||
_ => Err(BuildError::AmbiguousInPort { node: p.node, name: p.name.to_string() }),
|
||||
match resolve_input_slot(schema, p.name) {
|
||||
Ok(i) => Ok((p.node, i)),
|
||||
Err(PortResolveError::Unknown) => {
|
||||
Err(BuildError::UnknownInPort { node: p.node, name: p.name.to_string() })
|
||||
}
|
||||
Err(PortResolveError::Ambiguous) => {
|
||||
Err(BuildError::AmbiguousInPort { node: p.node, name: p.name.to_string() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,21 +180,59 @@ impl GraphBuilder {
|
||||
.schemas
|
||||
.get(p.node)
|
||||
.ok_or(BuildError::BadHandle { node: p.node })?;
|
||||
let m: Vec<usize> = schema
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, f)| f.name == p.name)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
match m.as_slice() {
|
||||
[i] => Ok(*i),
|
||||
[] => Err(BuildError::UnknownOutPort { node: p.node, name: p.name.to_string() }),
|
||||
_ => Err(BuildError::AmbiguousOutPort { node: p.node, name: p.name.to_string() }),
|
||||
match resolve_output_field(schema, p.name) {
|
||||
Ok(i) => Ok(i),
|
||||
Err(PortResolveError::Unknown) => {
|
||||
Err(BuildError::UnknownOutPort { node: p.node, name: p.name.to_string() })
|
||||
}
|
||||
Err(PortResolveError::Ambiguous) => {
|
||||
Err(BuildError::AmbiguousOutPort { node: p.node, name: p.name.to_string() })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A name→index resolution outcome, shared by `GraphBuilder` (mapped to
|
||||
/// `BuildError`) and `GraphSession` (mapped to `OpError`). Over `&str`, so it
|
||||
/// serves both `&'static str` literals and runtime document names.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum PortResolveError {
|
||||
Unknown,
|
||||
Ambiguous,
|
||||
}
|
||||
|
||||
/// Resolve an input-port name to its slot index by exactly-one-match.
|
||||
pub(crate) fn resolve_input_slot(schema: &NodeSchema, name: &str) -> Result<usize, PortResolveError> {
|
||||
let m: Vec<usize> = schema
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, port)| port.name == name)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
match m.as_slice() {
|
||||
[i] => Ok(*i),
|
||||
[] => Err(PortResolveError::Unknown),
|
||||
_ => Err(PortResolveError::Ambiguous),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve an output-field name to its field index by exactly-one-match.
|
||||
pub(crate) fn resolve_output_field(schema: &NodeSchema, name: &str) -> Result<usize, PortResolveError> {
|
||||
let m: Vec<usize> = schema
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, f)| f.name == name)
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
match m.as_slice() {
|
||||
[i] => Ok(*i),
|
||||
[] => Err(PortResolveError::Unknown),
|
||||
_ => Err(PortResolveError::Ambiguous),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BuildError, GraphBuilder};
|
||||
@@ -297,6 +332,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_port_resolution_matches_exactly_one() {
|
||||
use super::{resolve_input_slot, resolve_output_field, PortResolveError};
|
||||
use crate::BlueprintNode;
|
||||
use aura_std::Sub;
|
||||
let schema = BlueprintNode::from(Sub::builder()).signature(); // lhs, rhs -> value
|
||||
assert_eq!(resolve_input_slot(&schema, "lhs"), Ok(0));
|
||||
assert_eq!(resolve_input_slot(&schema, "rhs"), Ok(1));
|
||||
assert_eq!(resolve_input_slot(&schema, "nope"), Err(PortResolveError::Unknown));
|
||||
assert_eq!(resolve_output_field(&schema, "value"), Ok(0));
|
||||
assert_eq!(resolve_output_field(&schema, "nope"), Err(PortResolveError::Unknown));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_in_port_is_reported_at_build() {
|
||||
let mut g = GraphBuilder::new("x");
|
||||
|
||||
Reference in New Issue
Block a user