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 pub input: InputMapping,
100 pub output: OutputMapping,
102 pub routing: Routing,
104 #[cfg_attr(feature = "serde", serde(default))]
106 pub telemetry: TelemetryHints,
107}
108
109#[derive(Clone, Debug, PartialEq)]
111#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
112#[cfg_attr(feature = "schemars", derive(JsonSchema))]
113pub struct ComponentRef {
114 pub id: ComponentId,
116 #[cfg_attr(
118 feature = "serde",
119 serde(default, skip_serializing_if = "Option::is_none")
120 )]
121 pub pack_alias: Option<String>,
122 #[cfg_attr(
124 feature = "serde",
125 serde(default, skip_serializing_if = "Option::is_none")
126 )]
127 pub operation: Option<String>,
128}
129
130#[derive(Clone, Debug, PartialEq)]
132#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
133#[cfg_attr(feature = "schemars", derive(JsonSchema))]
134pub struct InputMapping {
135 #[cfg_attr(feature = "serde", serde(default))]
137 pub mapping: Value,
138}
139
140#[derive(Clone, Debug, PartialEq)]
142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
143#[cfg_attr(feature = "schemars", derive(JsonSchema))]
144pub struct OutputMapping {
145 #[cfg_attr(feature = "serde", serde(default))]
147 pub mapping: Value,
148}
149
150#[derive(Clone, Debug, PartialEq)]
152#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
153#[cfg_attr(feature = "schemars", derive(JsonSchema))]
154pub struct FlowMetadata {
155 #[cfg_attr(
157 feature = "serde",
158 serde(default, skip_serializing_if = "Option::is_none")
159 )]
160 pub title: Option<String>,
161 #[cfg_attr(
163 feature = "serde",
164 serde(default, skip_serializing_if = "Option::is_none")
165 )]
166 pub description: Option<String>,
167 #[cfg_attr(feature = "serde", serde(default))]
169 pub tags: BTreeSet<String>,
170 #[cfg_attr(feature = "serde", serde(default))]
172 pub extra: Value,
173}
174
175impl Default for FlowMetadata {
176 fn default() -> Self {
177 Self {
178 title: None,
179 description: None,
180 tags: BTreeSet::new(),
181 extra: Value::Null,
182 }
183 }
184}
185
186#[derive(Clone, Debug, PartialEq)]
188#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
189#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
190#[cfg_attr(feature = "schemars", derive(JsonSchema))]
191pub enum Routing {
192 Next {
194 node_id: NodeId,
196 },
197 Branch {
199 #[cfg_attr(feature = "serde", serde(default))]
201 #[cfg_attr(
202 feature = "schemars",
203 schemars(with = "alloc::collections::BTreeMap<String, NodeId>")
204 )]
205 on_status: BTreeMap<String, NodeId>,
206 #[cfg_attr(
208 feature = "serde",
209 serde(default, skip_serializing_if = "Option::is_none")
210 )]
211 default: Option<NodeId>,
212 },
213 End,
215 Reply,
217 Custom(Value),
219}
220
221#[derive(Clone, Debug, Default, PartialEq, Eq)]
223#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
224#[cfg_attr(feature = "schemars", derive(JsonSchema))]
225pub struct TelemetryHints {
226 #[cfg_attr(
228 feature = "serde",
229 serde(default, skip_serializing_if = "Option::is_none")
230 )]
231 pub span_name: Option<String>,
232 #[cfg_attr(feature = "serde", serde(default))]
234 pub attributes: BTreeMap<String, String>,
235 #[cfg_attr(
237 feature = "serde",
238 serde(default, skip_serializing_if = "Option::is_none")
239 )]
240 pub sampling: Option<String>,
241}