Skip to content

Commit f498cac

Browse files
committed
refactor(error): 重构错误处理机制,改进核心模块和 TUI 模块的结果类型
1 parent e0d8741 commit f498cac

28 files changed

Lines changed: 642 additions & 268 deletions

.github/workflows/check.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ jobs:
2727

2828
- run: cargo build --workspace --bins
2929

30-
- run: cargo clippy --workspace -- -D warnings
31-
32-
- run: cargo clippy -p sculk-tui --no-deps -- -D warnings -W clippy::unwrap_used -W clippy::expect_used -W clippy::panic
30+
- run: cargo clippy --workspace --all-targets --all-features
3331

3432
- run: cargo test --workspace --features sculk/ci

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,9 @@ rand = "0.10"
2424
serde = { version = "1", features = ["derive"] }
2525
toml = "1"
2626
url = "2.5"
27+
thiserror = "2"
28+
29+
[workspace.lints.clippy]
30+
unwrap_used = "deny"
31+
expect_used = "deny"
32+
panic = "deny"

Justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ uninstall-all: uninstall uninstall-tui
3030
check:
3131
cargo fmt --all -- --check
3232
cargo check --workspace
33-
cargo clippy --workspace -- -D warnings
33+
cargo clippy --workspace --all-targets --all-features
3434

3535
# 离线测试
3636
test:

cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ tracing-subscriber.workspace = true
2424
[[bin]]
2525
name = "sckc"
2626
path = "src/main.rs"
27+
28+
[lints]
29+
workspace = true

