Compare commits
3 Commits
d1013dfa08
...
f9cf1f71e4
| Author | SHA1 | Date | |
|---|---|---|---|
| f9cf1f71e4 | |||
| d80664db5e | |||
| 690e00e194 |
@ -148,6 +148,14 @@ make docker-multiarch IMAGE=ghcr.io/owner/yggdrasil:latest # amd64+arm64, push
|
||||
13. **WebP**: the `image` crate's `"webp"` feature is **intentionally excluded** — all WebP encode/decode goes through zenwebp (`src/webp.rs`). Do NOT add it.
|
||||
14. **JS libs** (`libs/`): pnpm workspace, TypeScript strict (target ES2020, `verbatimModuleSyntax` ⟹ use `import type`), Biome formatter (2-space, single quotes, semicolons, `trailingCommas: all`, line width 100), Vite 8 IIFE bundles written into `../../public/<dir>/`. `@yggdrasil/shared` is inlined into each IIFE — IIFEs cannot import each other at runtime. Use `make build-libs` or `make build-<name>` (`pnpm --filter`).
|
||||
15. **Heavy `//!` module docs explain WHY.** Read a module's top doc comment before editing it. User-facing strings are predominantly Chinese.
|
||||
16. **No `unwrap()` in non-test code.** `panic = "abort"` (see Profile config) means any panic kills the whole process — there is no unwind, no recovery, just an immediate crash. This is incompatible with `unwrap()`'s "I'll deal with it later" semantics. Rules:
|
||||
- **Default to `?` / `Result`** for fallible operations (DB, IO, parsing, header construction, regex compilation at call sites). Map failures through `AppError` (server) or return `Option` (WASM).
|
||||
- **`.expect("reason")` is permitted only for true invariants** — cases where a `None`/`Err` would indicate a code bug, not a runtime condition. The message MUST explain *why* it cannot fail (e.g. `"val.max(1) 保证非零"`, `"etag 仅含 ASCII hex"`, `"静态 302 响应必然构造成功"`). A bare `.expect("TODO")` or `.expect("unreachable")` is equivalent to `unwrap` and is not acceptable.
|
||||
- **`LazyLock` / `OnceLock` initialization of compile-time constants** (static `Regex`, `NonZeroU32::new(val.max(1))`, syntect's built-in `Plain Text` syntax) may use `.expect()` with an explanatory message — these run at most once and a failure means the source constant itself is wrong, which should surface immediately at startup, not silently degrade.
|
||||
- **WASM browser-context calls** (`web_sys::window()`, `Reflect::get` on a known global) may use `.expect()` only inside `#[cfg(target_arch = "wasm32")]` / `#[component]` / `use_effect` scopes where a missing `window` proves the code is running outside a browser — a deployment bug, not a runtime input.
|
||||
- **`unreachable!()` is permitted ONLY in `#[cfg(not(feature = "server"))]` stubs** of server functions — these branches are compiled out of the real server build and exist solely to satisfy the WASM target's type checker.
|
||||
- **Build-tool binaries** (`src/bin/*`) and **`#[cfg(test)]` modules** are exempt — `unwrap`/`expect`/`panic` are idiomatic in tests and one-shot codegen tools.
|
||||
- **If clippy's `unwrap_used` / `expect_used` lints are later wired in** (`[lints]` table in `Cargo.toml`), the exemptions above are the intended `allow` set; do not relax them further without a documented invariant.
|
||||
|
||||
## Workflow
|
||||
|
||||
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.6.1] - 2026-07-23
|
||||
|
||||
### Changed
|
||||
|
||||
- **消除非测试代码中的裸 `unwrap()`**:在 `panic = "abort"` 全局下,任何裸 `unwrap()` 都会直接崩溃整个进程且无法恢复。将所有非测试代码中的 `unwrap()` 改写为带不变量说明的 `expect()`(消息需解释*为何*不可能失败),并在 AGENTS.md 规范 #16 中固化该约束。
|
||||
|
||||
### Fixed
|
||||
|
||||
- **mhchem 转译器三处 panic 修复**:修复 `panic = "abort"` 下化学公式转译器的三处崩溃:`". __* "` 正则转录错误、`find_observe_end` 逐字节扫描多字节字符越界、以及 `re!` 宏编译失败时未降级为不匹配导致直接 panic。前两处为输入触发的运行时 panic,第三处补上缺失的防御性边界。
|
||||
- **Docker 构建缺 `patches/` 目录**:构建镜像时未复制 `patches/` 目录导致 `pnpm install` 报 ENOENT,补充复制修复。
|
||||
|
||||
## [0.6.0] - 2026-07-23
|
||||
|
||||
### Added
|
||||
|
||||
127
Cargo.lock
generated
127
Cargo.lock
generated
@ -321,9 +321,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@ -414,7 +414,7 @@ version = "3.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.23.0",
|
||||
"ident_case",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@ -493,9 +493,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.66"
|
||||
version = "1.2.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
||||
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@ -906,8 +906,18 @@ version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.21.3",
|
||||
"darling_macro 0.21.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"darling_macro 0.23.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -917,6 +927,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||
dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -930,7 +952,18 @@ version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.21.3",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||
dependencies = [
|
||||
"darling_core 0.23.0",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@ -1723,7 +1756,7 @@ version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.21.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@ -1742,7 +1775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2093,7 +2126,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"quanta",
|
||||
"rand 0.9.4",
|
||||
"rand 0.9.5",
|
||||
"smallvec",
|
||||
"spinning_top",
|
||||
"web-time",
|
||||
@ -2220,9 +2253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
@ -2230,9 +2263,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@ -3612,7 +3645,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3638,9 +3671,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
@ -3649,9 +3682,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
@ -3901,14 +3934,14 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.41"
|
||||
version = "0.23.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
|
||||
checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
@ -4141,9 +4174,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
@ -4189,9 +4222,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
@ -4262,9 +4295,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
@ -4561,9 +4594,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
|
||||
checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
@ -4698,14 +4731,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.12+spec-1.1.0"
|
||||
version = "0.25.13+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
||||
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow 1.0.3",
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4714,7 +4747,7 @@ version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow 1.0.3",
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4893,7 +4926,7 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.4",
|
||||
"rand 0.9.5",
|
||||
"sha1",
|
||||
"thiserror 2.0.18",
|
||||
"utf-8",
|
||||
@ -4910,7 +4943,7 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.4",
|
||||
"rand 0.9.5",
|
||||
"sha1",
|
||||
"thiserror 2.0.18",
|
||||
"utf-8",
|
||||
@ -4927,7 +4960,7 @@ dependencies = [
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.4",
|
||||
"rand 0.9.5",
|
||||
"sha1",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
@ -5021,9 +5054,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.4"
|
||||
version = "1.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
|
||||
checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
@ -5260,7 +5293,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5555,9 +5588,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -5591,7 +5624,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yggdrasil"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum",
|
||||
@ -5616,7 +5649,7 @@ dependencies = [
|
||||
"moka",
|
||||
"pinyin",
|
||||
"pulldown-cmark",
|
||||
"rand 0.8.6",
|
||||
"rand 0.8.7",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
@ -5789,9 +5822,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "yggdrasil"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
edition = "2021"
|
||||
|
||||
# 构建时工具二进制:用 syntect(server-only 依赖)生成 highlight.css。
|
||||
|
||||
@ -36,7 +36,8 @@ fn validate_username(username: &str) -> Result<(), String> {
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
static EMAIL_REGEX: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
|
||||
regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
|
||||
regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
|
||||
.expect("EMAIL_REGEX 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
|
||||
@ -93,7 +93,8 @@ pub fn validate_comment_name(name: &str) -> Result<(), String> {
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
static EMAIL_REGEX: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
|
||||
regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
|
||||
regex::Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
|
||||
.expect("EMAIL_REGEX 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 校验评论作者邮箱格式。
|
||||
|
||||
@ -252,6 +252,10 @@ fn image_response(
|
||||
headers: &HeaderMap,
|
||||
) -> Response {
|
||||
let etag = etag_for(&data);
|
||||
// etag 形如 `"deadbeef..."`(引号 + hex),都是合法 token-char,from_str 不可能失败。
|
||||
// 用 expect 说明恒成立的不变量,避免裸 unwrap 触发 lint。
|
||||
let etag_value = HeaderValue::from_str(&etag)
|
||||
.expect("etag 仅含 ASCII hex 与双引号,必然是合法的 HeaderValue");
|
||||
|
||||
if let Some(if_none_match) = headers
|
||||
.get(header::IF_NONE_MATCH)
|
||||
@ -261,7 +265,7 @@ fn image_response(
|
||||
return (
|
||||
StatusCode::NOT_MODIFIED,
|
||||
[
|
||||
(header::ETAG, HeaderValue::from_str(&etag).unwrap()),
|
||||
(header::ETAG, etag_value.clone()),
|
||||
(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static(cache_control),
|
||||
@ -286,7 +290,7 @@ fn image_response(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static(cache_control),
|
||||
),
|
||||
(header::ETAG, HeaderValue::from_str(&etag).unwrap()),
|
||||
(header::ETAG, etag_value),
|
||||
(
|
||||
header::X_CONTENT_TYPE_OPTIONS,
|
||||
HeaderValue::from_static("nosniff"),
|
||||
|
||||
@ -423,7 +423,8 @@ where
|
||||
// 匹配 pulldown-cmark 产出的 <img src="..." alt="..." /> 或 <img src="..." alt="...">
|
||||
// pulldown-cmark 格式可控:src 在前,alt 在后,属性用双引号
|
||||
static IMG_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"<img\s+src="(/uploads/[^"]+)"(?:\s+alt="([^"]*)")?\s*/?>"#).unwrap()
|
||||
Regex::new(r#"<img\s+src="(/uploads/[^"]+)"(?:\s+alt="([^"]*)")?\s*/?>"#)
|
||||
.expect("IMG_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
IMG_RE
|
||||
@ -476,8 +477,10 @@ fn wrap_tables(html: &str) -> String {
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
static TABLE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?s)<table(\s[^>]*)?>.*?</table>").unwrap());
|
||||
static TABLE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?s)<table(\s[^>]*)?>.*?</table>")
|
||||
.expect("TABLE_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
TABLE_RE
|
||||
.replace_all(html, |caps: ®ex::Captures| {
|
||||
|
||||
@ -28,7 +28,8 @@ fn env_or(key: &str, default: u32) -> NonZeroU32 {
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u32>().ok())
|
||||
.unwrap_or(default);
|
||||
NonZeroU32::new(val.max(1)).unwrap()
|
||||
// val.max(1) 保证 ≥ 1,NonZeroU32::new 必然 Some;expect 说明该不变量。
|
||||
NonZeroU32::new(val.max(1)).expect("val.max(1) 保证非零,NonZeroU32::new 不可能失败")
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
@ -82,7 +83,7 @@ static CODE_EXEC_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::
|
||||
static CODE_EXEC_DAILY_LIMITER: LazyLock<DefaultKeyedRateLimiter<String>> = LazyLock::new(|| {
|
||||
RateLimiter::keyed(
|
||||
Quota::with_period(Duration::from_secs(86_400))
|
||||
.unwrap()
|
||||
.expect("with_period 仅在 Duration 为 0 时返回 None;86_400s 必然 Some")
|
||||
.allow_burst(env_or("RATE_LIMIT_CODE_EXEC_DAILY", 50)),
|
||||
)
|
||||
});
|
||||
|
||||
@ -152,7 +152,8 @@ pub fn PostContent(content_html: String) -> Element {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use_effect(move || {
|
||||
let window = web_sys::window().unwrap();
|
||||
let window =
|
||||
web_sys::window().expect("post_content use_effect 仅在 WASM 浏览器上下文执行:无 window");
|
||||
|
||||
// 调用 window.__initPostContent('.post-content'):函数不存在时静默跳过
|
||||
// (与旧 eval 中的 if 守卫语义一致)。
|
||||
|
||||
@ -222,7 +222,7 @@ pub(crate) async fn admin_guard(
|
||||
.status(StatusCode::FOUND)
|
||||
.header(header::LOCATION, "/login")
|
||||
.body(Body::empty())
|
||||
.unwrap()
|
||||
.expect("静态 302 重定向响应(合法 status + 固定 header + 空 body)必然构造成功")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -368,7 +368,8 @@ pub fn ThemeToggle() -> Element {
|
||||
let coords = evt.client_coordinates();
|
||||
let x = coords.x;
|
||||
let y = coords.y;
|
||||
let window = web_sys::window().unwrap();
|
||||
let window = web_sys::window()
|
||||
.expect("主题切换回调仅在 WASM 浏览器上下文执行:无 window");
|
||||
let key = "__startThemeTransition".into();
|
||||
if let Ok(fn_val) = js_sys::Reflect::get(&window, &key) {
|
||||
if !fn_val.is_undefined() && !fn_val.is_null() {
|
||||
|
||||
@ -5,27 +5,34 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// 匹配 fenced code block(```...```)的正则。
|
||||
static CODE_BLOCK_RE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"```[\s\S]*?```").unwrap());
|
||||
static CODE_BLOCK_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"```[\s\S]*?```").expect("CODE_BLOCK_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 匹配行内代码(`...`)的正则。
|
||||
static INLINE_CODE_RE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"`[^`]*`").unwrap());
|
||||
static INLINE_CODE_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"`[^`]*`").expect("INLINE_CODE_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 匹配 Markdown 链接 `[text](url)` 的正则。
|
||||
static LINK_RE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"\[([^\]]*)\]\([^)]*\)").unwrap());
|
||||
static LINK_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"\[([^\]]*)\]\([^)]*\)").expect("LINK_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 匹配 Markdown 标题(# 到 ######)的正则。
|
||||
static HEADING_RE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"^#{1,6}\s*").unwrap());
|
||||
static HEADING_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"^#{1,6}\s*").expect("HEADING_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 匹配 Markdown 图片 `` 的正则。
|
||||
static IMAGE_RE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"!\[([^\]]*)\]\([^)]*\)").unwrap());
|
||||
static IMAGE_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"!\[([^\]]*)\]\([^)]*\)").expect("IMAGE_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 匹配任意空白字符的正则,用于把多个空白合并为单个空格。
|
||||
static WHITESPACE_RE: LazyLock<regex::Regex> = LazyLock::new(|| regex::Regex::new(r"\s+").unwrap());
|
||||
static WHITESPACE_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
|
||||
regex::Regex::new(r"\s+").expect("WHITESPACE_RE 正则模式应在编译期通过校验")
|
||||
});
|
||||
|
||||
/// 去除 Markdown 标记,返回近似纯文本。
|
||||
///
|
||||
|
||||
@ -13,18 +13,23 @@ use chrono::DateTime;
|
||||
///
|
||||
/// WASM 端用 `js_sys::Promise` + `web_sys::Window::set_timeout_*` 构造,
|
||||
/// 避免 `js_sys::eval` 字符串求值。全项目统一的 sleep 入口。
|
||||
///
|
||||
/// `setTimeout` 的 delay 参数是 i32,超过 `i32::MAX` 会被浏览器立即触发;这里 clamp
|
||||
/// 到安全上限,既避免 `u32 -> i32` 转换溢出 panic,也贴合 setTimeout 的合法范围。
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn sleep_ms(ms: u32) {
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
||||
web_sys::window()
|
||||
.expect("no window")
|
||||
// ms 是 u32,setTimeout 接受 i32;clamp 到 i32::MAX(约 24.8 天)避免溢出。
|
||||
let delay = ms.min(i32::MAX as u32) as i32;
|
||||
let window = web_sys::window().expect("sleep_ms 必须在浏览器上下文中调用:无 window");
|
||||
window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
&resolve.unchecked_into(),
|
||||
ms.try_into().unwrap(),
|
||||
delay,
|
||||
)
|
||||
.expect("set_timeout failed");
|
||||
.expect("setTimeout with a number delay cannot fail per WebIDL");
|
||||
});
|
||||
let _ = JsFuture::from(promise).await;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user