Unlucky Launcher v0.0.1-alpha
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s

Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
AskaEth committed 2026-10-31 21:54:10 +08:00
commit bc41dfcf7d
517 files changed
+92844

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
//! 下载错误:三态分类(同源重试 / 立刻换源 / 整体失败)+ 失败摘要。
//!
//! core 不引 `thiserror`(P0 阶段少一个依赖),手写 `Display`/`Error` 足够。
use std::fmt;
use serde::{Deserialize, Serialize};
/// 错误分类:决定「同源重试」「立刻换源」还是「整体失败」。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DownloadErrorKind {
/// 请求本身不合法(不重试)。
InvalidRequest,
/// 404/410/403:该源没有这个资源,立刻换源。
NotFound,
/// 5xx / 429:同源退避重试。
ServerError,
/// 超时:同源退避重试,多次后换源。
Timeout,
/// 连接/传输层错误。
Network,
/// 校验不符:同源重试一次后换源(可能是镜像内容不一致)。
Integrity,
/// 服务器不支持 Range(内部用于退化单连接,不向上暴露为失败)。
RangeUnsupported,
/// 本地 IO 错误(不重试)。
Io,
/// 所有候选源都失败。
AllSourcesFailed,
}
impl DownloadErrorKind {
/// 稳定字符串 id。
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::InvalidRequest => "invalid_request",
Self::NotFound => "not_found",
Self::ServerError => "server_error",
Self::Timeout => "timeout",
Self::Network => "network",
Self::Integrity => "integrity",
Self::RangeUnsupported => "range_unsupported",
Self::Io => "io",
Self::AllSourcesFailed => "all_sources_failed",
}
}
/// 是否值得在同源上退避重试。
#[must_use]
pub const fn is_retryable(self) -> bool {
matches!(
self,
Self::ServerError | Self::Timeout | Self::Network | Self::Integrity
)
}
}
/// 下载错误。
#[derive(Debug, Clone)]
pub struct DownloadError {
/// 分类。
pub kind: DownloadErrorKind,
/// 出错的源(整体失败时为 `None`)。
pub source: Option<String>,
/// 人类可读描述。
pub message: String,
/// 已经尝试的次数(含本次)。
pub attempts: usize,
/// 其它候选源的失败摘要,便于诊断「到底哪一层挂了」。
pub suppressed: Vec<String>,
}
impl DownloadError {
/// 构造错误。
#[must_use]
pub fn new(kind: DownloadErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
source: None,
message: message.into(),
attempts: 0,
suppressed: Vec::new(),
}
}
/// 标注出错的源。
#[must_use]
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
/// 标注尝试次数。
#[must_use]
pub fn with_attempts(mut self, attempts: usize) -> Self {
self.attempts = attempts;
self
}
/// 汇总其它源的失败摘要。
#[must_use]
pub fn with_suppressed(mut self, suppressed: Vec<String>) -> Self {
self.suppressed = suppressed;
self
}
}
impl fmt::Display for DownloadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.source {
Some(source) => write!(
f,
"[{}] {}(源 {})",
self.kind.as_str(),
self.message,
source
),
None => write!(f, "[{}] {}", self.kind.as_str(), self.message),
}
}
}
impl std::error::Error for DownloadError {}