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 开发中。
158 lines
4.0 KiB
Rust
158 lines
4.0 KiB
Rust
//! 错误形状与退出码。
|
||
//!
|
||
//! 约定(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 {}
|