Skip to main content

stepflow_client/
flow.rs

1// Copyright 2025 DataStax Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the License
9// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10// or implied. See the License for the specific language governing permissions and limitations under
11// the License.
12
13//! Flow definition types and programmatic builder API.
14//!
15//! Types are re-exported from [`stepflow_flow`] — the canonical definitions shared
16//! with the orchestrator. The [`FlowBuilder`] provides a convenient API for
17//! constructing flows programmatically.
18//!
19//! # Quick start
20//!
21//! ```rust
22//! use stepflow_client::{FlowBuilder, ValueExpr};
23//! use stepflow_flow::values::JsonPath;
24//!
25//! let mut builder = FlowBuilder::new();
26//! builder.add_step(
27//!     "say_hello",
28//!     "/builtin/eval",
29//!     ValueExpr::object(vec![("message".to_string(), ValueExpr::workflow_input(JsonPath::parse("$.name").unwrap()))]),
30//! );
31//! let flow = builder
32//!     .output(ValueExpr::step_output("say_hello"))
33//!     .build()
34//!     .unwrap();
35//!
36//! let json = serde_json::to_value(&flow).unwrap();
37//! ```
38
39use std::collections::HashMap;
40
41use crate::error::BuilderError;
42
43// Re-export the canonical types from stepflow-flow.
44pub use stepflow_flow::ValueExpr;
45pub use stepflow_flow::values::{JsonPath, ValueRef};
46pub use stepflow_flow::workflow::{
47    Component, ErrorAction, ExampleInput, Flow, FlowRef, FlowSchema, Step, TestCase, TestConfig,
48};
49
50// ---------------------------------------------------------------------------
51// FlowBuilder
52// ---------------------------------------------------------------------------
53
54/// Programmatically build a [`Flow`] definition.
55///
56/// # Example
57///
58/// ```rust
59/// use stepflow_client::{FlowBuilder, ValueExpr};
60///
61/// let mut builder = FlowBuilder::new();
62/// builder.add_step(
63///     "summarize",
64///     "/builtin/openai",
65///     ValueExpr::object(vec![
66///         ("model".to_string(), ValueExpr::literal(serde_json::json!("gpt-4o"))),
67///     ]),
68/// );
69/// let flow = builder
70///     .output(ValueExpr::step_output("summarize"))
71///     .build()
72///     .unwrap();
73/// ```
74#[derive(Debug, Default)]
75pub struct FlowBuilder {
76    name: Option<String>,
77    description: Option<String>,
78    version: Option<String>,
79    steps: Vec<Step>,
80    step_ids: std::collections::HashSet<String>,
81    output: Option<ValueExpr>,
82    metadata: HashMap<String, serde_json::Value>,
83}
84
85impl FlowBuilder {
86    /// Create a new, empty builder.
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Set the flow name.
92    pub fn name(mut self, name: impl Into<String>) -> Self {
93        self.name = Some(name.into());
94        self
95    }
96
97    /// Set the flow description.
98    pub fn description(mut self, description: impl Into<String>) -> Self {
99        self.description = Some(description.into());
100        self
101    }
102
103    /// Set the flow version.
104    pub fn version(mut self, version: impl Into<String>) -> Self {
105        self.version = Some(version.into());
106        self
107    }
108
109    /// Add a metadata entry.
110    pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
111        self.metadata.insert(key.into(), value);
112        self
113    }
114
115    /// Add a step to the flow.
116    ///
117    /// # Panics
118    ///
119    /// Panics if a step with the same ID already exists.
120    pub fn add_step(
121        &mut self,
122        id: impl Into<String>,
123        component: impl Into<String>,
124        input: impl Into<ValueExpr>,
125    ) {
126        self.try_add_step(id, component, input)
127            .unwrap_or_else(|e| panic!("{e}"));
128    }
129
130    /// Add a step to the flow, returning an error on duplicate IDs.
131    pub fn try_add_step(
132        &mut self,
133        id: impl Into<String>,
134        component: impl Into<String>,
135        input: impl Into<ValueExpr>,
136    ) -> Result<(), BuilderError> {
137        let id = id.into();
138        if self.step_ids.contains(&id) {
139            return Err(BuilderError::DuplicateStep(id));
140        }
141        self.step_ids.insert(id.clone());
142        self.steps.push(Step {
143            id,
144            component: Component::from_string(component.into()),
145            input: input.into(),
146            on_error: None,
147            must_execute: None,
148            metadata: HashMap::new(),
149        });
150        Ok(())
151    }
152
153    /// Set the flow output expression.
154    pub fn output(mut self, expr: impl Into<ValueExpr>) -> Self {
155        self.output = Some(expr.into());
156        self
157    }
158
159    /// Build the [`Flow`].
160    pub fn build(self) -> Result<Flow, BuilderError> {
161        Ok(Flow {
162            name: self.name,
163            description: self.description,
164            version: self.version,
165            steps: self.steps,
166            output: self.output.unwrap_or_else(ValueExpr::null),
167            metadata: self.metadata,
168            ..Default::default()
169        })
170    }
171}
172
173// ---------------------------------------------------------------------------
174// Tests
175// ---------------------------------------------------------------------------
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use serde_json::json;
181
182    #[test]
183    fn test_flow_builder() {
184        let mut builder = FlowBuilder::new();
185        builder.add_step("greet", "/builtin/eval", ValueExpr::null());
186        let flow = builder
187            .output(ValueExpr::step_output("greet"))
188            .build()
189            .unwrap();
190
191        assert_eq!(flow.steps.len(), 1);
192        assert_eq!(flow.steps[0].id, "greet");
193
194        let json = serde_json::to_value(&flow).unwrap();
195        assert_eq!(json["steps"][0]["id"], "greet");
196        assert_eq!(json["output"]["$step"], "greet");
197    }
198
199    #[test]
200    fn test_flow_serialization() {
201        let mut builder = FlowBuilder::new().name("test");
202        builder.add_step(
203            "step1",
204            "/builtin/eval",
205            ValueExpr::object(vec![(
206                "model".to_string(),
207                ValueExpr::literal(json!("gpt-4o")),
208            )]),
209        );
210        let flow = builder
211            .output(ValueExpr::step_output("step1"))
212            .build()
213            .unwrap();
214
215        let json = serde_json::to_value(&flow).unwrap();
216        assert_eq!(json["name"], "test");
217        assert_eq!(json["steps"][0]["component"], "/builtin/eval");
218    }
219}