refactor: remove panic-prone unwrap and expect calls

This commit is contained in:
lbl
2026-08-23 12:04:40 +08:00
parent 2100ec1ca5
commit c2fd1ff870
18 changed files with 161 additions and 128 deletions
+33 -21
View File
@@ -5,16 +5,20 @@
//! - 找不到 pnpm 时:已有产物则告警并沿用;没有产物则报错并给出指引
//! - 设置环境变量 VNT_WEB_SKIP_UI_BUILD=1 可完全跳过前端构建
use std::error::Error;
use std::io;
use std::path::Path;
use std::process::Command;
use std::time::SystemTime;
fn main() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
fn main() -> Result<(), Box<dyn Error>> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
let manifest_dir = Path::new(&manifest_dir);
let ui_dir = manifest_dir.join("ui");
let static_dir = manifest_dir.join("static");
let workspace_root = manifest_dir.parent().expect("workspace root");
let workspace_root = manifest_dir
.parent()
.ok_or_else(|| io::Error::other("vnt-web manifest directory has no parent"))?;
// UI 源码变化时重新运行本脚本
println!("cargo:rerun-if-changed={}", ui_dir.join("src").display());
@@ -38,12 +42,12 @@ fn main() {
println!("cargo:rerun-if-env-changed=VNT_WEB_SKIP_UI_BUILD");
if std::env::var("VNT_WEB_SKIP_UI_BUILD").is_ok() {
ensure_static_placeholder(&static_dir);
return;
ensure_static_placeholder(&static_dir)?;
return Ok(());
}
if static_is_fresh(&ui_dir, &static_dir) {
return;
return Ok(());
}
let Some(pnpm) = find_pnpm() else {
@@ -51,20 +55,22 @@ fn main() {
println!(
"cargo:warning=未找到 pnpm,沿用 vnt-web/static 中已有的前端产物(可能不是最新)"
);
return;
return Ok(());
}
panic!(
return Err(io::Error::other(
"未找到 pnpm 且 vnt-web/static 没有前端产物。\n\
请安装 Node.js 与 pnpm 后重新构建(cargo 会自动完成前端构建),\n\
或从发布包中获取 static 目录放入 vnt-web/。"
);
或从发布包中获取 static 目录放入 vnt-web/。",
)
.into());
};
if !ui_dir.join("node_modules").is_dir() {
// ui 依赖使用 workspace catalog,必须在仓库根目录安装
run_or_panic(pnpm, &["install", "--frozen-lockfile"], workspace_root);
run_command(pnpm, &["install", "--frozen-lockfile"], workspace_root)?;
}
run_or_panic(pnpm, &["--filter", "vnt-web-ui", "build"], workspace_root);
run_command(pnpm, &["--filter", "vnt-web-ui", "build"], workspace_root)?;
Ok(())
}
/// pnpm 命令名(Windows 上是 pnpm.cmd,由 cmd.exe 执行)
@@ -80,7 +86,7 @@ fn find_pnpm() -> Option<&'static str> {
.find(|cmd| Command::new(cmd).arg("--version").output().is_ok())
}
fn run_or_panic(program: &str, args: &[&str], dir: &Path) {
fn run_command(program: &str, args: &[&str], dir: &Path) -> io::Result<()> {
println!(
"cargo:warning=执行前端构建: {} {} ({})",
program,
@@ -91,15 +97,21 @@ fn run_or_panic(program: &str, args: &[&str], dir: &Path) {
.args(args)
.current_dir(dir)
.status()
.unwrap_or_else(|e| panic!("执行 {} 失败: {}", program, e));
.map_err(|error| {
io::Error::new(
error.kind(),
format!("执行 {program} 失败(目录 {}):{error}", dir.display()),
)
})?;
if !status.success() {
panic!(
return Err(io::Error::other(format!(
"前端构建失败: {} {} (exit: {:?})",
program,
args.join(" "),
status.code()
);
)));
}
Ok(())
}
/// static 产物是否比 UI 源码新
@@ -128,16 +140,16 @@ fn newest_mtime(dir: &Path) -> Option<SystemTime> {
}
/// 跳过构建时保证 static/ 存在,使 rust_embed 可以编译
fn ensure_static_placeholder(static_dir: &Path) {
fn ensure_static_placeholder(static_dir: &Path) -> io::Result<()> {
if static_dir.join("index.html").is_file() {
return;
return Ok(());
}
println!("cargo:warning=VNT_WEB_SKIP_UI_BUILD 已设置且 static 为空,写入占位页面");
std::fs::create_dir_all(static_dir).expect("创建 static 目录失败");
std::fs::create_dir_all(static_dir)?;
std::fs::write(
static_dir.join("index.html"),
"<!doctype html><html><body><p>VNT Web UI 未构建。请安装 pnpm 后重新执行 cargo build\
或取消 VNT_WEB_SKIP_UI_BUILD。</p></body></html>",
)
.expect("写入占位页面失败");
)?;
Ok(())
}
+17 -14
View File
@@ -21,7 +21,7 @@ use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use time::{OffsetDateTime, format_description};
use time::{OffsetDateTime, macros::format_description};
use tokio::fs;
use tokio::net::TcpListener;
use tokio_util::sync::CancellationToken;
@@ -205,7 +205,7 @@ impl HttpAppState {
fn timestamp() -> String {
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
let format = format_description!("[hour]:[minute]:[second]");
now.format(&format)
.unwrap_or_else(|_| "00:00:00".to_string())
}
@@ -649,9 +649,10 @@ pub async fn run_http_server(
let handle = service
.start_http(addr, token, cancellation.clone())
.await?;
shutdown_signal().await;
let shutdown_result = shutdown_signal().await;
cancellation.cancel();
handle.await??;
shutdown_result?;
Ok(())
}
@@ -751,7 +752,8 @@ fn build_headers_for_path(path: &str) -> HeaderMap {
};
headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_str(mime.as_ref()).unwrap(),
HeaderValue::from_str(mime.as_ref())
.unwrap_or_else(|_| HeaderValue::from_static("application/octet-stream")),
);
if is_gz {
@@ -1309,7 +1311,7 @@ async fn save_config(Json(req): Json<SaveConfigReq>) -> Json<ApiResponse<()>> {
.unwrap_or_else(|| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.unwrap_or_default()
.as_millis();
format!("{}.toml", now)
});
@@ -1455,28 +1457,29 @@ fn convert_config(cfg: StartConfig) -> anyhow::Result<CoreConfig> {
})
}
async fn shutdown_signal() {
async fn shutdown_signal() -> anyhow::Result<()> {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
.context("failed to install Ctrl+C handler")
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
let mut signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.context("failed to install terminate signal handler")?;
signal.recv().await;
Ok::<(), anyhow::Error>(())
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
let terminate = std::future::pending::<anyhow::Result<()>>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
result = ctrl_c => result?,
result = terminate => result?,
}
Ok(())
}
async fn get_peers(