1use std::fmt;
4
5use chrono::Utc;
6use serde::{Deserialize, Serialize};
7
8pub type DateTime = chrono::DateTime<Utc>;
10
11pub mod null_as_default {
20 use serde::{Deserialize, Deserializer};
21
22 pub fn deserialize<'de, D: Deserializer<'de>, T: Deserialize<'de> + Default>(
24 deserializer: D,
25 ) -> Result<T, D::Error> {
26 Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
27 }
28}
29
30#[derive(Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
33#[serde(transparent)]
34pub struct SensitiveString(String);
35
36impl SensitiveString {
37 pub fn new(value: impl Into<String>) -> SensitiveString {
39 SensitiveString(value.into())
40 }
41
42 pub fn expose(&self) -> &str {
44 &self.0
45 }
46
47 pub fn into_inner(self) -> String {
49 self.0
50 }
51
52 pub fn is_empty(&self) -> bool {
54 self.0.is_empty()
55 }
56}
57
58impl From<String> for SensitiveString {
59 fn from(value: String) -> SensitiveString {
60 SensitiveString(value)
61 }
62}
63
64impl From<&str> for SensitiveString {
65 fn from(value: &str) -> SensitiveString {
66 SensitiveString(value.to_string())
67 }
68}
69
70impl fmt::Debug for SensitiveString {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 if self.0.is_empty() {
73 f.write_str("\"\"")
74 } else {
75 f.write_str("[REDACTED]")
76 }
77 }
78}
79
80impl fmt::Display for SensitiveString {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 if self.0.is_empty() {
83 Ok(())
84 } else {
85 f.write_str("[REDACTED]")
86 }
87 }
88}
89
90#[cfg(test)]
91#[allow(clippy::unwrap_used)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn sensitive_strings_hide_their_value() {
97 let secret = SensitiveString::new("jane@example.com");
98 assert_eq!(format!("{secret:?}"), "[REDACTED]");
99 assert_eq!(secret.to_string(), "[REDACTED]");
100 assert_eq!(secret.expose(), "jane@example.com");
101 assert_eq!(
102 serde_json::to_string(&secret).unwrap(),
103 "\"jane@example.com\""
104 );
105 }
106
107 #[test]
108 fn timestamps_read_with_any_offset() {
109 let utc: DateTime = serde_json::from_str("\"2026-01-01T00:00:00Z\"").unwrap();
110 let offset: DateTime = serde_json::from_str("\"2025-12-31T19:00:00-05:00\"").unwrap();
111 assert_eq!(utc, offset);
112 }
113}