use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::String;
use core::hash::BuildHasherDefault;
use fnv::FnvHasher;
use indexmap::IndexMap;
use serde_json::Value;
use crate::{ComponentId, FlowId, NodeId};
pub type FlowHasher = BuildHasherDefault<FnvHasher>;
#[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,
Event,
ComponentConfig,
Job,
Http,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "schemars",
derive(JsonSchema),
schemars(
title = "Greentic Flow v1",
description = "Canonical flow model with components, routing and telemetry.",
rename = "greentic.flow.v1"
)
)]
pub struct Flow {
pub schema_version: String,
pub id: FlowId,
pub kind: FlowKind,
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(
feature = "schemars",
schemars(with = "alloc::collections::BTreeMap<String, Value>")
)]
pub entrypoints: BTreeMap<String, Value>,
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(
feature = "schemars",
schemars(with = "alloc::collections::BTreeMap<NodeId, Node>")
)]
pub nodes: IndexMap<NodeId, Node, FlowHasher>,
#[cfg_attr(feature = "serde", serde(default))]
pub metadata: FlowMetadata,
}
impl Flow {
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn ingress(&self) -> Option<(&NodeId, &Node)> {
self.nodes.iter().next()
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct Node {
pub id: NodeId,
pub component: ComponentRef,
pub input: InputMapping,
pub output: OutputMapping,
pub routing: Routing,
#[cfg_attr(feature = "serde", serde(default))]
pub telemetry: TelemetryHints,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct ComponentRef {
pub id: ComponentId,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub pack_alias: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub operation: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct InputMapping {
#[cfg_attr(feature = "serde", serde(default))]
pub mapping: Value,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct OutputMapping {
#[cfg_attr(feature = "serde", serde(default))]
pub mapping: Value,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct FlowMetadata {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub title: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub description: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
pub tags: BTreeSet<String>,
#[cfg_attr(feature = "serde", serde(default))]
pub extra: Value,
}
impl Default for FlowMetadata {
fn default() -> Self {
Self {
title: None,
description: None,
tags: BTreeSet::new(),
extra: Value::Null,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub enum Routing {
Next {
node_id: NodeId,
},
Branch {
#[cfg_attr(feature = "serde", serde(default))]
#[cfg_attr(
feature = "schemars",
schemars(with = "alloc::collections::BTreeMap<String, NodeId>")
)]
on_status: BTreeMap<String, NodeId>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
default: Option<NodeId>,
},
End,
Reply,
Custom(Value),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct TelemetryHints {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub span_name: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
pub attributes: BTreeMap<String, String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub sampling: Option<String>,
}