use crate::actions::ActionType;
use crate::measures::Measure;
use chrono::{DateTime, Utc};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum FlowType {
Intent,
Commitment,
Event,
Claim,
RecipeFlow,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Flow {
pub id: String,
pub flow_type: FlowType,
pub action: ActionType,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub provider: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub receiver: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub resource_quantity: Option<Measure>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub effort_quantity: Option<Measure>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub resource_conforms_to: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub resource_inventoried_as: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub input_of: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub output_of: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub has_point_in_time: Option<DateTime<Utc>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub note: Option<String>,
}
impl Flow {
pub fn new(id: impl Into<String>, flow_type: FlowType, action: ActionType) -> Self {
Self {
id: id.into(),
flow_type,
action,
provider: None,
receiver: None,
resource_quantity: None,
effort_quantity: None,
resource_conforms_to: None,
resource_inventoried_as: None,
input_of: None,
output_of: None,
has_point_in_time: None,
note: None,
}
}
pub fn is_input(&self) -> bool {
self.input_of.is_some()
}
pub fn is_output(&self) -> bool {
self.output_of.is_some()
}
pub fn is_observation(&self) -> bool {
matches!(self.flow_type, FlowType::Event)
}
pub fn is_planned(&self) -> bool {
matches!(
self.flow_type,
FlowType::Intent | FlowType::Commitment | FlowType::RecipeFlow
)
}
pub fn with_provider(mut self, agent_id: impl Into<String>) -> Self {
self.provider = Some(agent_id.into());
self
}
pub fn with_receiver(mut self, agent_id: impl Into<String>) -> Self {
self.receiver = Some(agent_id.into());
self
}
pub fn with_resource_quantity(mut self, quantity: Measure) -> Self {
self.resource_quantity = Some(quantity);
self
}
pub fn with_resource_conforms_to(mut self, spec_id: impl Into<String>) -> Self {
self.resource_conforms_to = Some(spec_id.into());
self
}
pub fn with_input_of(mut self, process_id: impl Into<String>) -> Self {
self.input_of = Some(process_id.into());
self
}
pub fn with_output_of(mut self, process_id: impl Into<String>) -> Self {
self.output_of = Some(process_id.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowDirection {
Forward,
Backward,
}
#[derive(Debug, Clone)]
pub struct FlowNode {
pub flow: Flow,
pub depth: usize,
pub parent: Option<String>,
}
impl FlowNode {
pub fn new(flow: Flow, depth: usize, parent: Option<String>) -> Self {
Self { flow, depth, parent }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::measures::Unit;
#[test]
fn test_flow_creation() {
let flow = Flow::new("flow-001", FlowType::Event, ActionType::Produce)
.with_provider("agent-001")
.with_receiver("agent-001")
.with_resource_quantity(Measure::new(100, Unit::Kilogram));
assert_eq!(flow.id, "flow-001");
assert!(flow.is_observation());
assert_eq!(flow.provider, Some("agent-001".to_string()));
}
#[test]
fn test_flow_classification() {
let intent = Flow::new("flow-001", FlowType::Intent, ActionType::Transfer);
assert!(intent.is_planned());
assert!(!intent.is_observation());
let event = Flow::new("flow-002", FlowType::Event, ActionType::Transfer);
assert!(event.is_observation());
assert!(!event.is_planned());
}
#[cfg(feature = "serde")]
#[test]
fn test_flow_serialization() {
let flow = Flow::new("flow-001", FlowType::Commitment, ActionType::Produce)
.with_provider("agent-001")
.with_resource_quantity(Measure::new(50, Unit::Each));
let json = serde_json::to_string(&flow).unwrap();
let parsed: Flow = serde_json::from_str(&json).unwrap();
assert_eq!(flow.id, parsed.id);
assert_eq!(flow.flow_type, parsed.flow_type);
}
}