Skip to main content

uptrakit_web_api_types/
users.rs

1use crate::validation::{Validate, ValidationError};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// A user with their assigned roles.
6#[derive(Serialize, Deserialize, Clone)]
7#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
8pub struct UserWithRolesResponse {
9    pub id: Uuid,
10    pub email: String,
11    pub first_name: String,
12    pub last_name: String,
13    pub is_active: bool,
14    pub roles: Vec<UserRoleSummary>,
15}
16
17/// Summary of a role assigned to a user.
18#[derive(Serialize, Deserialize, Clone)]
19#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
20pub struct UserRoleSummary {
21    pub id: Uuid,
22    pub name: String,
23}
24
25/// Request to replace a user's roles.
26#[derive(Serialize, Deserialize)]
27#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
28pub struct UpdateUserRolesRequest {
29    /// List of role IDs to assign. Replaces all existing role assignments.
30    pub role_ids: Vec<Uuid>,
31}
32
33impl Validate for UpdateUserRolesRequest {
34    fn validate(&self) -> Result<(), ValidationError> {
35        if self.role_ids.is_empty() {
36            return Err(ValidationError {
37                field: "role_ids",
38                message: "at least one role must be assigned".to_string(),
39            });
40        }
41        if self.role_ids.len() > 20 {
42            return Err(ValidationError {
43                field: "role_ids",
44                message: "cannot assign more than 20 roles".to_string(),
45            });
46        }
47        Ok(())
48    }
49}
50
51/// Request to activate or deactivate a user.
52#[derive(Serialize, Deserialize)]
53#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
54pub struct UpdateUserActiveRequest {
55    pub is_active: bool,
56}
57
58impl Validate for UpdateUserActiveRequest {
59    fn validate(&self) -> Result<(), ValidationError> {
60        // No format/length invariants beyond field types; capability/existence checks are handler-side.
61        Ok(())
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    #![expect(
68        clippy::assertions_on_result_states,
69        reason = "test assertions — is_ok/is_err provides readable failure messages"
70    )]
71    use super::*;
72
73    #[test]
74    fn update_user_active_validate_is_ok() {
75        assert!(
76            UpdateUserActiveRequest { is_active: false }
77                .validate()
78                .is_ok()
79        );
80    }
81}