use alloc::string::String;
use core::hash::BuildHasherDefault;
use fnv::FnvHasher;
use indexmap::IndexMap;
use serde_json::Value;
use crate::{ComponentId, FlowId, NodeId, component::ComponentManifest};
type FlowHasher = BuildHasherDefault<FnvHasher>;
pub type FlowNodes = IndexMap<NodeId, Node, FlowHasher>;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub enum FlowKind {
Messaging,
Events,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct Flow {
pub kind: FlowKind,
pub id: FlowId,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub description: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(
feature = "schemars",
schemars(with = "alloc::collections::BTreeMap<NodeId, Node>")
)]
pub nodes: FlowNodes,
}
impl Flow {
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn ingress(&self) -> Option<(&NodeId, &Node)> {
self.nodes.iter().next()
}
pub fn validate_structure(&self) -> Result<(), FlowValidationError> {
if self.is_empty() {
return Err(FlowValidationError::EmptyFlow);
}
Ok(())
}
pub fn validate_components<'a, F>(&self, mut resolver: F) -> Result<(), FlowValidationError>
where
F: FnMut(&ComponentId) -> Option<&'a ComponentManifest>,
{
self.validate_structure()?;
for (node_id, node) in &self.nodes {
if let Some(component_id) = &node.component {
let manifest = resolver(component_id).ok_or_else(|| {
FlowValidationError::MissingComponent {
node_id: node_id.clone(),
component: component_id.clone(),
}
})?;
if !manifest.supports_kind(self.kind) {
return Err(FlowValidationError::UnsupportedComponent {
node_id: node_id.clone(),
component: component_id.clone(),
flow_kind: self.kind,
});
}
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct Node {
pub kind: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub profile: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub component: Option<ComponentId>,
#[cfg_attr(feature = "serde", serde(default))]
pub config: Value,
#[cfg_attr(feature = "serde", serde(default))]
pub routing: Value,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlowValidationError {
EmptyFlow,
MissingComponent {
node_id: NodeId,
component: ComponentId,
},
UnsupportedComponent {
node_id: NodeId,
component: ComponentId,
flow_kind: FlowKind,
},
}
impl core::fmt::Display for FlowValidationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FlowValidationError::EmptyFlow => f.write_str("flows must declare at least one node"),
FlowValidationError::MissingComponent { node_id, component } => write!(
f,
"node `{}` references missing component `{}`",
node_id.as_str(),
component.as_str()
),
FlowValidationError::UnsupportedComponent {
node_id,
component,
flow_kind,
} => write!(
f,
"component `{}` used by node `{}` does not support `{:?}` flows",
component.as_str(),
node_id.as_str(),
flow_kind
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FlowValidationError {}