1use alloc::collections::{BTreeMap, BTreeSet};
4use alloc::string::String;
5use core::hash::BuildHasherDefault;
6
7use fnv::FnvHasher;
8use indexmap::IndexMap;
9use serde_json::Value;
10
11use crate::{ComponentId, FlowId, NodeId};
12
13pub type FlowHasher = BuildHasherDefault<FnvHasher>;
15
16#[cfg(feature = "schemars")]
17use schemars::JsonSchema;
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
25#[cfg_attr(feature = "schemars", derive(JsonSchema))]
26pub enum FlowKind {
27 Messaging,
29 Event,
31 ComponentConfig,
33 Job,
35 Http,
37}
38
39#[derive(Clone, Debug, PartialEq)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42#[cfg_attr(
43 feature = "schemars",
44 derive(JsonSchema),
45 schemars(
46 title = "Greentic Flow v1",
47 description = "Canonical flow model with components, routing and telemetry.",
48 rename = "greentic.flow.v1"
49 )
50)]
51pub struct Flow {
52 pub schema_version: String,
54 pub id: FlowId,
56 pub kind: FlowKind,
58 #[cfg_attr(feature = "serde", serde(default))]
60 #[cfg_attr(
61 feature = "schemars",
62 schemars(with = "alloc::collections::BTreeMap<String, Value>")
63 )]
64 pub entrypoints: BTreeMap<String, Value>,
65 #[cfg_attr(feature = "serde", serde(default))]
67 #[cfg_attr(
68 feature = "schemars",
69 schemars(with = "alloc::collections::BTreeMap<NodeId, Node>")
70 )]
71 pub nodes: IndexMap<NodeId, Node, FlowHasher>,
72 #[cfg_attr(feature = "serde", serde(default))]
74 pub metadata: FlowMetadata,
75}
76
77impl Flow {
78 pub fn is_empty(&self) -> bool {
80 self.nodes.is_empty()
81 }
82
83 pub fn ingress(&self) -> Option<(&NodeId, &Node)> {
85 self.nodes.iter().next()
86 }
87}
88
89#[derive(Clone, Debug, PartialEq)]
91#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
92#[cfg_attr(feature = "schemars", derive(JsonSchema))]
93pub struct Node {
94 pub id: NodeId,
96 pub component: ComponentRef,
98 #[cfg_attr(feature = "serde", serde(alias = "in_map"))]
100 pub input: InputMapping,
101 #[cfg_attr(feature = "serde", serde(alias = "out_map"))]
103 pub output: OutputMapping,
104 #[cfg_attr(
106 feature = "serde",
107 serde(
108 default,
109 skip_serializing_if = "Option::is_none",
110 rename = "err_map",
111 alias = "error_output"
112 )
113 )]
114 pub err_map: Option<OutputMapping>,
115 pub routing: Routing,
117 #[cfg_attr(feature = "serde", serde(default))]
119 pub telemetry: TelemetryHints,
120}
121
122impl Node {
123 pub fn in_map(&self) -> &InputMapping {
125 &self.input
126 }
127
128 pub fn out_map(&self) -> &OutputMapping {
130 &self.output
131 }
132
133 pub fn err_map(&self) -> Option<&OutputMapping> {
135 self.err_map.as_ref()
136 }
137}
138
139#[derive(Clone, Debug, PartialEq)]
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
142#[cfg_attr(feature = "schemars", derive(JsonSchema))]
143pub struct ComponentRef {
144 pub id: ComponentId,
146 #[cfg_attr(
148 feature = "serde",
149 serde(default, skip_serializing_if = "Option::is_none")
150 )]
151 pub pack_alias: Option<String>,
152 #[cfg_attr(
154 feature = "serde",
155 serde(default, skip_serializing_if = "Option::is_none")
156 )]
157 pub operation: Option<String>,
158}
159
160#[derive(Clone, Debug, PartialEq)]
162#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
163#[cfg_attr(feature = "schemars", derive(JsonSchema))]
164pub struct InputMapping {
165 #[cfg_attr(feature = "serde", serde(default))]
167 pub mapping: Value,
168}
169
170#[derive(Clone, Debug, PartialEq)]
172#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
173#[cfg_attr(feature = "schemars", derive(JsonSchema))]
174pub struct OutputMapping {
175 #[cfg_attr(feature = "serde", serde(default))]
177 pub mapping: Value,
178}
179
180#[derive(Clone, Debug, PartialEq)]
182#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
183#[cfg_attr(feature = "schemars", derive(JsonSchema))]
184pub struct FlowMetadata {
185 #[cfg_attr(
187 feature = "serde",
188 serde(default, skip_serializing_if = "Option::is_none")
189 )]
190 pub title: Option<String>,
191 #[cfg_attr(
193 feature = "serde",
194 serde(default, skip_serializing_if = "Option::is_none")
195 )]
196 pub description: Option<String>,
197 #[cfg_attr(feature = "serde", serde(default))]
199 pub tags: BTreeSet<String>,
200 #[cfg_attr(feature = "serde", serde(default))]
202 pub extra: Value,
203}
204
205impl Default for FlowMetadata {
206 fn default() -> Self {
207 Self {
208 title: None,
209 description: None,
210 tags: BTreeSet::new(),
211 extra: Value::Null,
212 }
213 }
214}
215
216#[derive(Clone, Debug, PartialEq)]
218#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
219#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
220#[cfg_attr(feature = "schemars", derive(JsonSchema))]
221pub enum Routing {
222 Next {
224 node_id: NodeId,
226 },
227 Branch {
229 #[cfg_attr(feature = "serde", serde(default))]
231 #[cfg_attr(
232 feature = "schemars",
233 schemars(with = "alloc::collections::BTreeMap<String, NodeId>")
234 )]
235 on_status: BTreeMap<String, NodeId>,
236 #[cfg_attr(
238 feature = "serde",
239 serde(default, skip_serializing_if = "Option::is_none")
240 )]
241 default: Option<NodeId>,
242 },
243 End,
245 Reply,
247 Custom(Value),
249}
250
251#[derive(Clone, Debug, Default, PartialEq, Eq)]
253#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
254#[cfg_attr(feature = "schemars", derive(JsonSchema))]
255pub struct TelemetryHints {
256 #[cfg_attr(
258 feature = "serde",
259 serde(default, skip_serializing_if = "Option::is_none")
260 )]
261 pub span_name: Option<String>,
262 #[cfg_attr(feature = "serde", serde(default))]
264 pub attributes: BTreeMap<String, String>,
265 #[cfg_attr(
267 feature = "serde",
268 serde(default, skip_serializing_if = "Option::is_none")
269 )]
270 pub sampling: Option<String>,
271}