Skip to main content

max / audiofiles

lint: deny clippy::unwrap_used in audiofiles-sync Continues the deny(unwrap_used) ratchet. The two production unwraps were both in percent_decode: hex_val(hi_byte).unwrap()/hex_val(lo_byte).unwrap() guarded by earlier is_some() checks. Rewritten to bind the decoded nibble directly via peek().and_then(|b| hex_val(b).map(|v| (b, v))), which also drops the redundant second hex_val call. Decoding semantics unchanged -- the partial-escape edge cases (%%41 -> %A, %4%41 -> %4A, trailing %) still pass. Crate clean under the deny; 88 tests green. Remaining: audiofiles-core. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-11 17:20 UTC
Commit: 438450e78b3a9a013e955242887dd4ec5998aa7d
Parent: 824428d
2 files changed, +12 insertions, -8 deletions
@@ -207,18 +207,17 @@ fn percent_decode(input: &str) -> String {
207 207 let mut chars = input.bytes().peekable();
208 208 while let Some(b) = chars.next() {
209 209 match b {
210 - b'%' => match chars.peek().copied() {
211 - Some(hi_byte) if hex_val(hi_byte).is_some() => {
210 + b'%' => match chars.peek().copied().and_then(|byte| hex_val(byte).map(|v| (byte, v))) {
211 + Some((hi_byte, hi)) => {
212 212 chars.next(); // consume the high nibble
213 - match chars.peek().copied() {
214 - Some(lo_byte) if hex_val(lo_byte).is_some() => {
213 + match chars.peek().copied().and_then(|byte| hex_val(byte).map(|v| (byte, v))) {
214 + Some((_, lo)) => {
215 215 chars.next(); // consume the low nibble
216 - // Both nibbles validated above.
217 - bytes.push((hex_val(hi_byte).unwrap() << 4) | hex_val(lo_byte).unwrap());
216 + bytes.push((hi << 4) | lo);
218 217 }
219 218 // Valid high nibble but no valid low nibble: emit '%' and
220 219 // the high byte literally, leaving the rest unconsumed.
221 - _ => {
220 + None => {
222 221 bytes.push(b'%');
223 222 bytes.push(hi_byte);
224 223 }
@@ -226,7 +225,7 @@ fn percent_decode(input: &str) -> String {
226 225 }
227 226 // '%' not followed by a hex digit: emit it literally and leave the
228 227 // next byte for the next iteration (it may start a real escape).
229 - _ => bytes.push(b'%'),
228 + None => bytes.push(b'%'),
230 229 },
231 230 b'+' => bytes.push(b' '),
232 231 other => bytes.push(other),
@@ -3,6 +3,11 @@
3 3 //! Provides [`SyncManager`] as the public API, callable from the GUI thread.
4 4 //! All async work runs on an internal tokio runtime handle.
5 5
6 + // Part of the deny(unwrap_used) ratchet (see audiofiles-app / -rhai / -browser).
7 + // Forbid bare unwrap in production code; tests opt out. `expect` with a
8 + // justification stays allowed for documented invariants.
9 + #![cfg_attr(not(test), deny(clippy::unwrap_used))]
10 +
6 11 pub mod auth;
7 12 pub mod error;
8 13 pub mod scheduler;