use std::fmt;
use std::sync::OnceLock;
use cheetah_string::CheetahString;
use serde::Deserialize;
use serde::Serialize;
use crate::authentication::enums::subject_type::SubjectType;
use crate::authentication::enums::user_status::UserStatus;
use crate::authentication::enums::user_type::UserType;
use crate::authentication::model::subject::Subject;
#[derive(Clone, Serialize, Deserialize)]
pub struct User {
username: CheetahString,
password: Option<CheetahString>,
#[serde(rename = "userType")]
user_type: Option<UserType>,
#[serde(rename = "userStatus")]
user_status: Option<UserStatus>,
#[serde(skip)]
subject_key_cache: OnceLock<String>,
}
impl fmt::Debug for User {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("User")
.field("username", &self.username)
.field("password", &self.password.as_ref().map(|_| "<redacted>"))
.field("user_type", &self.user_type)
.field("user_status", &self.user_status)
.finish_non_exhaustive()
}
}
impl User {
pub fn of(username: impl Into<CheetahString>) -> Self {
User {
username: username.into(),
password: None,
user_type: None,
user_status: None,
subject_key_cache: OnceLock::new(),
}
}
pub fn of_with_password(username: impl Into<CheetahString>, password: impl Into<CheetahString>) -> Self {
User {
username: username.into(),
password: Some(password.into()),
user_type: None,
user_status: None,
subject_key_cache: OnceLock::new(),
}
}
pub fn of_with_type(
username: impl Into<CheetahString>,
password: impl Into<CheetahString>,
user_type: UserType,
) -> Self {
User {
username: username.into(),
password: Some(password.into()),
user_type: Some(user_type),
user_status: None,
subject_key_cache: OnceLock::new(),
}
}
}
impl User {
pub fn username(&self) -> &CheetahString {
&self.username
}
pub fn password(&self) -> Option<&CheetahString> {
self.password.as_ref()
}
pub fn user_type(&self) -> Option<UserType> {
self.user_type
}
pub fn user_status(&self) -> Option<UserStatus> {
self.user_status
}
pub fn set_user_status(&mut self, status: UserStatus) {
self.user_status = Some(status);
}
pub fn set_password(&mut self, password: impl Into<CheetahString>) {
self.password = Some(password.into());
}
pub fn set_user_type(&mut self, user_type: UserType) {
self.user_type = Some(user_type);
}
pub fn username_from_subject_key(subject_key: &str) -> &str {
subject_key
.split_once(':')
.map(|(_, username)| username)
.unwrap_or(subject_key)
}
}
impl Subject for User {
fn subject_key(&self) -> &str {
self.subject_key_cache
.get_or_init(|| format!("{}:{}", SubjectType::User.name(), self.username))
.as_str()
}
fn subject_type(&self) -> SubjectType {
SubjectType::User
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_of() {
let username = "test_user";
let user = User::of(username);
assert_eq!(user.username(), &CheetahString::from(username));
assert!(user.password().is_none());
assert!(user.user_type().is_none());
assert!(user.user_status().is_none());
}
#[test]
fn test_user_of_with_password() {
let username = "test_user";
let password = "test_password";
let user = User::of_with_password(username, password);
assert_eq!(user.username(), &CheetahString::from(username));
assert_eq!(user.password(), Some(&CheetahString::from(password)));
assert!(user.user_type().is_none());
assert!(user.user_status().is_none());
}
#[test]
fn test_user_of_with_type() {
let username = "test_user";
let password = "test_password";
let user_type = UserType::Normal;
let user = User::of_with_type(username, password, user_type);
assert_eq!(user.username(), &CheetahString::from(username));
assert_eq!(user.password(), Some(&CheetahString::from(password)));
assert_eq!(user.user_type(), Some(user_type));
assert!(user.user_status().is_none());
}
#[test]
fn test_user_set_user_status() {
let mut user = User::of("test_user");
user.set_user_status(UserStatus::Enable);
assert_eq!(user.user_status(), Some(UserStatus::Enable));
}
#[test]
fn test_user_subject_trait() {
let username = "test_user";
let user = User::of(username);
assert_eq!(user.subject_key(), "User:test_user");
assert_eq!(user.subject_type(), SubjectType::User);
}
#[test]
fn user_debug_redacts_password() {
let user = User::of_with_password("alice", "top-secret-password");
let debug = format!("{user:?}");
assert!(debug.contains("alice"));
assert!(debug.contains("<redacted>"));
assert!(!debug.contains("top-secret-password"));
}
}