From ebf8f499af70a97a39937bfa8447ef5685e4b6b4 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 21 Apr 2026 09:10:47 -0400 Subject: [PATCH] refactor(types): add StatusReason parsing helpers Add Display and FromStr support for StatusReason so the domain type can replace the generated API enum without losing string conversion behavior. --- lib/crates/fabro-types/src/status.rs | 60 +++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/lib/crates/fabro-types/src/status.rs b/lib/crates/fabro-types/src/status.rs index 00f78b163..60f02f5ae 100644 --- a/lib/crates/fabro-types/src/status.rs +++ b/lib/crates/fabro-types/src/status.rs @@ -172,6 +172,57 @@ pub enum StatusReason { SandboxInitializing, } +impl fmt::Display for StatusReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + Self::Completed => "completed", + Self::PartialSuccess => "partial_success", + Self::WorkflowError => "workflow_error", + Self::Cancelled => "cancelled", + Self::Terminated => "terminated", + Self::TransientInfra => "transient_infra", + Self::BudgetExhausted => "budget_exhausted", + Self::LaunchFailed => "launch_failed", + Self::BootstrapFailed => "bootstrap_failed", + Self::SandboxInitFailed => "sandbox_init_failed", + Self::SandboxInitializing => "sandbox_initializing", + }; + f.write_str(s) + } +} + +impl FromStr for StatusReason { + type Err = ParseStatusReasonError; + + fn from_str(s: &str) -> Result { + match s { + "completed" => Ok(Self::Completed), + "partial_success" => Ok(Self::PartialSuccess), + "workflow_error" => Ok(Self::WorkflowError), + "cancelled" => Ok(Self::Cancelled), + "terminated" => Ok(Self::Terminated), + "transient_infra" => Ok(Self::TransientInfra), + "budget_exhausted" => Ok(Self::BudgetExhausted), + "launch_failed" => Ok(Self::LaunchFailed), + "bootstrap_failed" => Ok(Self::BootstrapFailed), + "sandbox_init_failed" => Ok(Self::SandboxInitFailed), + "sandbox_initializing" => Ok(Self::SandboxInitializing), + _ => Err(ParseStatusReasonError(s.to_string())), + } + } +} + +#[derive(Debug, Clone)] +pub struct ParseStatusReasonError(String); + +impl fmt::Display for ParseStatusReasonError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid status reason: {:?}", self.0) + } +} + +impl std::error::Error for ParseStatusReasonError {} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum BlockedReason { @@ -211,7 +262,7 @@ impl RunStatusRecord { mod tests { use std::str::FromStr; - use super::{InvalidTransition, RunStatus}; + use super::{InvalidTransition, RunStatus, StatusReason}; #[test] fn queued_and_blocked_parse_and_format() { @@ -249,6 +300,13 @@ mod tests { assert_eq!(parsed.to_string(), "archived"); } + #[test] + fn status_reason_parses_and_round_trips() { + let parsed = StatusReason::from_str("cancelled").expect("cancelled should parse"); + assert_eq!(parsed, StatusReason::Cancelled); + assert_eq!(parsed.to_string(), "cancelled"); + } + #[test] fn terminal_statuses_can_transition_to_archived() { assert!(RunStatus::Succeeded.can_transition_to(RunStatus::Archived));