mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 14:14:53 +08:00
fix(sync): 固化 release flow 状态码
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
//! 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,
|
||||
/// 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::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::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.
|
||||
pub fn from_str(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,
|
||||
"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 => "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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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::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('.')));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user