Skip to main content

klieo_workflow/
error.rs

1//! Typed compile-time failures. One variant per failure class — no
2//! stringly-typed errors.
3
4use thiserror::Error;
5
6/// Why a [`crate::WorkflowDef`] could not be compiled into a flow.
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum CompileError {
10    /// Two nodes share an id.
11    #[error("duplicate node id: {0:?}")]
12    DuplicateNodeId(String),
13    /// A node references an id not present in the registry.
14    #[error("unknown {kind} in node {node:?}: {id:?} not registered")]
15    UnknownRef {
16        /// One of `model` | `tool` | `subflow`.
17        kind: &'static str,
18        /// Node that made the reference.
19        node: String,
20        /// The missing id.
21        id: String,
22    },
23    /// `entry` names a node that does not exist.
24    #[error("entry node not found: {0:?}")]
25    MissingEntry(String),
26    /// An edge endpoint (`from`/`to`) names a node that does not exist.
27    #[error("edge endpoint not found: {0:?}")]
28    MissingEndpoint(String),
29    /// More than one edge shares a `from` node.
30    #[error("multiple edges from node: {0:?}")]
31    DuplicateEdgeSource(String),
32    /// An agent/tool node is missing a required `input_from`/`output_to`.
33    #[error("node {node:?} missing required field: {field}")]
34    MissingField {
35        /// Node id.
36        node: String,
37        /// The missing field name.
38        field: &'static str,
39    },
40    /// An edge names no target node — neither an unconditional `to` nor a
41    /// conditional `then`/`else` pair.
42    #[error("edge from {0:?} has no target node")]
43    EdgeMissingBranchTarget(String),
44    /// An edge specifies both an unconditional `to` and a conditional
45    /// `when`; the two forms are mutually exclusive.
46    #[error("edge from {0:?} has both `to` and `when`")]
47    EdgeHasBothToAndWhen(String),
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn messages_include_offending_id() {
56        let e = CompileError::DuplicateNodeId("a".into());
57        assert!(e.to_string().contains("a"));
58        let e = CompileError::MissingEntry("start".into());
59        assert!(e.to_string().contains("start"));
60    }
61}