use std::fmt;
use chrono::Utc;
use serde::{Deserialize, Serialize};
pub type DateTime = chrono::DateTime<Utc>;
pub mod null_as_default {
use serde::{Deserialize, Deserializer};
pub fn deserialize<'de, D: Deserializer<'de>, T: Deserialize<'de> + Default>(
deserializer: D,
) -> Result<T, D::Error> {
Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SensitiveString(String);
impl SensitiveString {
pub fn new(value: impl Into<String>) -> SensitiveString {
SensitiveString(value.into())
}
pub fn expose(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<String> for SensitiveString {
fn from(value: String) -> SensitiveString {
SensitiveString(value)
}
}
impl From<&str> for SensitiveString {
fn from(value: &str) -> SensitiveString {
SensitiveString(value.to_string())
}
}
impl fmt::Debug for SensitiveString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
f.write_str("\"\"")
} else {
f.write_str("[REDACTED]")
}
}
}
impl fmt::Display for SensitiveString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
Ok(())
} else {
f.write_str("[REDACTED]")
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn sensitive_strings_hide_their_value() {
let secret = SensitiveString::new("jane@example.com");
assert_eq!(format!("{secret:?}"), "[REDACTED]");
assert_eq!(secret.to_string(), "[REDACTED]");
assert_eq!(secret.expose(), "jane@example.com");
assert_eq!(
serde_json::to_string(&secret).unwrap(),
"\"jane@example.com\""
);
}
#[test]
fn timestamps_read_with_any_offset() {
let utc: DateTime = serde_json::from_str("\"2026-01-01T00:00:00Z\"").unwrap();
let offset: DateTime = serde_json::from_str("\"2025-12-31T19:00:00-05:00\"").unwrap();
assert_eq!(utc, offset);
}
}