1use zeroize::{Zeroize, ZeroizeOnDrop};
2
3#[cfg_attr(feature = "diesel", derive(diesel_derive_newtype::DieselNewType))]
10#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent))]
11pub struct Secret(pub(crate) String);
12
13#[cfg_attr(feature = "diesel", derive(diesel_derive_newtype::DieselNewType))]
21#[cfg_attr(feature = "sqlx", derive(sqlx::Type), sqlx(transparent))]
22pub struct PasswordHash(pub(crate) Option<Secret>);
23
24impl Secret {
25 pub fn expose(&self) -> &str {
28 &self.0
29 }
30}
31
32impl Drop for Secret {
33 fn drop(&mut self) {
34 self.0.zeroize();
35 }
36}
37
38impl ZeroizeOnDrop for Secret {}
39
40impl PasswordHash {
41 pub const NONE: Self = Self(None);
42
43 pub fn exists(&self) -> bool {
44 self.0.is_some()
45 }
46
47 pub fn expose(&self) -> Option<&str> {
50 self.0.as_ref()
51 .map(Secret::expose)
52 }
53}
54
55impl From<String> for Secret {
56 fn from(string: String) -> Self {
57 Self(string)
58 }
59}
60
61impl From<String> for PasswordHash {
62 fn from(string: String) -> Self {
63 Self(Some(Secret(string)))
64 }
65}
66
67impl From<Option<String>> for PasswordHash {
68 fn from(string: Option<String>) -> Self {
69 Self(string.map(Secret))
70 }
71}
72
73impl std::fmt::Debug for Secret {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.write_str("[SECRET]")
76 }
77}
78
79impl std::fmt::Debug for PasswordHash {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.write_str(if self.exists() { "[SECRET]" } else { "[BLANK]" })
82 }
83}
84
85impl<'de> serde::Deserialize<'de> for Secret {
86 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
87 String::deserialize(deserializer)
88 .map(Self::from)
89 }
90}
91
92impl<'de> serde::Deserialize<'de> for PasswordHash {
93 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94 Option::<Secret>::deserialize(deserializer)
95 .map(Self)
96 }
97}
98
99