use std::fmt;
use crate::util::MASK;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct ProxyAuth {
username: String,
password: Password,
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum Password {
Present(String),
Absent,
NotRead,
InKeychain,
}
#[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_or(Password::Absent, |password| {
Password::Present(password.into())
}),
}
}
#[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(self) -> Self {
self.mark_missing(Password::NotRead)
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) fn password_in_keychain(self) -> Self {
self.mark_missing(Password::InKeychain)
}
#[cfg_attr(
not(any(target_os = "macos", all(target_os = "linux", feature = "linux-gnome"))),
allow(dead_code)
)]
fn mark_missing(mut self, reason: Password) -> Self {
if !matches!(self.password, Password::Present(_)) {
self.password = reason;
}
self
}
#[must_use]
pub fn username(&self) -> &str {
&self.username
}
#[must_use]
pub fn password(&self) -> Option<&str> {
match &self.password {
Password::Present(password) => Some(password),
_ => None,
}
}
#[must_use]
pub fn has_password(&self) -> bool {
matches!(self.password, Password::Present(_))
}
#[must_use]
pub fn password_state(&self) -> PasswordState {
match self.password {
Password::Present(_) => PasswordState::Present,
Password::Absent => PasswordState::Absent,
Password::NotRead => PasswordState::NotRead,
Password::InKeychain => PasswordState::InKeychain,
}
}
}
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().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
);
}
}