use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg_attr(feature = "diesel", derive(diesel_derive_newtype::DieselNewType))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent))]
pub struct Secret(pub(crate) String);
#[cfg_attr(feature = "diesel", derive(diesel_derive_newtype::DieselNewType))]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent))]
pub struct PasswordHash(pub(crate) Option<Secret>);
impl Secret {
pub fn expose(&self) -> &str {
&self.0
}
}
impl Drop for Secret {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl ZeroizeOnDrop for Secret {}
impl PasswordHash {
pub const NONE: Self = Self(None);
pub fn exists(&self) -> bool {
self.0.is_some()
}
pub fn expose(&self) -> Option<&str> {
self.0.as_ref()
.map(Secret::expose)
}
}
impl From<String> for Secret {
fn from(string: String) -> Self {
Self(string)
}
}
impl From<String> for PasswordHash {
fn from(string: String) -> Self {
Self(Some(Secret(string)))
}
}
impl From<Option<String>> for PasswordHash {
fn from(string: Option<String>) -> Self {
Self(string.map(Secret))
}
}
impl std::fmt::Debug for Secret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("[SECRET]")
}
}
impl std::fmt::Debug for PasswordHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(if self.exists() { "[SECRET]" } else { "[BLANK]" })
}
}
impl<'de> serde::Deserialize<'de> for Secret {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer)
.map(Self::from)
}
}
impl<'de> serde::Deserialize<'de> for PasswordHash {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Option::<Secret>::deserialize(deserializer)
.map(Self)
}
}