cli/src/main.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ async fn run_command(cli: Cli) -> anyhow::Result<()> {
115115
password,
116116
max_players,
117117
} => {
118-
let path = key_path.unwrap_or_else(persist::default_key_path);
118+
let path = match key_path {
119+
Some(path) => path,
120+
None => persist::default_key_path()?,
121+
};
119122
let secret_key = if new_key {
120123
persist::generate_new_key(&path)?
121124
} else {
@@ -220,11 +223,11 @@ async fn run_command(cli: Cli) -> anyhow::Result<()> {
220223
println!("Using default n0 relay servers.");
221224
}
222225
} else {
223-
Cli::command()
226+
let mut relay_subcommand = Cli::command()
224227
.find_subcommand("relay")
225-
.expect("relay subcommand should exist")
226-
.clone()
227-
.print_help()?;
228+
.ok_or_else(|| anyhow::anyhow!("relay subcommand should exist"))?
229+
.clone();
230+
relay_subcommand.print_help()?;
228231
}
229232
}
230233
}
@@ -266,13 +269,17 @@ mod tests {
266269

267270
#[test]
268271
fn parse_host_command_from_args() {
269-
let cli = Cli::try_parse_from(["sculk", "host", "-p", "25565"]).expect("parse host");
272+
let cli_res = Cli::try_parse_from(["sckc", "host", "-p", "25565"]);
273+
assert!(cli_res.is_ok(), "parse host");
274+
let cli = if let Ok(v) = cli_res { v } else { return };
270275
assert!(matches!(cli.command, Commands::Host { port: 25565, .. }));
271276
}
272277

273278
#[test]
274279
fn parse_join_defaults() {
275-
let cli = Cli::try_parse_from(["sculk", "join", "ticket"]).expect("parse join");
280+
let cli_res = Cli::try_parse_from(["sckc", "join", "ticket"]);
281+
assert!(cli_res.is_ok(), "parse join");
282+
let cli = if let Ok(v) = cli_res { v } else { return };
276283
assert!(
277284
matches!(cli.command, Commands::Join { port, .. } if port == sculk::DEFAULT_INLET_PORT)
278285
);

clippy.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
allow-unwrap-in-tests = false
2+
allow-expect-in-tests = false
3+
allow-panic-in-tests = false

core/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ clipboard = ["dep:arboard"]
2020
persist = ["dep:dirs", "dep:rand", "dep:serde", "dep:toml"]
2121

2222
[dependencies]
23-
anyhow.workspace = true
2423
iroh.workspace = true
2524
tokio.workspace = true
2625
tracing.workspace = true
2726
url.workspace = true
27+
thiserror.workspace = true
2828
arboard = { workspace = true, optional = true }
2929
dirs = { workspace = true, optional = true }
3030
rand = { workspace = true, optional = true }
@@ -33,3 +33,6 @@ toml = { workspace = true, optional = true }
3333

3434
[dev-dependencies]
3535
rand.workspace = true
36+
37+
[lints]
38+
workspace = true

core/src/error.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//! core crate 统一错误类型。
2+
3+
use std::path::PathBuf;
4+
5+
use thiserror::Error;
6+
7+
/// 持久化层错误。
8+
#[derive(Debug, Error)]
9+
pub enum PersistError {
10+
/// 系统数据目录不可用。
11+
#[error("cannot determine system data directory")]
12+
SystemDataDirUnavailable,
13+
/// 路径 I/O 错误。
14+
#[error("{op} `{path}` failed: {source}")]
15+
PathIo {
16+
op: &'static str,
17+
path: PathBuf,
18+
#[source]
19+
source: std::io::Error,
20+
},
21+
/// 密钥文件长度非法。
22+
#[error("invalid key file length: expected {expected} bytes, got {actual} bytes")]
23+
InvalidKeyLength { expected: usize, actual: usize },
24+
/// profile 解析失败。
25+
#[cfg(feature = "persist")]
26+
#[error("failed to parse profile `{path}`: {source}")]
27+
ProfileParse {
28+
path: PathBuf,
29+
#[source]
30+
source: toml::de::Error,
31+
},
32+
/// profile 序列化失败。
33+
#[cfg(feature = "persist")]
34+
#[error(transparent)]
35+
ProfileSerialize(#[from] toml::ser::Error),
36+
/// relay URL 解析失败。
37+
#[error("invalid relay URL: {0}")]
38+
RelayUrlParse(String),
39+
}
40+
41+
/// 票据解析错误。
42+
#[derive(Debug, Error)]
43+
pub enum TicketError {
44+
/// URL 解析失败。
45+
#[error(transparent)]
46+
UrlParse(#[from] url::ParseError),
47+
/// 协议不匹配。
48+
#[error("invalid scheme: expected \"{expected}\", got \"{actual}\"")]
49+
InvalidScheme {
50+
expected: &'static str,
51+
actual: String,
52+
},
53+
/// 缺少 endpoint id。
54+
#[error("missing endpoint id in ticket URL")]
55+
MissingEndpointId,
56+
/// endpoint id 非法。
57+
#[error("invalid endpoint id: {0}")]
58+
EndpointIdParse(String),
59+
/// relay URL 非法。
60+
#[error("invalid relay URL: {0}")]
61+
RelayUrlParse(String),
62+
}
63+
64+
/// 隧道运行时错误。
65+
#[derive(Debug, Error)]
66+
pub enum TunnelError {
67+
/// 锁中毒。
68+
#[error("mutex poisoned: {name}")]
69+
MutexPoisoned { name: &'static str },
70+
/// 绑定 host endpoint 失败。
71+
#[error("bind host endpoint failed: {0}")]
72+
BindHostEndpoint(String),
73+
/// 绑定 join endpoint 失败。
74+
#[error("bind join endpoint failed: {0}")]
75+
BindJoinEndpoint(String),
76+
/// 绑定本地 TCP 监听失败。
77+
#[error("bind local tcp listener failed: {0}")]
78+
BindLocalListener(String),
79+
/// host accept 失败。
80+
#[error("accept host connection failed: {0}")]
81+
AcceptHostConnection(String),
82+
/// QUIC 双向流 accept 失败。
83+
#[error("accept QUIC bi stream failed: {0}")]
84+
AcceptQuicBiStream(String),
85+
/// 本地 TCP 客户端 accept 失败。
86+
#[error("accept local tcp client failed: {0}")]
87+
AcceptLocalTcpClient(String),
88+
/// 与 host 建连失败。
89+
#[error("connect host endpoint failed: {0}")]
90+
ConnectHostEndpoint(String),
91+
/// 初次连接重试耗尽。
92+
#[error("initial connection failed after {attempts} attempts")]
93+
InitialConnectionExhausted { attempts: u32 },
94+
/// 打开认证流失败。
95+
#[error("open auth stream failed: {0}")]
96+
OpenAuthStream(String),
97+
/// 接收认证流失败。
98+
#[error("accept auth stream failed: {0}")]
99+
AcceptAuthStream(String),
100+
/// 读取认证结果失败。
101+
#[error("read auth result failed: {0}")]
102+
ReadAuthResult(String),
103+
/// 读取认证负载失败。
104+
#[error("read auth payload failed: {0}")]
105+
ReadAuthPayload(String),
106+
/// 写入认证负载失败。
107+
#[error("write auth payload failed: {0}")]
108+
WriteAuthPayload(String),
109+
/// 写入认证拒绝失败。
110+
#[error("write auth rejected failed: {0}")]
111+
WriteAuthRejected(String),
112+
/// 写入认证决策失败。
113+
#[error("write auth decision failed: {0}")]
114+
WriteAuthDecision(String),
115+
/// 结束认证流失败。
116+
#[error("finish auth stream failed: {0}")]
117+
FinishAuthStream(String),
118+
/// 认证被 host 拒绝。
119+
#[error("auth rejected by host")]
120+
AuthRejectedByHost,
121+
/// 桥接 tcp->quic 失败。
122+
#[error("bridge tcp->quic failed: {0}")]
123+
BridgeTcpToQuic(String),
124+
/// 桥接 quic->tcp 失败。
125+
#[error("bridge quic->tcp failed: {0}")]
126+
BridgeQuicToTcp(String),
127+
}
128+
129+
/// sculk core 顶层错误。
130+
#[derive(Debug, Error)]
131+
pub enum SculkError {
132+
/// 持久化错误。
133+
#[error(transparent)]
134+
Persist(#[from] PersistError),
135+
/// 票据错误。
136+
#[error(transparent)]
137+
Ticket(#[from] TicketError),
138+
/// 隧道错误。
139+
#[error(transparent)]
140+
Tunnel(#[from] TunnelError),
141+
}
142+
143+
impl TunnelError {
144+
/// 构造锁中毒错误。
145+
pub fn mutex_poisoned(name: &'static str) -> Self {
146+
Self::MutexPoisoned { name }
147+
}
148+
}
149+
150+
/// core crate 统一 `Result` 别名。
151+
pub type Result<T> = std::result::Result<T, SculkError>;

core/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! ```no_run
1818
//! use sculk::tunnel::{IrohTunnel, TunnelConfig};
1919
//!
20-
//! # async fn demo() -> anyhow::Result<()> {
20+
//! # async fn demo() -> sculk::Result<()> {
2121
//! let (_tunnel, ticket, mut events) =
2222
//! IrohTunnel::host(25565, None, None, TunnelConfig::default()).await?;
2323
//! println!("share ticket: {ticket}");
@@ -34,7 +34,7 @@
3434
//! ```no_run
3535
//! use sculk::tunnel::{IrohTunnel, Ticket, TunnelConfig};
3636
//!
37-
//! # async fn demo() -> anyhow::Result<()> {
37+
//! # async fn demo() -> sculk::Result<()> {
3838
//! let ticket: Ticket = "sculk://<endpoint-id>".parse()?;
3939
//! let (_tunnel, mut events) = IrohTunnel::join(&ticket, 30000, TunnelConfig::default()).await?;
4040
//!
@@ -53,10 +53,13 @@
5353
5454
#[cfg(feature = "clipboard")]
5555
pub mod clipboard;
56+
pub mod error;
5657
#[cfg(feature = "persist")]
5758
pub mod persist;
5859
pub mod tunnel;
5960

61+
pub use error::{Result, SculkError};
62+
6063
/// Minecraft 服务端标准端口。
6164
pub const DEFAULT_MC_PORT: u16 = 25565;
6265

0 commit comments

Comments
 (0)