refactor: remove panic-prone unwrap and expect calls
This commit is contained in:
+33
-21
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user