use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Stage {
Ingest,
Extract,
Validate,
Project,
Index,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
Ok,
Recovered,
Failed(String),
Skipped,
}
pub fn step(stage: Stage, outcome: &Outcome) -> Option<Stage> {
match outcome {
Outcome::Failed(_) | Outcome::Skipped => None,
Outcome::Ok | Outcome::Recovered => match stage {
Stage::Ingest => Some(Stage::Extract),
Stage::Extract => Some(Stage::Validate),
Stage::Validate => Some(Stage::Project),
Stage::Project => Some(Stage::Index),
Stage::Index => None,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn happy_path_advances_through_all_stages() {
let mut stage = Stage::Ingest;
let path = [
Stage::Extract,
Stage::Validate,
Stage::Project,
Stage::Index,
];
for expected in path {
assert_eq!(step(stage, &Outcome::Ok), Some(expected));
stage = expected;
}
assert_eq!(step(Stage::Index, &Outcome::Ok), None);
}
#[test]
fn recovered_advances_like_ok() {
assert_eq!(
step(Stage::Extract, &Outcome::Recovered),
Some(Stage::Validate)
);
}
#[test]
fn failure_stops_pipeline_from_any_stage() {
for stage in [
Stage::Ingest,
Stage::Extract,
Stage::Validate,
Stage::Project,
Stage::Index,
] {
assert_eq!(
step(stage, &Outcome::Failed("timeout".into())),
None,
"pipeline should stop on failure from {stage:?}"
);
}
}
#[test]
fn skipped_stops_pipeline_from_any_stage() {
for stage in [
Stage::Ingest,
Stage::Extract,
Stage::Validate,
Stage::Project,
Stage::Index,
] {
assert_eq!(
step(stage, &Outcome::Skipped),
None,
"pipeline should stop on skip from {stage:?}"
);
}
}
#[test]
fn stage_order_is_linear_no_shortcuts() {
assert_eq!(step(Stage::Ingest, &Outcome::Ok), Some(Stage::Extract));
assert_eq!(step(Stage::Extract, &Outcome::Ok), Some(Stage::Validate));
assert_eq!(step(Stage::Validate, &Outcome::Ok), Some(Stage::Project));
assert_eq!(step(Stage::Project, &Outcome::Ok), Some(Stage::Index));
}
}