diff --git a/Cargo.lock b/Cargo.lock index 4db672fa7..a52eabf07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4606,6 +4606,7 @@ dependencies = [ "tracing", "url", "uv-once-map", + "uv-small-str", "uv-static", "wiremock", ] diff --git a/crates/uv-auth/Cargo.toml b/crates/uv-auth/Cargo.toml index 218e2c390..9c454a357 100644 --- a/crates/uv-auth/Cargo.toml +++ b/crates/uv-auth/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] uv-once-map = { workspace = true } +uv-small-str = { workspace = true } uv-static = { workspace = true } anyhow = { workspace = true } diff --git a/crates/uv-auth/src/cache.rs b/crates/uv-auth/src/cache.rs index be61796ba..d794b52a1 100644 --- a/crates/uv-auth/src/cache.rs +++ b/crates/uv-auth/src/cache.rs @@ -1,3 +1,4 @@ +use std::fmt::Formatter; use std::hash::BuildHasherDefault; use std::sync::Arc; use std::sync::RwLock; @@ -41,26 +42,30 @@ impl CredentialsCache { /// Return the credentials that should be used for a realm and username, if any. pub(crate) fn get_realm(&self, realm: Realm, username: Username) -> Option> { let realms = self.realms.read().unwrap(); - let name = if let Some(username) = username.as_deref() { - format!("{username}@{realm}") - } else { - realm.to_string() - }; let given_username = username.is_some(); let key = (realm, username); let Some(credentials) = realms.get(&key).cloned() else { - trace!("No credentials in cache for realm {name}"); + trace!( + "No credentials in cache for realm {}", + RealmUsername::from(key) + ); return None; }; if given_username && credentials.password().is_none() { // If given a username, don't return password-less credentials - trace!("No password in cache for realm {name}"); + trace!( + "No password in cache for realm {}", + RealmUsername::from(key) + ); return None; } - trace!("Found cached credentials for realm {name}"); + trace!( + "Found cached credentials for realm {}", + RealmUsername::from(key) + ); Some(credentials) } @@ -214,6 +219,26 @@ impl TrieState { } } +#[derive(Debug)] +struct RealmUsername(Realm, Username); + +impl std::fmt::Display for RealmUsername { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let Self(realm, username) = self; + if let Some(username) = username.as_deref() { + write!(f, "{username}@{realm}") + } else { + write!(f, "{realm}") + } + } +} + +impl From<(Realm, Username)> for RealmUsername { + fn from((realm, username): (Realm, Username)) -> Self { + Self(realm, username) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/uv-auth/src/credentials.rs b/crates/uv-auth/src/credentials.rs index cba5b264b..82408b556 100644 --- a/crates/uv-auth/src/credentials.rs +++ b/crates/uv-auth/src/credentials.rs @@ -84,6 +84,10 @@ impl Credentials { self.username.clone() } + pub(crate) fn as_username(&self) -> &Username { + &self.username + } + pub fn password(&self) -> Option<&str> { self.password.as_deref() } diff --git a/crates/uv-auth/src/middleware.rs b/crates/uv-auth/src/middleware.rs index 16c4e542b..8a59b91c5 100644 --- a/crates/uv-auth/src/middleware.rs +++ b/crates/uv-auth/src/middleware.rs @@ -194,7 +194,7 @@ impl Middleware for AuthMiddleware { None } else if let Some(credentials) = self .cache() - .get_url(request.url(), &credentials.to_username()) + .get_url(request.url(), credentials.as_username()) { request = credentials.authenticate(request); // Do not insert already-cached credentials diff --git a/crates/uv-auth/src/realm.rs b/crates/uv-auth/src/realm.rs index 92d73d5f8..cfedf299c 100644 --- a/crates/uv-auth/src/realm.rs +++ b/crates/uv-auth/src/realm.rs @@ -1,6 +1,7 @@ use std::{fmt::Display, fmt::Formatter}; use url::Url; +use uv_small_str::SmallString; /// Used to determine if authentication information should be retained on a new URL. /// Based on the specification defined in RFC 7235 and 7230. @@ -23,16 +24,16 @@ use url::Url; // so we do not need any special handling here. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct Realm { - scheme: String, - host: Option, + scheme: SmallString, + host: Option, port: Option, } impl From<&Url> for Realm { fn from(url: &Url) -> Self { Self { - scheme: url.scheme().to_string(), - host: url.host_str().map(str::to_string), + scheme: SmallString::from(url.scheme()), + host: url.host_str().map(SmallString::from), port: url.port(), } }