use std::sync::Arc;
use serde::Serialize;
use crate::workflow::Flow;
#[derive(Clone)]
pub enum StepId {
WithFlow {
flow: Arc<Flow>,
index: usize,
},
Standalone {
name: String,
index: usize,
},
}
impl StepId {
pub fn for_step(flow: Arc<Flow>, index: usize) -> Self {
Self::WithFlow { flow, index }
}
pub fn new(name: String, index: usize) -> Self {
Self::Standalone { name, index }
}
pub fn index(&self) -> usize {
match self {
Self::WithFlow { index, .. } => *index,
Self::Standalone { index, .. } => *index,
}
}
pub fn name(&self) -> &str {
match self {
Self::WithFlow { flow, index } => &flow.steps[*index].id,
Self::Standalone { name, .. } => name,
}
}
pub fn to_standalone(&self) -> Self {
match self {
Self::WithFlow { flow, index } => Self::Standalone {
name: flow.steps[*index].id.clone(),
index: *index,
},
Self::Standalone { name, index } => Self::Standalone {
name: name.clone(),
index: *index,
},
}
}
pub fn into_standalone(self) -> Self {
match self {
Self::WithFlow { flow, index } => Self::Standalone {
name: flow.steps[index].id.clone(),
index,
},
standalone @ Self::Standalone { .. } => standalone,
}
}
pub fn has_flow(&self) -> bool {
matches!(self, Self::WithFlow { .. })
}
pub fn flow(&self) -> Option<&Arc<Flow>> {
match self {
Self::WithFlow { flow, .. } => Some(flow),
Self::Standalone { .. } => None,
}
}
}
impl PartialEq for StepId {
fn eq(&self, other: &Self) -> bool {
self.index() == other.index() && self.name() == other.name()
}
}
impl Eq for StepId {}
impl std::hash::Hash for StepId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index().hash(state);
self.name().hash(state);
}
}
impl PartialOrd for StepId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StepId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.index().cmp(&other.index())
}
}
impl std::fmt::Display for StepId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl std::fmt::Debug for StepId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StepId")
.field("index", &self.index())
.field("name", &self.name())
.finish()
}
}
impl Serialize for StepId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ValueExpr;
use crate::workflow::builders::{FlowBuilder, StepBuilder};
use serde_json::json;
fn create_test_flow() -> Arc<Flow> {
let step_one = StepBuilder::new("step_one")
.component("/builtin/eval")
.input_literal(json!({"expression": "1 + 1"}))
.build();
let step_two = StepBuilder::new("step_two")
.component("/builtin/eval")
.input_literal(json!({"expression": "2 + 2"}))
.build();
let flow = FlowBuilder::new()
.step(step_one)
.step(step_two)
.output(ValueExpr::step_output("step_two"))
.build();
Arc::new(flow)
}
#[test]
fn test_for_step_creation() {
let flow = create_test_flow();
let step_id = StepId::for_step(flow.clone(), 0);
assert_eq!(step_id.index(), 0);
assert_eq!(step_id.name(), "step_one");
assert!(step_id.has_flow());
}
#[test]
fn test_standalone_creation() {
let step_id = StepId::new("my_step".to_string(), 5);
assert_eq!(step_id.index(), 5);
assert_eq!(step_id.name(), "my_step");
assert!(!step_id.has_flow());
}
#[test]
fn test_to_standalone() {
let flow = create_test_flow();
let step_id = StepId::for_step(flow, 1);
let standalone = step_id.to_standalone();
assert_eq!(standalone.index(), 1);
assert_eq!(standalone.name(), "step_two");
assert!(!standalone.has_flow());
}
#[test]
fn test_into_standalone() {
let flow = create_test_flow();
let step_id = StepId::for_step(flow, 0);
let standalone = step_id.into_standalone();
assert_eq!(standalone.index(), 0);
assert_eq!(standalone.name(), "step_one");
assert!(!standalone.has_flow());
}
#[test]
fn test_equality_across_variants() {
let flow = create_test_flow();
let with_flow = StepId::for_step(flow, 0);
let standalone = StepId::new("step_one".to_string(), 0);
assert_eq!(with_flow, standalone);
}
#[test]
fn test_inequality_different_index() {
let step1 = StepId::new("step".to_string(), 0);
let step2 = StepId::new("step".to_string(), 1);
assert_ne!(step1, step2);
}
#[test]
fn test_inequality_different_name() {
let step1 = StepId::new("step_a".to_string(), 0);
let step2 = StepId::new("step_b".to_string(), 0);
assert_ne!(step1, step2);
}
#[test]
fn test_ordering() {
let step0 = StepId::new("z".to_string(), 0);
let step1 = StepId::new("a".to_string(), 1);
let step2 = StepId::new("m".to_string(), 2);
assert!(step0 < step1);
assert!(step1 < step2);
}
#[test]
fn test_hash_consistency() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let flow = create_test_flow();
let with_flow = StepId::for_step(flow, 0);
let standalone = StepId::new("step_one".to_string(), 0);
let mut hasher1 = DefaultHasher::new();
with_flow.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
standalone.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_eq!(hash1, hash2);
}
#[test]
fn test_serialize_as_string() {
let step_id = StepId::new("my_step".to_string(), 5);
let json = serde_json::to_string(&step_id).unwrap();
assert_eq!(json, "\"my_step\"");
}
#[test]
fn test_serialize_with_flow() {
let flow = create_test_flow();
let step_id = StepId::for_step(flow, 1);
let json = serde_json::to_string(&step_id).unwrap();
assert_eq!(json, "\"step_two\"");
}
#[test]
fn test_display() {
let step_id = StepId::new("my_step".to_string(), 3);
assert_eq!(format!("{}", step_id), "my_step");
}
#[test]
fn test_debug() {
let step_id = StepId::new("my_step".to_string(), 3);
let debug_str = format!("{:?}", step_id);
assert!(debug_str.contains("index: 3"));
assert!(debug_str.contains("name: \"my_step\""));
}
}