//! Stable status codes for the official-resource to localized-release flow. //! //! The codes describe observable lifecycle state. They are deliberately //! separate from `BAT-ERR-*`: an error code explains why an operation failed, //! while a flow status code explains what a caller can do next. use serde::{Deserialize, Serialize}; /// Stable status code shared by Rust reports and read-only RPC data. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ReleaseFlowStatusCode { /// No published official release is currently available. #[serde(rename = "official.unavailable")] OfficialUnavailable, /// The producer is discovering remote and local official-resource state. #[serde(rename = "official.checking")] OfficialChecking, /// The producer determined that a new or repaired official release is needed. #[serde(rename = "official.update_available")] OfficialUpdateAvailable, /// Official resources are being downloaded or reused into staging. #[serde(rename = "official.downloading")] OfficialDownloading, /// Downloaded official resources are being verified. #[serde(rename = "official.validating")] OfficialValidating, /// A verified official release is being staged or atomically published. #[serde(rename = "official.publishing")] OfficialPublishing, /// A verified official release has been published. #[serde(rename = "official.published")] OfficialPublished, /// The published official release already matches the observed remote state. #[serde(rename = "official.up_to_date")] OfficialUpToDate, /// Launcher/server-info has advanced before required CDN resources are readable. #[serde(rename = "official.waiting_for_resources")] OfficialWaitingForResources, /// Official-resource production failed before a publishable state was reached. #[serde(rename = "official.failed")] OfficialFailed, /// Parsing is blocked because there is no published official release. #[serde(rename = "parse.blocked_official")] ParseBlockedOfficial, /// A published official release exists but parse cache is not present yet. #[serde(rename = "parse.pending")] ParsePending, /// Parse cache or TextUnit index generation is running. #[serde(rename = "parse.running")] ParseRunning, /// Parse cache and TextUnit indexes are present without recorded parse failures. #[serde(rename = "parse.completed")] ParseCompleted, /// Parse cache exists but contains parser or extraction failures. #[serde(rename = "parse.completed_with_errors")] ParseCompletedWithErrors, /// Translation worker integration is not available for the observed release. #[serde(rename = "translation.unavailable")] TranslationUnavailable, /// Translation handoff files are being prepared from the official change set. #[serde(rename = "translation.handoff_preparing")] TranslationHandoffPreparing, /// Translation tasks have been queued to local offline handoff files. #[serde(rename = "translation.queued_offline")] TranslationQueuedOffline, /// Human proofreading is in progress for the observed localized release. #[serde(rename = "translation.manual_proofreading")] TranslationManualProofreading, /// Localized publication is blocked because there is no official release. #[serde(rename = "localized.blocked_official")] LocalizedBlockedOfficial, /// The current official release has no matching localized publication yet. #[serde(rename = "localized.pending")] LocalizedPending, /// A localized state exists but it does not match the current official release. #[serde(rename = "localized.stale")] LocalizedStale, /// A localized release is published and matches the current official release. #[serde(rename = "localized.published")] LocalizedPublished, /// Distribution cannot serve a usable release for the observed channel. #[serde(rename = "distribution.blocked")] DistributionBlocked, /// Distribution can serve the published release. #[serde(rename = "distribution.ready")] DistributionReady, } impl ReleaseFlowStatusCode { /// Returns the stable wire label. pub const fn as_str(self) -> &'static str { match self { Self::OfficialUnavailable => "official.unavailable", Self::OfficialChecking => "official.checking", Self::OfficialUpdateAvailable => "official.update_available", Self::OfficialDownloading => "official.downloading", Self::OfficialValidating => "official.validating", Self::OfficialPublishing => "official.publishing", Self::OfficialPublished => "official.published", Self::OfficialUpToDate => "official.up_to_date", Self::OfficialWaitingForResources => "official.waiting_for_resources", Self::OfficialFailed => "official.failed", Self::ParseBlockedOfficial => "parse.blocked_official", Self::ParsePending => "parse.pending", Self::ParseRunning => "parse.running", Self::ParseCompleted => "parse.completed", Self::ParseCompletedWithErrors => "parse.completed_with_errors", Self::TranslationUnavailable => "translation.unavailable", Self::TranslationHandoffPreparing => "translation.handoff_preparing", Self::TranslationQueuedOffline => "translation.queued_offline", Self::TranslationManualProofreading => "translation.manual_proofreading", Self::LocalizedBlockedOfficial => "localized.blocked_official", Self::LocalizedPending => "localized.pending", Self::LocalizedStale => "localized.stale", Self::LocalizedPublished => "localized.published", Self::DistributionBlocked => "distribution.blocked", Self::DistributionReady => "distribution.ready", } } /// Returns the short status value used alongside `status_code` in RPC /// payloads. This keeps existing human-facing labels independent from the /// namespaced wire code. pub const fn status(self) -> &'static str { match self { Self::OfficialUnavailable => "unavailable", Self::OfficialChecking => "checking", Self::OfficialUpdateAvailable => "update_available", Self::OfficialDownloading => "downloading", Self::OfficialValidating => "validating", Self::OfficialPublishing => "publishing", Self::OfficialPublished => "published", Self::OfficialUpToDate => "up_to_date", Self::OfficialWaitingForResources => "waiting_for_resources", Self::OfficialFailed => "failed", Self::ParseBlockedOfficial => "blocked_official", Self::ParsePending => "pending", Self::ParseRunning => "running", Self::ParseCompleted => "completed", Self::ParseCompletedWithErrors => "completed_with_errors", Self::TranslationUnavailable => "unavailable", Self::TranslationHandoffPreparing => "handoff_preparing", Self::TranslationQueuedOffline => "queued_offline", Self::TranslationManualProofreading => "manual_proofreading", Self::LocalizedBlockedOfficial => "blocked_official", Self::LocalizedPending => "pending", Self::LocalizedStale => "stale", Self::LocalizedPublished => "published", Self::DistributionBlocked => "blocked", Self::DistributionReady => "ready", } } /// Parses a status code read from a persisted daemon/RPC snapshot. fn parse_wire_code(code: &str) -> Option { Some(match code { "official.unavailable" => Self::OfficialUnavailable, "official.checking" => Self::OfficialChecking, "official.update_available" => Self::OfficialUpdateAvailable, "official.downloading" => Self::OfficialDownloading, "official.validating" => Self::OfficialValidating, "official.publishing" => Self::OfficialPublishing, "official.published" => Self::OfficialPublished, "official.up_to_date" => Self::OfficialUpToDate, "official.waiting_for_resources" => Self::OfficialWaitingForResources, "official.failed" => Self::OfficialFailed, "parse.blocked_official" => Self::ParseBlockedOfficial, "parse.pending" => Self::ParsePending, "parse.running" => Self::ParseRunning, "parse.completed" => Self::ParseCompleted, "parse.completed_with_errors" => Self::ParseCompletedWithErrors, "translation.unavailable" => Self::TranslationUnavailable, "translation.handoff_preparing" => Self::TranslationHandoffPreparing, "translation.queued_offline" => Self::TranslationQueuedOffline, "translation.manual_proofreading" => Self::TranslationManualProofreading, "localized.blocked_official" => Self::LocalizedBlockedOfficial, "localized.pending" => Self::LocalizedPending, "localized.stale" => Self::LocalizedStale, "localized.published" => Self::LocalizedPublished, "distribution.blocked" => Self::DistributionBlocked, "distribution.ready" => Self::DistributionReady, _ => return None, }) } /// Returns the broad flow phase represented by this code. pub const fn phase(self) -> &'static str { match self { Self::OfficialUnavailable | Self::OfficialChecking | Self::OfficialUpdateAvailable | Self::OfficialDownloading | Self::OfficialValidating | Self::OfficialPublishing | Self::OfficialPublished | Self::OfficialUpToDate | Self::OfficialWaitingForResources | Self::OfficialFailed => "official_sync", Self::ParseBlockedOfficial | Self::ParsePending | Self::ParseRunning | Self::ParseCompleted | Self::ParseCompletedWithErrors => "parse", Self::TranslationUnavailable | Self::TranslationHandoffPreparing | Self::TranslationQueuedOffline | Self::TranslationManualProofreading => "translation", Self::LocalizedBlockedOfficial | Self::LocalizedPending | Self::LocalizedStale | Self::LocalizedPublished => "localized_publish", Self::DistributionBlocked | Self::DistributionReady => "distribution", } } /// Returns whether the state is stable for the current observation. pub const fn terminal(self) -> bool { !matches!( self, Self::OfficialChecking | Self::OfficialDownloading | Self::OfficialValidating | Self::OfficialPublishing | Self::ParseRunning | Self::TranslationHandoffPreparing ) } /// Returns whether the producer may retry the operation automatically. pub const fn retryable(self) -> bool { matches!( self, Self::OfficialWaitingForResources | Self::OfficialFailed | Self::ParsePending | Self::ParseCompletedWithErrors ) } /// Maps an existing official update result to the stable flow code. pub fn from_update_status(status: &str) -> Self { match status { "would_download" => Self::OfficialUpdateAvailable, "waiting_for_official_resources" => Self::OfficialWaitingForResources, "downloaded" => Self::OfficialPublished, "up_to_date" => Self::OfficialUpToDate, _ => Self::OfficialFailed, } } /// Maps an existing progress stage to the stable flow code. pub fn from_progress_stage(stage: &str) -> Self { match stage { "download" => Self::OfficialDownloading, "audit" | "snapshot" => Self::OfficialValidating, "publish" | "launcher-bootstrap" => Self::OfficialPublishing, "parse" => Self::ParseRunning, "changes" => Self::TranslationHandoffPreparing, "finish" => Self::OfficialPublished, _ => Self::OfficialChecking, } } } impl std::str::FromStr for ReleaseFlowStatusCode { type Err = (); fn from_str(code: &str) -> Result { Self::parse_wire_code(code).ok_or(()) } } #[cfg(test)] mod tests { use super::ReleaseFlowStatusCode; #[test] fn status_codes_are_stable_and_namespaced() { let codes = [ ReleaseFlowStatusCode::OfficialUnavailable, ReleaseFlowStatusCode::OfficialChecking, ReleaseFlowStatusCode::OfficialUpdateAvailable, ReleaseFlowStatusCode::OfficialDownloading, ReleaseFlowStatusCode::OfficialValidating, ReleaseFlowStatusCode::OfficialPublishing, ReleaseFlowStatusCode::OfficialPublished, ReleaseFlowStatusCode::OfficialUpToDate, ReleaseFlowStatusCode::OfficialWaitingForResources, ReleaseFlowStatusCode::OfficialFailed, ReleaseFlowStatusCode::ParseBlockedOfficial, ReleaseFlowStatusCode::ParsePending, ReleaseFlowStatusCode::ParseRunning, ReleaseFlowStatusCode::ParseCompleted, ReleaseFlowStatusCode::ParseCompletedWithErrors, ReleaseFlowStatusCode::TranslationUnavailable, ReleaseFlowStatusCode::TranslationHandoffPreparing, ReleaseFlowStatusCode::TranslationQueuedOffline, ReleaseFlowStatusCode::TranslationManualProofreading, ReleaseFlowStatusCode::LocalizedBlockedOfficial, ReleaseFlowStatusCode::LocalizedPending, ReleaseFlowStatusCode::LocalizedStale, ReleaseFlowStatusCode::LocalizedPublished, ReleaseFlowStatusCode::DistributionBlocked, ReleaseFlowStatusCode::DistributionReady, ]; let labels = codes.iter().map(|code| code.as_str()).collect::>(); let unique = labels.iter().collect::>(); assert_eq!(labels.len(), unique.len()); assert!(labels.iter().all(|label| label.contains('.'))); } #[test] fn status_codes_round_trip_through_from_str() { let code = ReleaseFlowStatusCode::LocalizedPublished; assert_eq!(code.as_str().parse::(), Ok(code)); assert!("unknown.status".parse::().is_err()); } }