use crate::error::VecboostError;
use crate::i18n;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
pub struct RefreshTokenRequest {
pub refresh_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
pub struct AuthResponse {
pub token: String,
pub token_type: String,
pub expires_in: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub username: String,
pub role: String,
pub permissions: Vec<String>,
}
pub fn validate_username_format(username: &str) -> Result<(), VecboostError> {
if username.len() < 3 || username.len() > 32 {
return Err(VecboostError::ValidationError(i18n::tr(
"auth-username-length",
)));
}
if !username
.chars()
.next()
.map(|c| c.is_ascii_alphabetic())
.unwrap_or(false)
{
return Err(VecboostError::ValidationError(i18n::tr(
"auth-username-start",
)));
}
if !username
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
{
return Err(VecboostError::ValidationError(i18n::tr(
"auth-username-charset",
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_login_request_serialization() {
let req = LoginRequest {
username: "alice".to_string(),
password: "secret".to_string(),
};
let json = serde_json::to_string(&req).unwrap();
let deserialized: LoginRequest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.username, "alice");
assert_eq!(deserialized.password, "secret");
}
#[test]
fn test_auth_response_serialization() {
let resp = AuthResponse {
token: "tok123".to_string(),
token_type: "Bearer".to_string(),
expires_in: 3600,
};
let json = serde_json::to_string(&resp).unwrap();
let deserialized: AuthResponse = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.token, "tok123");
assert_eq!(deserialized.token_type, "Bearer");
assert_eq!(deserialized.expires_in, 3600);
}
#[test]
fn test_refresh_token_request_serialization() {
let req = RefreshTokenRequest {
refresh_token: "refresh_tok".to_string(),
};
let json = serde_json::to_string(&req).unwrap();
let deserialized: RefreshTokenRequest = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.refresh_token, "refresh_tok");
}
#[test]
fn test_user_serialization() {
let user = User {
username: "bob".to_string(),
role: "admin".to_string(),
permissions: vec!["read".to_string(), "write".to_string()],
};
let json = serde_json::to_string(&user).unwrap();
let deserialized: User = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.username, "bob");
assert_eq!(deserialized.role, "admin");
assert_eq!(deserialized.permissions.len(), 2);
}
#[test]
fn test_validate_username_format_valid() {
assert!(validate_username_format("alice").is_ok());
assert!(validate_username_format("Bob123").is_ok());
assert!(validate_username_format("a-b").is_ok());
assert!(validate_username_format("user_name").is_ok());
}
#[test]
fn test_validate_username_format_too_short() {
assert!(validate_username_format("ab").is_err());
assert!(validate_username_format("").is_err());
}
#[test]
fn test_validate_username_format_too_long() {
let long_name = "a".repeat(33);
assert!(validate_username_format(&long_name).is_err());
}
#[test]
fn test_validate_username_format_must_start_with_letter() {
assert!(validate_username_format("1user").is_err());
assert!(validate_username_format("_user").is_err());
assert!(validate_username_format("-user").is_err());
}
#[test]
fn test_validate_username_format_invalid_chars() {
assert!(validate_username_format("user name").is_err());
assert!(validate_username_format("user@name").is_err());
assert!(validate_username_format("user.name").is_err());
}
}