Skip to main content

greentic_types/
node_io.rs

1//! Uniform node I/O contract types (spec §4.2–§4.4).
2//!
3//! - [`NodeOutput`] — a node's output, **mutually exclusive** `{data}` (success)
4//!   or `{errors}` (failure), per JSON:API / JSON-RPC 2.0.
5//! - [`NodeError`] — a Temporal-shaped error object (`code`/`message`/`kind`/
6//!   `retryable`/`source`/`details`).
7//! - [`InputValue`] / [`StructuredInput`] — explicit literal-vs-ref-vs-template
8//!   inputs (Step Functions lesson: encode it structurally, never sniff delimiters).
9//! - [`FlowState`] — the per-node output namespace, keyed by node id (n8n model).
10
11use alloc::collections::BTreeMap;
12use alloc::string::String;
13use alloc::vec::Vec;
14
15use serde_json::Value;
16
17#[cfg(feature = "schemars")]
18use schemars::JsonSchema;
19#[cfg(feature = "serde")]
20use serde::{Deserialize, Serialize};
21
22/// Coarse, routing/retry-oriented classification of a [`NodeError`]. This is the
23/// branch/retry key (Step Functions `ErrorEquals`, Temporal failure `type`);
24/// `code` stays the canonical open-ended string.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
28#[cfg_attr(feature = "schemars", derive(JsonSchema))]
29pub enum ErrorKind {
30    /// Input failed validation.
31    Validation,
32    /// Operation timed out.
33    Timeout,
34    /// Authentication / authorization failure.
35    Auth,
36    /// Required entity not found.
37    NotFound,
38    /// Throttled by rate limits.
39    RateLimited,
40    /// Unclassified internal failure.
41    Internal,
42    /// Operation cancelled (e.g. a user cancelled a form/QA step).
43    Cancelled,
44}
45
46/// Where an error originated — the node and (optionally) the input that failed.
47#[derive(Clone, Debug, PartialEq)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49#[cfg_attr(feature = "schemars", derive(JsonSchema))]
50pub struct NodeErrorSource {
51    /// The node id that produced the error.
52    pub node: String,
53    /// The input name that triggered it, when applicable.
54    #[cfg_attr(
55        feature = "serde",
56        serde(default, skip_serializing_if = "Option::is_none")
57    )]
58    pub input: Option<String>,
59}
60
61/// A single error in a node's `{errors}` envelope.
62#[derive(Clone, Debug, PartialEq)]
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64#[cfg_attr(feature = "schemars", derive(JsonSchema))]
65pub struct NodeError {
66    /// Stable, author/UI-facing error code (open-ended string, per JSON:API).
67    pub code: String,
68    /// Required human-readable message (one concise sentence).
69    pub message: String,
70    /// Coarse routing/retry classification.
71    pub kind: ErrorKind,
72    /// Explicit retryability — never inferred from `kind` (Temporal's `non_retryable`).
73    pub retryable: bool,
74    /// Optional provenance.
75    #[cfg_attr(
76        feature = "serde",
77        serde(default, skip_serializing_if = "Option::is_none")
78    )]
79    pub source: Option<NodeErrorSource>,
80    /// Free-form structured detail; omitted from the wire when null.
81    #[cfg_attr(
82        feature = "serde",
83        serde(default, skip_serializing_if = "Value::is_null")
84    )]
85    pub details: Value,
86}
87
88/// A node's output: success carries `{data}`, failure carries `{errors}`. The two
89/// are mutually exclusive (enforced by the type); engine metadata lives in a
90/// sibling `meta`, never inside `data`.
91#[derive(Clone, Debug, PartialEq)]
92#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
93#[cfg_attr(feature = "serde", serde(untagged))]
94#[cfg_attr(feature = "schemars", derive(JsonSchema))]
95pub enum NodeOutput {
96    /// Success: arbitrary node-defined fields under `data`.
97    Data {
98        /// The success payload.
99        data: Value,
100    },
101    /// Failure: one or more errors under `errors`.
102    Errors {
103        /// The error list (a node may report several validation failures at once).
104        errors: Vec<NodeError>,
105    },
106}
107
108impl NodeOutput {
109    /// Build a success output `{ data }`.
110    pub fn ok(data: Value) -> Self {
111        Self::Data { data }
112    }
113
114    /// Build a failure output `{ errors }`.
115    pub fn failed(errors: Vec<NodeError>) -> Self {
116        Self::Errors { errors }
117    }
118
119    /// `true` when this is a success (`{data}`) output.
120    pub fn is_ok(&self) -> bool {
121        matches!(self, Self::Data { .. })
122    }
123
124    /// The success payload, or `None` on a failure output.
125    pub fn data(&self) -> Option<&Value> {
126        match self {
127            Self::Data { data } => Some(data),
128            Self::Errors { .. } => None,
129        }
130    }
131
132    /// The error list, or an empty slice on a success output.
133    pub fn errors(&self) -> &[NodeError] {
134        match self {
135            Self::Errors { errors } => errors,
136            Self::Data { .. } => &[],
137        }
138    }
139}
140
141/// An explicit node input value — resolved structurally, never by sniffing
142/// delimiters. Serializes to a single-key object: `{literal|ref|template}`.
143#[derive(Clone, Debug, PartialEq)]
144#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
145#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
146#[cfg_attr(feature = "schemars", derive(JsonSchema))]
147pub enum InputValue {
148    /// Used as-is.
149    Literal(Value),
150    /// A single addressable path (`node.<id>.data.<field>`), resolved to its typed value.
151    Ref(String),
152    /// String interpolation; always yields a string.
153    Template(String),
154}
155
156/// A named node input (`{ name, value }`).
157#[derive(Clone, Debug, PartialEq)]
158#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
159#[cfg_attr(feature = "schemars", derive(JsonSchema))]
160pub struct StructuredInput {
161    /// The input parameter name.
162    pub name: String,
163    /// Its value: literal, reference, or template.
164    pub value: InputValue,
165}
166
167/// The per-node output namespace: each node's [`NodeOutput`] kept immutable,
168/// keyed by its stable node id. Downstream addresses `<node_id>.data.<path>`.
169pub type FlowState = BTreeMap<String, NodeOutput>;