klieo-workflow 3.3.0

Declarative no-code workflow substrate for the klieo agent framework.
Documentation
//! Typed compile-time failures. One variant per failure class — no
//! stringly-typed errors.

use thiserror::Error;

/// Why a [`crate::WorkflowDef`] could not be compiled into a flow.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CompileError {
    /// Two nodes share an id.
    #[error("duplicate node id: {0:?}")]
    DuplicateNodeId(String),
    /// A node references an id not present in the registry.
    #[error("unknown {kind} in node {node:?}: {id:?} not registered")]
    UnknownRef {
        /// One of `model` | `tool` | `subflow`.
        kind: &'static str,
        /// Node that made the reference.
        node: String,
        /// The missing id.
        id: String,
    },
    /// `entry` names a node that does not exist.
    #[error("entry node not found: {0:?}")]
    MissingEntry(String),
    /// An edge endpoint (`from`/`to`) names a node that does not exist.
    #[error("edge endpoint not found: {0:?}")]
    MissingEndpoint(String),
    /// More than one edge shares a `from` node.
    #[error("multiple edges from node: {0:?}")]
    DuplicateEdgeSource(String),
    /// An agent/tool node is missing a required `input_from`/`output_to`.
    #[error("node {node:?} missing required field: {field}")]
    MissingField {
        /// Node id.
        node: String,
        /// The missing field name.
        field: &'static str,
    },
    /// An edge names no target node — neither an unconditional `to` nor a
    /// conditional `then`/`else` pair.
    #[error("edge from {0:?} has no target node")]
    EdgeMissingBranchTarget(String),
    /// An edge specifies both an unconditional `to` and a conditional
    /// `when`; the two forms are mutually exclusive.
    #[error("edge from {0:?} has both `to` and `when`")]
    EdgeHasBothToAndWhen(String),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn messages_include_offending_id() {
        let e = CompileError::DuplicateNodeId("a".into());
        assert!(e.to_string().contains("a"));
        let e = CompileError::MissingEntry("start".into());
        assert!(e.to_string().contains("start"));
    }
}