greentic-flow 0.4.63

Generic YGTC flow schema/loader/IR for self-describing component nodes.
Documentation
use crate::{
    error::{FlowError, FlowErrorLocation, Result},
    flow_ir::Route,
    splice::NEXT_NODE_PLACEHOLDER,
};

pub fn rewrite_placeholder_routes(
    provided: Vec<Route>,
    fallback: &[Route],
    allow_cycles: bool,
    anchor: &str,
    require_placeholder: bool,
) -> std::result::Result<Vec<Route>, String> {
    let mut out = Vec::new();
    let mut replaced = false;
    for route in provided {
        if let Some(to) = &route.to
            && to == NEXT_NODE_PLACEHOLDER
        {
            replaced = true;
            for f in fallback {
                if !allow_cycles && f.to.as_deref() == Some(anchor) {
                    return Err("routing would introduce a cycle back to anchor".to_string());
                }
                out.push(f.clone());
            }
            continue;
        }
        if !allow_cycles && route.to.as_deref() == Some(anchor) {
            return Err("routing would introduce a cycle back to anchor".to_string());
        }
        out.push(route);
    }

    if !replaced && require_placeholder {
        return Err(
            "Config flow output missing NEXT_NODE_PLACEHOLDER; cannot preserve anchor routing semantics."
                .to_string(),
        );
    }

    if !replaced && require_placeholder {
        for f in fallback {
            if !allow_cycles && f.to.as_deref() == Some(anchor) {
                return Err("routing would introduce a cycle back to anchor".to_string());
            }
        }
        out.extend_from_slice(fallback);
    }
    Ok(out)
}

pub fn apply_threaded_routing(
    new_node_id: &str,
    prior_routes: &[Route],
    allow_cycles: bool,
    anchor: &str,
) -> Result<Vec<Route>> {
    if !allow_cycles {
        for r in prior_routes {
            if r.to.as_deref() == Some(anchor) {
                return Err(FlowError::Routing {
                    node_id: anchor.to_string(),
                    message: "inserting step would create a cycle back to anchor".to_string(),
                    location: FlowErrorLocation::at_path(format!("nodes.{anchor}.routing")),
                });
            }
        }
    }

    Ok(vec![Route {
        to: Some(new_node_id.to_string()),
        ..Route::default()
    }])
}