From f7e1ae94b729cefdf42ca6c0e693a2da284eb39b Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Tue, 10 Nov 2026 11:24:27 +0100 Subject: [PATCH] url_utils: hash Extension by what equality compares --- .../lib/url_utils/src/extension/extension.rs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/lib/url_utils/src/extension/extension.rs b/crates/lib/url_utils/src/extension/extension.rs index a92df6d43c..fd0819b1e8 100644 --- a/crates/lib/url_utils/src/extension/extension.rs +++ b/crates/lib/url_utils/src/extension/extension.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::fmt; +use std::hash::{Hash, Hasher}; /// A file extension that can be either a compile-time constant or a runtime-parsed value. /// @@ -16,7 +17,7 @@ use std::fmt; /// assert_eq!(ext.without_period(), "mp4"); /// assert_eq!(ext.with_period(), ".mp4"); /// ``` -#[derive(Clone, Eq, Hash)] +#[derive(Clone, Eq)] pub struct Extension { /// The extension without the leading period, e.g. "png", "mp4". value: Cow<'static, str>, @@ -67,6 +68,14 @@ impl PartialEq for Extension { } } +// Hashes what `eq` compares, so equal extensions always hash alike (a derived `Hash` would also +// hash `dotted`, which `eq` ignores). +impl Hash for Extension { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + impl fmt::Debug for Extension { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Extension(\"{}\")", self.value) @@ -126,6 +135,22 @@ mod tests { assert_eq!(a, PNG); } + #[test] + fn equal_extensions_hash_alike() { + use std::collections::hash_map::DefaultHasher; + fn hash_of(ext: &Extension) -> u64 { + let mut h = DefaultHasher::new(); + ext.hash(&mut h); + h.finish() + } + // `eq` looks only at the bare extension, so a constant whose dotted form was written + // differently is still equal, and must land in the same hash bucket. + const ODD: Extension = Extension::from_static("png", "PNG"); + assert_eq!(ODD, PNG); + assert_eq!(hash_of(&ODD), hash_of(&PNG)); + assert_eq!(hash_of(&Extension::new("png")), hash_of(&PNG)); + } + #[test] fn clone() { let a = Extension::new("wav");