fix(unwrap): production unwrap sweep + ban_unwrap_in_production rename (#844 item 11) - #851
Conversation
…#844) Closes #844 item 11. ## Migration sweep (~28 sites across 14 files) Removed every `.unwrap()` from fbuild production code outside of `#[cfg(test)]` modules and sibling test files. Strategy per site: * serde JSON serialization of typed structs/enums (`ClientMessage`, `SerialServerMessage`) — `.expect("CRATE: <type> serialization is infallible")`. These types contain only primitives + collections, so serialization cannot fail. * `Mutex::lock()` / `RwLock::write()` — `.unwrap_or_else(|e| e.into_inner())` for poison-tolerant lock acquisition (matches the `ban_poison_panic` dylint guidance). * Static regex / URL `.parse()` — `.expect("CRATE: <pattern> is valid")` for compile-time-known patterns. * Slice `.try_into()` to `[u8; N]` with bounds-checked indices — `.expect("CRATE: N-byte slice converts to [u8; N] (bounds checked above)")`. * Guard-protected `Option::unwrap` — `.expect("CRATE: <invariant enforced upstream>")` with a searchable, actionable message. * `find_firmware().parent().unwrap()` — `.expect(...)` (a discovered firmware path always has a parent dir). * `fbuild_build::avr::mcu_config::get_avr_config().unwrap()` in `handlers/operations/deploy.rs` — converted to `?` since the enclosing pipeline closure already returns `Result`. * `axum::response::Response::builder().body(...).unwrap()` — `.expect("static NDJSON response builder cannot fail")`. Files touched: crates/fbuild-build/src/compiler.rs crates/fbuild-build/src/esp32/orchestrator/build.rs crates/fbuild-build/src/pipeline/compile.rs crates/fbuild-build/src/pipeline/sequential.rs crates/fbuild-cli/src/cli/bloat_lookup.rs crates/fbuild-cli/src/cli/clang_tools.rs crates/fbuild-cli/src/lib_select.rs crates/fbuild-cli/src/mcp/server.rs crates/fbuild-config/src/bin/enrich_boards.rs crates/fbuild-config/src/ini_parser/parser.rs crates/fbuild-daemon/src/handlers/operations/build.rs crates/fbuild-daemon/src/handlers/operations/deploy.rs crates/fbuild-daemon/src/handlers/websockets.rs crates/fbuild-daemon/src/main.rs crates/fbuild-deploy/src/esp32/image.rs crates/fbuild-header-scan/src/scanner.rs crates/fbuild-paths/src/lib.rs crates/fbuild-python/src/async_serial_monitor.rs crates/fbuild-python/src/json_rpc.rs crates/fbuild-python/src/serial_monitor.rs crates/fbuild-serial/src/crash_decoder.rs No function signatures changed — blast radius is local to each call site. Only one site (deploy.rs `avr_config?`) became fallible, and the enclosing closure already had a `Result` return type so no caller change was needed. ## Lint extension + rename Renamed `dylints/ban_unwrap_in_daemon_handlers/` → `dylints/ban_unwrap_in_production/`, since the scope is no longer daemon-handler-only. Widened the in-scope filter from `crates/fbuild-daemon/src/handlers/` to: * `crates/fbuild-daemon/src/**` (all daemon code, not just handlers) * `crates/fbuild-cli/src/cli/**` (all CLI subcommand code) Tightened sibling-test-file detection from a substring match (`name.contains("tests")`, which would have exempted production files named e.g. `run_tests_pipeline.rs`) to a strict pattern: `tests.rs`, `*_tests.rs`, or `tests_*.rs`. Existing test-file exemptions still fire correctly. Also updated: * workspace root `Cargo.toml` `[workspace] exclude` entry * `ci/hooks/crate_guard.py` `APPROVED_CRATE_DIRS` allowlist * `dylints/README.md` lint catalog * `dylints/ban_std_sync_mutex_in_async/src/lib.rs` doc reference ## Verification * `soldr cargo check --workspace --all-targets` — clean * `soldr cargo clippy --workspace --all-targets -- -D warnings` — clean (exit 0; only the pre-existing clippy.toml MSRV-mismatch info note) * `soldr cargo test --workspace --no-fail-fast --lib` — all crates I touched pass; the 3 daemon-lib failures (`handlers::{health,locks}::tests::...`) reproduce identically on origin/main and are unrelated to this change (they panic with "can call blocking only when running on the multi-threaded runtime" in `status_manager.rs:221` — a pre-existing test-runtime setup bug). Post-sweep audit script confirms zero production unwrap sites remain outside `#[cfg(test)]`-annotated functions and sibling test files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (31)
📝 WalkthroughWalkthroughThe Changesban_unwrap_in_production lint expansion and call-site remediation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Releases the post-#813 / #802 / #826 / #844 work landed across: - PR #843 — workspace-wide timeout sweep (#802 family) - PR #837, #849, #850, #851 — internal-bridges + dylint sweep (#826 + #844) - PR #842 — async migration follow-ups (#813) 15 dylints now ship in dylints/ (4 from earlier, 10 from #826/#844, plus the renamed ban_unwrap_in_production). 7 internal bridge APIs: fbuild_core::{http, fs, time, channel, path::canonicalize_existing}, fbuild_paths::temp_subdir, fbuild_cli::output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes #844 item 11. After accurate filtering (excluding
#[cfg(test)]mod blocks and sibling test files liketests.rs/*_tests.rs), the production unwrap count was ~30 — not 2,238. All migrated, lint renamed and widened.Migrations (28 sites across 21 files)
Per-file approach:
Mutex::lock→unwrap_or_else(|e| e.into_inner())for poison tolerance; serdeto_string→expect("ClientMessage X serialization is infallible")expect("static <name> pattern is a valid regex")expect("4-byte slice converts to [u8; 4] (bounds checked above)")expect("infallible")expect("board JSON path always has a file stem")expect("infallible")expect("is_raw_string_open guarantees ' ' / '(' ahead")COMPILER_IDENTITY_CACHE.lock()→ poison-tolerantexpect("never closed before all tasks finish")expect("'http://localhost' is a valid CORS origin")?(deploy pipeline closure already returns Result)Lint extension
dylints/ban_unwrap_in_daemon_handlers/renamed todylints/ban_unwrap_in_production/:crates/fbuild-daemon/src/handlers/**to ALL ofcrates/fbuild-daemon/src/**ANDcrates/fbuild-cli/src/cli/**name.contains("tests")to strict patternstests.rs/*_tests.rs/tests_*.rs(the old substring match would have accidentally exempted production files likerun_tests_pipeline.rs)Cargo.tomlexclude,ci/hooks/crate_guard.pyallowlist,dylints/README.mdrow, and cross-references all updatedTest plan
soldr cargo check --workspace --all-targets— cleansoldr cargo clippy --workspace --all-targets -- -D warnings— cleansoldr cargo test --workspace --no-fail-fast --lib— passes for every touched crate. The 3 pre-existingfbuild-daemonhandlers::{health,locks}::tests::...failures (panic atstatus_manager.rs:221: "can call blocking only when running on the multi-threaded runtime") reproduce identically onorigin/mainand are unrelated to this change.Closes #844.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
.unwrap()usage with safer alternatives and clearer error handling.Bug Fixes