1use alloc::string::String;
4use core::hash::BuildHasherDefault;
5
6use fnv::FnvHasher;
7use indexmap::IndexMap;
8use serde_json::Value;
9
10use crate::{ComponentId, FlowId, NodeId, component::ComponentManifest};
11
12type FlowHasher = BuildHasherDefault<FnvHasher>;
14
15pub type FlowNodes = IndexMap<NodeId, Node, FlowHasher>;
17
18#[cfg(feature = "schemars")]
19use schemars::JsonSchema;
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
27#[cfg_attr(feature = "schemars", derive(JsonSchema))]
28pub enum FlowKind {
29 Messaging,
31 Events,
33}
34
35#[derive(Clone, Debug, PartialEq)]
37#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38#[cfg_attr(feature = "schemars", derive(JsonSchema))]
39pub struct Flow {
40 pub kind: FlowKind,
42 pub id: FlowId,
44 #[cfg_attr(
46 feature = "serde",
47 serde(default, skip_serializing_if = "Option::is_none")
48 )]
49 pub description: Option<String>,
50 #[cfg_attr(feature = "serde", serde(default))]
52 #[cfg_attr(
53 feature = "schemars",
54 schemars(with = "alloc::collections::BTreeMap<NodeId, Node>")
55 )]
56 pub nodes: FlowNodes,
57}
58
59impl Flow {
60 pub fn is_empty(&self) -> bool {
62 self.nodes.is_empty()
63 }
64
65 pub fn ingress(&self) -> Option<(&NodeId, &Node)> {
67 self.nodes.iter().next()
68 }
69
70 pub fn validate_structure(&self) -> Result<(), FlowValidationError> {
72 if self.is_empty() {
73 return Err(FlowValidationError::EmptyFlow);
74 }
75 Ok(())
76 }
77
78 pub fn validate_components<'a, F>(&self, mut resolver: F) -> Result<(), FlowValidationError>
80 where
81 F: FnMut(&ComponentId) -> Option<&'a ComponentManifest>,
82 {
83 self.validate_structure()?;
84 for (node_id, node) in &self.nodes {
85 if let Some(component_id) = &node.component {
86 let manifest = resolver(component_id).ok_or_else(|| {
87 FlowValidationError::MissingComponent {
88 node_id: node_id.clone(),
89 component: component_id.clone(),
90 }
91 })?;
92
93 if !manifest.supports_kind(self.kind) {
94 return Err(FlowValidationError::UnsupportedComponent {
95 node_id: node_id.clone(),
96 component: component_id.clone(),
97 flow_kind: self.kind,
98 });
99 }
100 }
101 }
102 Ok(())
103 }
104}
105
106#[derive(Clone, Debug, PartialEq)]
108#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
109#[cfg_attr(feature = "schemars", derive(JsonSchema))]
110pub struct Node {
111 pub kind: String,
113 #[cfg_attr(
115 feature = "serde",
116 serde(default, skip_serializing_if = "Option::is_none")
117 )]
118 pub profile: Option<String>,
119 #[cfg_attr(
121 feature = "serde",
122 serde(default, skip_serializing_if = "Option::is_none")
123 )]
124 pub component: Option<ComponentId>,
125 #[cfg_attr(feature = "serde", serde(default))]
127 pub config: Value,
128 #[cfg_attr(feature = "serde", serde(default))]
130 pub routing: Value,
131}
132
133#[derive(Clone, Debug, PartialEq, Eq)]
135pub enum FlowValidationError {
136 EmptyFlow,
138 MissingComponent {
140 node_id: NodeId,
142 component: ComponentId,
144 },
145 UnsupportedComponent {
147 node_id: NodeId,
149 component: ComponentId,
151 flow_kind: FlowKind,
153 },
154}
155
156impl core::fmt::Display for FlowValidationError {
157 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158 match self {
159 FlowValidationError::EmptyFlow => f.write_str("flows must declare at least one node"),
160 FlowValidationError::MissingComponent { node_id, component } => write!(
161 f,
162 "node `{}` references missing component `{}`",
163 node_id.as_str(),
164 component.as_str()
165 ),
166 FlowValidationError::UnsupportedComponent {
167 node_id,
168 component,
169 flow_kind,
170 } => write!(
171 f,
172 "component `{}` used by node `{}` does not support `{:?}` flows",
173 component.as_str(),
174 node_id.as_str(),
175 flow_kind
176 ),
177 }
178 }
179}
180
181#[cfg(feature = "std")]
182impl std::error::Error for FlowValidationError {}