use pretty_simple_display::{DebugPretty, DisplaySimple};
use serde::{Deserialize, Serialize};
use std::fmt;
const REDACTED: &str = "***";
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct LoginCredentials {
pub login: String,
pub password: String,
pub remember_me: bool,
}
impl fmt::Debug for LoginCredentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LoginCredentials")
.field("login", &self.login)
.field("password", &REDACTED)
.field("remember_me", &self.remember_me)
.finish()
}
}
impl fmt::Display for LoginCredentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[allow(dead_code)]
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LoginResponseUser {
pub email: String,
pub username: String,
pub external_id: String,
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LoginResponse {
pub user: LoginResponseUser,
pub session_token: String,
pub remember_token: Option<String>,
}
impl fmt::Debug for LoginResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LoginResponse")
.field("user", &self.user)
.field("session_token", &REDACTED)
.field(
"remember_token",
&self.remember_token.as_deref().map(|_| REDACTED),
)
.finish()
}
}
impl fmt::Display for LoginResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_response() -> LoginResponse {
LoginResponse {
user: LoginResponseUser {
email: "user@example.com".to_string(),
username: "user".to_string(),
external_id: "ext-1".to_string(),
},
session_token: "super-secret-session-token".to_string(),
remember_token: Some("super-secret-remember-token".to_string()),
}
}
#[test]
fn test_login_response_debug_redacts_tokens() {
let resp = sample_response();
for output in [format!("{resp:?}"), format!("{resp}")] {
assert!(!output.contains("super-secret-session-token"));
assert!(!output.contains("super-secret-remember-token"));
assert!(output.contains("***"));
assert!(output.contains("user@example.com"));
}
}
#[test]
fn test_login_credentials_debug_redacts_password() {
let creds = LoginCredentials {
login: "user".to_string(),
password: "super-secret-password".to_string(),
remember_me: true,
};
for output in [format!("{creds:?}"), format!("{creds}")] {
assert!(!output.contains("super-secret-password"));
assert!(output.contains("***"));
}
}
}