Files
Unlucky-Launcher/crates/cli/src/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

158 lines
4.0 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.
//! 错误形状与退出码。
//!
//! 约定(10-cli-design.md §3):**0 成功 / 1 用户错误 / 2 内部错误**。
//! 用户错误 = 用法错、目标不存在、环境不支持、依赖模块未接线;
//! 内部错误 = 不该发生的事(序列化失配等),脚本见到 2 应该当 bug 报。
use std::fmt;
use serde_json::{json, Value};
/// 退出码:成功。
pub const EXIT_OK: i32 = 0;
/// 退出码:用户错误。
pub const EXIT_USER: i32 = 1;
/// 退出码:内部错误。
pub const EXIT_INTERNAL: i32 = 2;
/// 错误大类(决定退出码)。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
/// 用户侧问题:改参数/改环境可解决。
User,
/// 内部问题:不该发生,应报 bug。
Internal,
}
impl ErrorKind {
/// 稳定名(`--json` 输出用)。
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::User => "user",
Self::Internal => "internal",
}
}
/// 对应的进程退出码。
#[must_use]
pub const fn exit_code(self) -> i32 {
match self {
Self::User => EXIT_USER,
Self::Internal => EXIT_INTERNAL,
}
}
}
/// 一条 CLI 错误。
///
/// `code` 是给脚本的稳定机读标识(`usage` / `not_found` / `unsupported_os` …),
/// `message` 是给人看的中文说明,`hint` 是「下一步该做什么」。
#[derive(Debug, Clone)]
pub struct CliError {
kind: ErrorKind,
code: &'static str,
message: String,
hint: Option<String>,
}
impl CliError {
/// 用法错误(参数不对)。
#[must_use]
pub fn usage(message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::User,
code: "usage",
message: message.into(),
hint: None,
}
}
/// 用户错误,带稳定 code。
#[must_use]
pub fn user(code: &'static str, message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::User,
code,
message: message.into(),
hint: None,
}
}
/// 内部错误,带稳定 code。
#[must_use]
pub fn internal(code: &'static str, message: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Internal,
code,
message: message.into(),
hint: None,
}
}
/// IO 失败(读配置/读写会话文件):按用户错误处理,路径进消息。
#[must_use]
pub fn io(op: &str, path: &std::path::Path, error: &std::io::Error) -> Self {
Self::user("io", format!("{op}失败:{}({error})", path.display()))
}
/// 补一句提示。
#[must_use]
pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
self.hint = Some(hint.into());
self
}
/// 错误大类。
#[must_use]
pub const fn kind(&self) -> ErrorKind {
self.kind
}
/// 稳定错误码。
#[must_use]
pub const fn code(&self) -> &'static str {
self.code
}
/// 人类可读说明。
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
/// 下一步提示。
#[must_use]
pub fn hint(&self) -> Option<&str> {
self.hint.as_deref()
}
/// 进程退出码。
#[must_use]
pub const fn exit_code(&self) -> i32 {
self.kind.exit_code()
}
/// `--json` 里的错误对象。
#[must_use]
pub fn to_json(&self) -> Value {
let mut error = json!({
"code": self.code,
"kind": self.kind.as_str(),
"message": self.message,
});
if let Some(hint) = &self.hint {
error["hint"] = Value::String(hint.clone());
}
json!({ "ok": false, "error": error })
}
}
impl fmt::Display for CliError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.message)
}
}
impl std::error::Error for CliError {}