use crate::utils::{WireMap, empty_string_is_none, validate_name};
use crate::{Client, Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub expiration: i64,
pub pod: i8,
pub role: String,
pub username: String,
#[serde(
deserialize_with = "empty_string_is_none",
skip_serializing_if = "Option::is_none"
)]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub folder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lab: Option<String>,
#[serde(
deserialize_with = "empty_string_is_none",
skip_serializing_if = "Option::is_none"
)]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pexpiration: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session: Option<u64>,
}
pub struct UsersClient {
client: Client,
}
impl UsersClient {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
pub async fn list(&self) -> Result<HashMap<String, User>> {
Ok(self
.client
.get::<WireMap<String, User>>("users/")
.await?
.into_data()?
.0)
}
pub async fn add(&self, params: AddUserRequest) -> Result<UserClient> {
self.client
.post::<(), AddUserRequest>("users", ¶ms)
.await?;
self.user(¶ms.username)
}
fn user(&self, username: impl Into<String>) -> Result<UserClient> {
UserClient::new(self.client.clone(), username)
}
}
pub(crate) struct UserName(String);
impl std::fmt::Display for UserName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.as_str())
}
}
impl UserName {
pub(crate) fn new(username: impl Into<String>) -> Result<Self> {
let username = username.into();
if username.is_empty() {
return Err(Error::Client("Username cannot be empty".to_string()));
}
if !validate_name(&username, &['-', '_']) {
return Err(Error::Client(format!(
"Invalid username {}, must contain letters, digits, '-' and '_'.",
username
)));
}
Ok(UserName(username))
}
}
pub struct UserClient {
client: Client,
username: UserName,
}
impl UserClient {
pub(crate) fn new(client: Client, username: impl Into<String>) -> Result<Self> {
Ok(Self {
client,
username: UserName::new(username)?,
})
}
pub async fn get(&self) -> Result<User> {
self.client
.get(&format!("users/{}", self.username))
.await?
.into_data()
}
pub async fn edit(&self, params: EditUserRequest) -> Result<()> {
self.get().await?;
self.client
.put::<(), EditUserRequest>(&format!("users/{}", self.username), ¶ms)
.await?;
Ok(())
}
pub async fn delete(self) -> Result<()> {
self.get().await?;
self.client
.delete::<()>(&format!("users/{}", self.username))
.await?;
Ok(())
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AddUserRequest {
username: String,
password: String,
role: String,
expiration: i64,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
}
impl AddUserRequest {
pub fn new(username: impl Into<String>, password: impl Into<String>) -> Result<Self> {
let username = UserName::new(username.into())?;
let password = password.into();
if password.is_empty() {
return Err(Error::Client("Password cannot be empty".to_string()));
}
Ok(Self {
username: username.to_string(),
password,
expiration: -1,
role: "user".to_string(),
..Default::default()
})
}
pub fn email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
pub fn expiration(mut self, expiration: i64) -> Self {
self.expiration = expiration;
self
}
pub fn name(mut self, name: impl Into<String>) -> Result<Self> {
let name = name.into();
if !validate_name(&name, &['-', '_', ' ']) {
return Err(Error::Client(format!(
"Invalid name '{}', must contain letters, digits, spaces, `-` and `_`.",
name,
)));
}
self.name = Some(name);
Ok(self)
}
pub fn role(mut self, role: impl Into<String>) -> Self {
self.role = role.into();
self
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct EditUserRequest {
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
expiration: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
role: Option<String>,
}
impl EditUserRequest {
pub fn new() -> Self {
Default::default()
}
pub fn password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
pub fn role(mut self, role: impl Into<String>) -> Self {
self.role = Some(role.into());
self
}
pub fn email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
pub fn expiration(mut self, expiration: i64) -> Self {
self.expiration = Some(expiration);
self
}
pub fn name(mut self, name: impl Into<String>) -> Result<Self> {
let name = name.into();
if !validate_name(&name, &['-', '_', ' ']) {
return Err(Error::Client(format!(
"Invalid name '{}', must contain letters, digits, spaces, '-' and '_'.",
name,
)));
}
self.name = Some(name);
Ok(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_username() {
let result = UserName::new("test");
assert!(result.is_ok());
}
#[test]
fn invalid_username() {
let result = UserName::new("test user");
assert!(result.is_err());
}
#[test]
fn add_user_defaults() {
let req = AddUserRequest::new("test", "test").unwrap();
assert_eq!(req.expiration, -1);
assert_eq!(req.role, "user".to_string());
assert_eq!(req.email, None);
assert_eq!(req.name, None);
}
#[test]
fn add_user_invalid() {
let req = AddUserRequest::new("test", "");
assert!(req.is_err());
let req = AddUserRequest::new("", "test");
assert!(req.is_err());
let req = AddUserRequest::new("test", "test")
.unwrap()
.name("Test+User");
assert!(req.is_err());
}
#[test]
fn add_user_setters() {
let req = AddUserRequest::new("test", "test")
.unwrap()
.expiration(1792081391)
.role("admin")
.email("test@test.com")
.name("Test User")
.unwrap();
assert_eq!(req.expiration, 1792081391);
assert_eq!(req.role, "admin".to_string());
assert_eq!(req.email, Some("test@test.com".to_string()));
assert_eq!(req.name, Some("Test User".to_string()));
}
#[test]
fn edit_user_defaults() {
let req = EditUserRequest::new();
assert_eq!(req.password, None);
assert_eq!(req.expiration, None);
assert_eq!(req.role, None);
assert_eq!(req.email, None);
assert_eq!(req.name, None);
}
#[test]
fn edit_user_invalid() {
let req = EditUserRequest::new().name("Test+User");
assert!(req.is_err());
}
#[test]
fn edit_user_setters() {
let req = EditUserRequest::new()
.expiration(1792081391)
.role("admin")
.email("test@test.com")
.name("Test User")
.unwrap();
assert_eq!(req.expiration, Some(1792081391));
assert_eq!(req.role, Some("admin".to_string()));
assert_eq!(req.email, Some("test@test.com".to_string()));
assert_eq!(req.name, Some("Test User".to_string()));
}
}