mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 12:14:56 +08:00
319 lines
14 KiB
Rust
319 lines
14 KiB
Rust
//! 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<Self> {
|
|
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, Self::Err> {
|
|
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::<Vec<_>>();
|
|
let unique = labels.iter().collect::<std::collections::BTreeSet<_>>();
|
|
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::<ReleaseFlowStatusCode>(), Ok(code));
|
|
assert!("unknown.status".parse::<ReleaseFlowStatusCode>().is_err());
|
|
}
|
|
}
|