mtjp9_rs_auth0_client/users/
create_user.rs1use crate::{
33 domain::Domain,
34 error::{Auth0Error, Result},
35 token::BearerToken,
36};
37use reqwest::Client;
38use serde::{Deserialize, Serialize};
39use serde_json::Value;
40
41#[derive(Debug, Default, Serialize, Deserialize)]
42pub struct CreateUserRequest {
43 pub email: String,
45
46 pub connection: String,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub password: Option<String>,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub given_name: Option<String>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub family_name: Option<String>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub name: Option<String>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub nickname: Option<String>,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub picture: Option<String>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub user_id: Option<String>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub email_verified: Option<bool>,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub phone_number: Option<String>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub phone_verified: Option<bool>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub user_metadata: Option<Value>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub app_metadata: Option<Value>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub blocked: Option<bool>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub verify_email: Option<bool>,
104}
105
106impl CreateUserRequest {
107 pub fn builder() -> CreateUserRequestBuilder {
108 CreateUserRequestBuilder::default()
109 }
110}
111
112#[derive(Default)]
113pub struct CreateUserRequestBuilder {
114 email: Option<String>,
115 connection: Option<String>,
116 password: Option<String>,
117 given_name: Option<String>,
118 family_name: Option<String>,
119 name: Option<String>,
120 nickname: Option<String>,
121 picture: Option<String>,
122 user_id: Option<String>,
123 email_verified: Option<bool>,
124 phone_number: Option<String>,
125 phone_verified: Option<bool>,
126 user_metadata: Option<Value>,
127 app_metadata: Option<Value>,
128 blocked: Option<bool>,
129 verify_email: Option<bool>,
130}
131
132impl CreateUserRequestBuilder {
133 pub fn email(mut self, email: impl Into<String>) -> Self {
134 self.email = Some(email.into());
135 self
136 }
137
138 pub fn connection(mut self, connection: impl Into<String>) -> Self {
139 self.connection = Some(connection.into());
140 self
141 }
142
143 pub fn password(mut self, password: impl Into<String>) -> Self {
144 self.password = Some(password.into());
145 self
146 }
147
148 pub fn given_name(mut self, given_name: impl Into<String>) -> Self {
149 self.given_name = Some(given_name.into());
150 self
151 }
152
153 pub fn family_name(mut self, family_name: impl Into<String>) -> Self {
154 self.family_name = Some(family_name.into());
155 self
156 }
157
158 pub fn name(mut self, name: impl Into<String>) -> Self {
159 self.name = Some(name.into());
160 self
161 }
162
163 pub fn nickname(mut self, nickname: impl Into<String>) -> Self {
164 self.nickname = Some(nickname.into());
165 self
166 }
167
168 pub fn picture(mut self, picture: impl Into<String>) -> Self {
169 self.picture = Some(picture.into());
170 self
171 }
172
173 pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
174 self.user_id = Some(user_id.into());
175 self
176 }
177
178 pub fn email_verified(mut self, verified: bool) -> Self {
179 self.email_verified = Some(verified);
180 self
181 }
182
183 pub fn phone_number(mut self, phone_number: impl Into<String>) -> Self {
184 self.phone_number = Some(phone_number.into());
185 self
186 }
187
188 pub fn phone_verified(mut self, verified: bool) -> Self {
189 self.phone_verified = Some(verified);
190 self
191 }
192
193 pub fn user_metadata(mut self, metadata: Value) -> Self {
194 self.user_metadata = Some(metadata);
195 self
196 }
197
198 pub fn app_metadata(mut self, metadata: Value) -> Self {
199 self.app_metadata = Some(metadata);
200 self
201 }
202
203 pub fn blocked(mut self, blocked: bool) -> Self {
204 self.blocked = Some(blocked);
205 self
206 }
207
208 pub fn verify_email(mut self, verify_email: bool) -> Self {
209 self.verify_email = Some(verify_email);
210 self
211 }
212
213 pub fn build(self) -> Result<CreateUserRequest> {
214 let email = self
215 .email
216 .ok_or_else(|| Auth0Error::InvalidRequest("Email is required".to_string()))?;
217
218 let connection = self
219 .connection
220 .ok_or_else(|| Auth0Error::InvalidRequest("Connection is required".to_string()))?;
221
222 if !email.contains('@') {
223 return Err(Auth0Error::InvalidRequest(
224 "Invalid email format".to_string(),
225 ));
226 }
227
228 Ok(CreateUserRequest {
229 email,
230 connection,
231 password: self.password,
232 given_name: self.given_name,
233 family_name: self.family_name,
234 name: self.name,
235 nickname: self.nickname,
236 picture: self.picture,
237 user_id: self.user_id,
238 email_verified: self.email_verified,
239 phone_number: self.phone_number,
240 phone_verified: self.phone_verified,
241 user_metadata: self.user_metadata,
242 app_metadata: self.app_metadata,
243 blocked: self.blocked,
244 verify_email: self.verify_email,
245 })
246 }
247}
248
249#[derive(Debug, Deserialize)]
250pub struct CreateUserResponse {
251 pub user_id: String,
253
254 pub email: String,
256
257 pub email_verified: bool,
259
260 pub given_name: Option<String>,
262
263 pub family_name: Option<String>,
265
266 pub name: Option<String>,
268
269 pub nickname: Option<String>,
271
272 pub picture: Option<String>,
274
275 pub phone_number: Option<String>,
277
278 pub phone_verified: Option<bool>,
280
281 pub user_metadata: Option<Value>,
283
284 pub app_metadata: Option<Value>,
286
287 pub blocked: Option<bool>,
289
290 pub created_at: String,
292
293 pub updated_at: String,
295
296 pub identities: Vec<Identity>,
298}
299
300#[derive(Debug, Deserialize)]
301pub struct Identity {
302 pub connection: String,
304
305 pub user_id: String,
307
308 pub provider: String,
310
311 pub is_social: Option<bool>,
313}
314
315pub async fn create_user(
321 domain: &Domain,
322 token: &BearerToken,
323 request: CreateUserRequest,
324) -> Result<CreateUserResponse> {
325 let url = domain.to_url("/api/v2/users");
326
327 let resp = Client::new()
328 .post(url)
329 .bearer_auth(token.as_str())
330 .header("Content-Type", "application/json")
331 .json(&request)
332 .send()
333 .await?;
334
335 if resp.status().is_success() {
336 let user = resp.json::<CreateUserResponse>().await?;
337 Ok(user)
338 } else {
339 Err(Auth0Error::from_response(resp).await)
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use serde_json::json;
351
352 #[test]
367 fn test_create_user_request_builder_valid() {
368 let req = CreateUserRequest::builder()
369 .email("test@example.com")
370 .connection("Username-Password-Authentication")
371 .password("SecurePassword123!")
372 .given_name("John")
373 .family_name("Doe")
374 .name("John Doe")
375 .nickname("johndoe")
376 .picture("https://example.com/avatar.jpg")
377 .email_verified(true)
378 .phone_number("+1234567890")
379 .phone_verified(false)
380 .user_metadata(json!({"favorite_color": "blue"}))
381 .app_metadata(json!({"roles": ["user"]}))
382 .blocked(false)
383 .verify_email(true)
384 .build();
385
386 assert!(req.is_ok());
387 let req = req.unwrap();
388 assert_eq!(req.email, "test@example.com");
389 assert_eq!(req.connection, "Username-Password-Authentication");
390 assert_eq!(req.password, Some("SecurePassword123!".to_string()));
391 assert_eq!(req.given_name, Some("John".to_string()));
392 assert_eq!(req.family_name, Some("Doe".to_string()));
393 assert_eq!(req.name, Some("John Doe".to_string()));
394 assert_eq!(req.nickname, Some("johndoe".to_string()));
395 assert_eq!(
396 req.picture,
397 Some("https://example.com/avatar.jpg".to_string())
398 );
399 assert_eq!(req.email_verified, Some(true));
400 assert_eq!(req.phone_number, Some("+1234567890".to_string()));
401 assert_eq!(req.phone_verified, Some(false));
402 assert_eq!(req.user_metadata, Some(json!({"favorite_color": "blue"})));
403 assert_eq!(req.app_metadata, Some(json!({"roles": ["user"]})));
404 assert_eq!(req.blocked, Some(false));
405 assert_eq!(req.verify_email, Some(true));
406 }
407
408 #[test]
409 fn test_create_user_request_builder_minimal() {
410 let req = CreateUserRequest::builder()
411 .email("test@example.com")
412 .connection("Username-Password-Authentication")
413 .build();
414
415 assert!(req.is_ok());
416 let req = req.unwrap();
417 assert_eq!(req.email, "test@example.com");
418 assert_eq!(req.connection, "Username-Password-Authentication");
419 assert_eq!(req.password, None);
420 assert_eq!(req.given_name, None);
421 assert_eq!(req.family_name, None);
422 }
423
424 #[test]
425 fn test_create_user_request_builder_missing_email() {
426 let req = CreateUserRequest::builder()
427 .connection("Username-Password-Authentication")
428 .build();
429
430 assert!(req.is_err());
431 match req {
432 Err(Auth0Error::InvalidRequest(msg)) => {
433 assert_eq!(msg, "Email is required");
434 }
435 _ => panic!("Expected InvalidRequest error"),
436 }
437 }
438
439 #[test]
440 fn test_create_user_request_builder_missing_connection() {
441 let req = CreateUserRequest::builder()
442 .email("test@example.com")
443 .build();
444
445 assert!(req.is_err());
446 match req {
447 Err(Auth0Error::InvalidRequest(msg)) => {
448 assert_eq!(msg, "Connection is required");
449 }
450 _ => panic!("Expected InvalidRequest error"),
451 }
452 }
453
454 #[test]
455 fn test_create_user_request_builder_invalid_email() {
456 let req = CreateUserRequest::builder()
457 .email("invalid_email")
458 .connection("Username-Password-Authentication")
459 .build();
460
461 assert!(req.is_err());
462 match req {
463 Err(Auth0Error::InvalidRequest(msg)) => {
464 assert_eq!(msg, "Invalid email format");
465 }
466 _ => panic!("Expected InvalidRequest error"),
467 }
468 }
469}