use crate::ast::GraphDef;
use std::collections::BTreeSet;
pub fn validate_definition(g: &GraphDef) -> Result<(), String> {
let mut node_ix = BTreeSet::new();
for n in &g.nodes {
if !node_ix.insert(n.idx) {
return Err(format!("duplicate node index {}", n.idx));
}
}
if node_ix.len() != g.nodes.len() || !node_ix.iter().copied().enumerate().all(|(i, x)| i == x) {
return Err("node indexes must be 0..nodes.len()-1".into());
}
let mut edge_ix = BTreeSet::new();
for e in &g.edges {
if !edge_ix.insert(e.idx) {
return Err(format!("duplicate edge index {}", e.idx));
}
}
if edge_ix.len() != g.edges.len() || !edge_ix.iter().copied().enumerate().all(|(i, x)| i == x) {
return Err("edge indexes must be 0..edges.len()-1".into());
}
use quote::ToTokens;
use std::collections::BTreeMap;
let mut in_q_by_node: BTreeMap<usize, String> = BTreeMap::new();
let mut out_q_by_node: BTreeMap<usize, String> = BTreeMap::new();
let mut in_m_by_node: BTreeMap<usize, String> = BTreeMap::new();
let mut out_m_by_node: BTreeMap<usize, String> = BTreeMap::new();
for e in &g.edges {
let from = g
.nodes
.get(e.from_node)
.ok_or_else(|| format!("edge {} from.node out of range", e.idx))?;
if e.from_port >= from.out_ports {
return Err(format!(
"edge {} from.port {} >= node{}.out_ports {}",
e.idx, e.from_port, e.from_node, from.out_ports
));
}
let to = g
.nodes
.get(e.to_node)
.ok_or_else(|| format!("edge {} to.node out of range", e.idx))?;
if e.to_port >= to.in_ports {
return Err(format!(
"edge {} to.port {} >= node{}.in_ports {}",
e.idx, e.to_port, e.to_node, to.in_ports
));
}
let e_payload = e.payload.to_token_stream().to_string();
let from_out = from.out_payload.to_token_stream().to_string();
let to_in = to.in_payload.to_token_stream().to_string();
if e_payload != from_out {
return Err(format!(
"edge {} payload {:?} != node{}.out_payload {:?}",
e.idx, e_payload, e.from_node, from_out
));
}
if e_payload != to_in {
return Err(format!(
"edge {} payload {:?} != node{}.in_payload {:?}",
e.idx, e_payload, e.to_node, to_in
));
}
let qn = e.ty.to_token_stream().to_string();
in_q_by_node
.entry(e.to_node)
.and_modify(|s| {
if *s != qn {
*s = "__MISMATCH__".into()
}
})
.or_insert(qn.clone());
out_q_by_node
.entry(e.from_node)
.and_modify(|s| {
if *s != qn {
*s = "__MISMATCH__".into()
}
})
.or_insert(qn);
let mn = e.manager_ty.to_token_stream().to_string();
in_m_by_node
.entry(e.to_node)
.and_modify(|s| {
if *s != mn {
*s = "__MISMATCH__".into()
}
})
.or_insert(mn.clone());
out_m_by_node
.entry(e.from_node)
.and_modify(|s| {
if *s != mn {
*s = "__MISMATCH__".into()
}
})
.or_insert(mn);
}
for (i, s) in in_q_by_node {
if s == "__MISMATCH__" {
return Err(format!("node {} has non-uniform input queue types", i));
}
}
for (i, s) in out_q_by_node {
if s == "__MISMATCH__" {
return Err(format!("node {} has non-uniform output queue types", i));
}
}
for (i, s) in in_m_by_node {
if s == "__MISMATCH__" {
return Err(format!("node {} has non-uniform input manager types", i));
}
}
for (i, s) in out_m_by_node {
if s == "__MISMATCH__" {
return Err(format!("node {} has non-uniform output manager types", i));
}
}
for n in &g.nodes {
let is_source = n.in_ports == 0 && n.out_ports > 0;
if is_source && n.ingress_policy_opt.is_none() {
return Err(format!(
"source node {} must declare `ingress_policy` to expose external ingress",
n.idx
));
}
}
Ok(())
}