use std::fmt;
use crate::util::MASK;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct ProxyAuth {
username: String,
password: Option<String>,
missing: PasswordState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PasswordState {
Present,
Absent,
NotRead,
InKeychain,
}
impl ProxyAuth {
#[must_use]
pub fn new(username: impl Into<String>, password: Option<impl Into<String>>) -> Self {
Self {
username: username.into(),
password: password.map(Into::into),
missing: PasswordState::Absent,
}
}
#[must_use]
pub fn from_username(username: impl Into<String>) -> Self {
Self::new(username, None::<String>)
}
#[cfg_attr(
not(all(target_os = "linux", feature = "linux-gnome")),
allow(dead_code)
)]
pub(crate) fn password_not_read(mut self) -> Self {
if self.password.is_none() {
self.missing = PasswordState::NotRead;
}
self
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) fn password_in_keychain(mut self) -> Self {
if self.password.is_none() {
self.missing = PasswordState::InKeychain;
}
self
}
#[must_use]
pub fn username(&self) -> &str {
&self.username
}
#[must_use]
pub fn password(&self) -> Option<&str> {
self.password.as_deref()
}
#[must_use]
pub fn has_password(&self) -> bool {
self.password.is_some()
}
#[must_use]
pub fn password_state(&self) -> PasswordState {
match self.password {
Some(_) => PasswordState::Present,
None => self.missing,
}
}
}
impl fmt::Debug for ProxyAuth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let username = match self.username.split_once(':') {
Some((user, _)) => std::borrow::Cow::Owned(format!("{user}:{MASK}")),
None => std::borrow::Cow::Borrowed(self.username.as_str()),
};
f.debug_struct("ProxyAuth")
.field("username", &username)
.field("password", &self.password.as_ref().map(|_| MASK))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_auth_debug_names_its_fields_after_the_accessors() {
assert_eq!(
format!("{:?}", ProxyAuth::new("alice", Some("hunter2"))),
format!("ProxyAuth {{ username: \"alice\", password: Some({MASK:?}) }}")
);
assert_eq!(
format!("{:?}", ProxyAuth::from_username("alice:hunter2")),
format!("ProxyAuth {{ username: \"alice:{MASK}\", password: None }}")
);
}
#[test]
fn a_present_password_is_present_whatever_was_marked() {
let marked = ProxyAuth::new("alice", Some("hunter2")).password_in_keychain();
assert_eq!(marked.password_state(), PasswordState::Present);
let marked = ProxyAuth::new("alice", Some("hunter2")).password_not_read();
assert_eq!(marked.password_state(), PasswordState::Present);
assert_eq!(marked, ProxyAuth::new("alice", Some("hunter2")));
assert_eq!(
ProxyAuth::from_username("alice")
.password_in_keychain()
.password_state(),
PasswordState::InKeychain
);
}
}