uptrakit_web_api_types/
profile.rs1use serde::{Deserialize, Serialize};
2use uptrakit_shared_types::SecretString;
3
4use crate::validation::{Validate, ValidationError};
5
6#[derive(Serialize, Deserialize)]
7#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
8pub struct UpdateProfileRequest {
9 #[cfg_attr(feature = "openapi", schema(example = "Jane"))]
10 pub first_name: String,
11 #[cfg_attr(feature = "openapi", schema(example = "Doe"))]
12 pub last_name: String,
13}
14
15impl Validate for UpdateProfileRequest {
16 fn validate(&self) -> Result<(), ValidationError> {
17 if self.first_name.is_empty() {
18 return Err(ValidationError {
19 field: "first_name",
20 message: "first_name must not be empty".to_string(),
21 });
22 }
23 if self.first_name.len() > 100 {
24 return Err(ValidationError {
25 field: "first_name",
26 message: "first_name must not exceed 100 characters".to_string(),
27 });
28 }
29 if self.last_name.len() > 100 {
30 return Err(ValidationError {
31 field: "last_name",
32 message: "last_name must not exceed 100 characters".to_string(),
33 });
34 }
35 Ok(())
36 }
37}
38
39#[derive(Serialize, Deserialize)]
40#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
41pub struct InitiateEmailChangeRequest {
42 #[cfg_attr(feature = "openapi", schema(example = "currentpassword123"))]
43 pub current_password: SecretString,
44 #[cfg_attr(feature = "openapi", schema(example = "newemail@example.com"))]
45 pub new_email: String,
46}
47
48impl Validate for InitiateEmailChangeRequest {
49 fn validate(&self) -> Result<(), ValidationError> {
50 if !self.new_email.contains('@') {
51 return Err(ValidationError {
52 field: "new_email",
53 message: "new_email must contain '@'".to_string(),
54 });
55 }
56 if self.new_email.len() > 254 {
57 return Err(ValidationError {
58 field: "new_email",
59 message: "new_email must not exceed 254 characters".to_string(),
60 });
61 }
62 Ok(())
63 }
64}
65
66#[derive(Serialize, Deserialize)]
67#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
68pub struct ChangePasswordRequest {
69 #[cfg_attr(feature = "openapi", schema(example = "currentpassword123"))]
70 pub current_password: SecretString,
71 #[cfg_attr(
72 feature = "openapi",
73 schema(example = "newpassword123", min_length = 8)
74 )]
75 pub new_password: SecretString,
76}
77
78impl Validate for ChangePasswordRequest {
79 fn validate(&self) -> Result<(), ValidationError> {
80 let len = self.new_password.expose_secret().len();
81 if len < 8 {
82 return Err(ValidationError {
83 field: "new_password",
84 message: "new_password must be at least 8 characters".to_string(),
85 });
86 }
87 if len > 128 {
88 return Err(ValidationError {
89 field: "new_password",
90 message: "new_password must not exceed 128 characters".to_string(),
91 });
92 }
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn valid_update_profile() -> UpdateProfileRequest {
104 UpdateProfileRequest {
105 first_name: "Jane".to_string(),
106 last_name: "Doe".to_string(),
107 }
108 }
109
110 #[test]
111 fn update_profile_valid() {
112 assert!(valid_update_profile().validate().is_ok());
113 }
114
115 #[test]
116 fn update_profile_empty_first_name_fails() {
117 let mut req = valid_update_profile();
118 req.first_name = String::new();
119 let err = req.validate().unwrap_err();
120 assert_eq!(err.field, "first_name");
121 }
122
123 #[test]
124 fn update_profile_first_name_too_long() {
125 let mut req = valid_update_profile();
126 req.first_name = "a".repeat(101);
127 let err = req.validate().unwrap_err();
128 assert_eq!(err.field, "first_name");
129 }
130
131 #[test]
132 fn update_profile_last_name_too_long() {
133 let mut req = valid_update_profile();
134 req.last_name = "a".repeat(101);
135 let err = req.validate().unwrap_err();
136 assert_eq!(err.field, "last_name");
137 }
138
139 #[test]
140 fn update_profile_empty_last_name_ok() {
141 let req = UpdateProfileRequest {
142 first_name: "Jane".to_string(),
143 last_name: String::new(),
144 };
145 assert!(req.validate().is_ok());
146 }
147
148 fn valid_email_change() -> InitiateEmailChangeRequest {
151 InitiateEmailChangeRequest {
152 current_password: SecretString::new("currentpassword123"),
153 new_email: "newemail@example.com".to_string(),
154 }
155 }
156
157 #[test]
158 fn initiate_email_change_valid() {
159 assert!(valid_email_change().validate().is_ok());
160 }
161
162 #[test]
163 fn initiate_email_change_missing_at_fails() {
164 let mut req = valid_email_change();
165 req.new_email = "notanemail".to_string();
166 let err = req.validate().unwrap_err();
167 assert_eq!(err.field, "new_email");
168 }
169
170 #[test]
171 fn initiate_email_change_email_too_long() {
172 let mut req = valid_email_change();
173 req.new_email = format!("{}@x.com", "a".repeat(250));
174 let err = req.validate().unwrap_err();
175 assert_eq!(err.field, "new_email");
176 }
177
178 fn valid_change_password() -> ChangePasswordRequest {
181 ChangePasswordRequest {
182 current_password: SecretString::new("oldpassword123"),
183 new_password: SecretString::new("newpassword123"),
184 }
185 }
186
187 #[test]
188 fn change_password_valid() {
189 assert!(valid_change_password().validate().is_ok());
190 }
191
192 #[test]
193 fn change_password_too_short_fails() {
194 let mut req = valid_change_password();
195 req.new_password = SecretString::new("short");
196 let err = req.validate().unwrap_err();
197 assert_eq!(err.field, "new_password");
198 }
199
200 #[test]
201 fn change_password_too_long_fails() {
202 let mut req = valid_change_password();
203 req.new_password = SecretString::new("a".repeat(129));
204 let err = req.validate().unwrap_err();
205 assert_eq!(err.field, "new_password");
206 }
207
208 #[test]
209 fn change_password_exactly_8_chars_ok() {
210 let req = ChangePasswordRequest {
211 current_password: SecretString::new("oldpass"),
212 new_password: SecretString::new("12345678"),
213 };
214 assert!(req.validate().is_ok());
215 }
216
217 #[test]
218 fn change_password_exactly_128_chars_ok() {
219 let req = ChangePasswordRequest {
220 current_password: SecretString::new("oldpass"),
221 new_password: SecretString::new("a".repeat(128)),
222 };
223 assert!(req.validate().is_ok());
224 }
225}