Files
Unlucky-Launcher/crates/core/src/launch/error.rs
T
AskaEth bc41dfcf7d
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
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +08:00

166 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! launch 模块统一错误类型。
//!
//! 设计依据:`DevDocs/PlanDoc/06-launch-and-diagnostics.md` §1、`analyse/02` §8.4-3
//! (缺库/缺组件必须结构化报错并显式失败,而不是交给运行时 `NoClassDefFoundError`)。
use std::fmt;
use std::path::PathBuf;
/// launch 相关操作的统一结果类型。
pub type Result<T> = std::result::Result<T, LaunchError>;
/// 缺失的组件(库 / natives / 资产),供 UI 与 agent 直接展示与修复。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MissingComponent {
/// 逻辑名,例如 `org.lwjgl:lwjgl:3.3.1` 或 `libraries/foo/bar.jar`。
pub name: String,
/// 期望落盘路径(未知时为 `None`)。
pub path: Option<PathBuf>,
}
impl MissingComponent {
/// 只按名字构造(路径未知)。
pub fn named(name: impl Into<String>) -> Self {
Self {
name: name.into(),
path: None,
}
}
/// 带期望路径构造。
pub fn at_path(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
Self {
name: name.into(),
path: Some(path.into()),
}
}
}
/// 命令行组装阶段的失败原因(全部是“启动前就能确定”的错误)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssemblyError {
/// 清单里没有主类,无法启动(HMCL 在此直接抛异常)。
MissingMainClass,
/// classpath 为空(一个库和主 jar 都没有)。
EmptyClasspath,
/// Java 可执行文件路径为空。
EmptyJavaPath,
/// 组件不完整:启动前显式失败,附结构化清单。
MissingComponents(Vec<MissingComponent>),
/// 清单参数畸形(例如空参数项)。
MalformedArgument {
/// 原始参数项文本。
entry: String,
/// 原因说明。
reason: &'static str,
},
/// natives 解压失败(jar 缺失/损坏、目录不可写)。
///
/// 归到「启动前失败」而不是运行时错误:native 库没落地时游戏只会给出
/// `UnsatisfiedLinkError`,那时候已经离「到底哪个 jar 坏了」太远了。
NativesExtraction {
/// 人读原因(含失败路径)。
message: String,
},
}
impl fmt::Display for AssemblyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingMainClass => f.write_str("版本清单缺少主类(mainClass)"),
Self::EmptyClasspath => f.write_str("classpath 为空:没有任何库或主 jar"),
Self::EmptyJavaPath => f.write_str("Java 可执行文件路径为空"),
Self::MissingComponents(items) => {
write!(f, "组件缺失 {} 项", items.len())?;
for item in items.iter().take(8) {
write!(f, "; {}", item.name)?;
}
Ok(())
}
Self::MalformedArgument { entry, reason } => {
write!(f, "参数畸形({reason}):{entry}")
}
Self::NativesExtraction { message } => {
write!(f, "natives 解压失败:{message}")
}
}
}
}
impl std::error::Error for AssemblyError {}
/// launch 模块的统一错误。
#[derive(Debug)]
pub enum LaunchError {
/// 命令行组装失败(纯计算,可单测)。
Assembly(AssemblyError),
/// 文件系统操作失败。
Io {
/// 操作名(`create_session` / `read_argfile` 等)。
op: &'static str,
/// 相关路径。
path: PathBuf,
/// 底层错误。
source: std::io::Error,
},
/// 进程派生失败(Java 不存在、权限不足等)。
Spawn {
/// 被执行的程序。
program: PathBuf,
/// 底层错误。
source: std::io::Error,
},
/// 会话事件流写入/解析失败。
Session {
/// 会话文件路径。
path: PathBuf,
/// 原因说明。
reason: String,
},
/// 进程树 kill 失败。
Kill {
/// 目标进程号。
pid: u32,
/// 底层错误。
source: std::io::Error,
},
}
impl fmt::Display for LaunchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assembly(err) => write!(f, "命令行组装失败:{err}"),
Self::Io { op, path, source } => {
write!(f, "{op} 失败({}):{source}", path.display())
}
Self::Spawn { program, source } => {
write!(f, "启动进程失败({}):{source}", program.display())
}
Self::Session { path, reason } => {
write!(f, "会话事件流错误({}):{reason}", path.display())
}
Self::Kill { pid, source } => {
write!(f, "结束进程树失败(pid {pid}):{source}")
}
}
}
}
impl std::error::Error for LaunchError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Assembly(err) => Some(err),
Self::Io { source, .. } | Self::Spawn { source, .. } | Self::Kill { source, .. } => {
Some(source)
}
Self::Session { .. } => None,
}
}
}
impl From<AssemblyError> for LaunchError {
fn from(err: AssemblyError) -> Self {
Self::Assembly(err)
}
}