etwin_core/
user.rs

1use crate::core::{FinitePeriod, Instant};
2use crate::dinoparc::{DinoparcPassword, DinoparcServer, DinoparcUserId, DinoparcUsername};
3use crate::email::EmailAddress;
4use crate::hammerfest::{
5  HammerfestPassword, HammerfestServer, HammerfestSessionKey, HammerfestUserId, HammerfestUsername,
6};
7use crate::link::VersionedLinks;
8use crate::oauth::RfcOauthAccessToken;
9use crate::password::{Password, PasswordHash};
10use crate::twinoid::TwinoidUserId;
11use crate::types::AnyError;
12use async_trait::async_trait;
13use auto_impl::auto_impl;
14use chrono::Duration;
15#[cfg(feature = "serde")]
16use etwin_serde_tools::{deserialize_explicit_option, deserialize_nested_option, Deserialize, Serialize};
17use once_cell::sync::Lazy;
18use std::error::Error;
19
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
22#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct CompleteSimpleUser {
24  pub id: UserId,
25  pub created_at: Instant,
26  pub display_name: UserDisplayNameVersions,
27  pub is_administrator: bool,
28  #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_explicit_option"))]
29  pub username: Option<Username>,
30  #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_explicit_option"))]
31  pub email_address: Option<EmailAddress>,
32}
33
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
36#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct CompleteUser {
38  pub id: UserId,
39  pub created_at: Instant,
40  pub display_name: UserDisplayNameVersions,
41  pub is_administrator: bool,
42  pub links: VersionedLinks,
43  pub username: Option<Username>,
44  pub email_address: Option<EmailAddress>,
45  pub has_password: bool,
46}
47
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct CreateUserOptions {
51  pub display_name: UserDisplayName,
52  pub email: Option<EmailAddress>,
53  pub username: Option<Username>,
54  pub password: Option<PasswordHash>,
55}
56
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct UpdateUserOptions {
60  pub r#ref: UserIdRef,
61  pub patch: UpdateUserPatch,
62}
63
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub struct UpdateUserPatch {
67  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
68  pub display_name: Option<UserDisplayName>,
69  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
70  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
71  pub username: Option<Option<Username>>,
72  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
73  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
74  pub password: Option<Option<Password>>,
75}
76
77#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub struct RawUpdateUserOptions {
80  pub actor: UserIdRef,
81  pub r#ref: UserIdRef,
82  pub patch: RawUpdateUserPatch,
83}
84
85#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct RawUpdateUserPatch {
88  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
89  pub display_name: Option<UserDisplayName>,
90  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
91  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
92  pub username: Option<Option<Username>>,
93  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
94  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
95  pub password: Option<Option<PasswordHash>>,
96}
97
98#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "type"))]
99#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
100pub enum UserFields {
101  CompleteIfSelf { self_user_id: UserId },
102  Complete,
103  Default,
104  Short,
105}
106
107#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
108#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
109pub struct GetUserOptions {
110  pub r#ref: UserRef,
111  pub fields: UserFields,
112  pub time: Option<Instant>,
113}
114
115#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
116#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
117pub struct GetShortUserOptions {
118  pub r#ref: UserRef,
119  pub time: Option<Instant>,
120}
121
122impl From<UserRef> for GetShortUserOptions {
123  fn from(r#ref: UserRef) -> Self {
124    Self { r#ref, time: None }
125  }
126}
127
128impl From<UserIdRef> for GetShortUserOptions {
129  fn from(r: UserIdRef) -> Self {
130    let r: UserRef = r.into();
131    r.into()
132  }
133}
134
135impl From<UserId> for GetShortUserOptions {
136  fn from(id: UserId) -> Self {
137    let r: UserRef = id.into();
138    r.into()
139  }
140}
141
142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
143#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
144pub enum GetUserResult {
145  Complete(CompleteUser),
146  Default(User),
147}
148
149#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
150#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
151pub enum RawGetUserResult {
152  Complete(CompleteSimpleUser),
153  Default(SimpleUser),
154  Short(ShortUser),
155}
156
157impl RawGetUserResult {
158  pub fn id(&self) -> UserId {
159    match self {
160      Self::Complete(u) => u.id,
161      Self::Default(u) => u.id,
162      Self::Short(u) => u.id,
163    }
164  }
165}
166
167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
168#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
169#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
170pub struct ShortUser {
171  pub id: UserId,
172  pub display_name: UserDisplayNameVersions,
173}
174
175impl ShortUser {
176  pub const fn as_ref(&self) -> UserIdRef {
177    UserIdRef { id: self.id }
178  }
179}
180
181impl From<SimpleUser> for ShortUser {
182  fn from(user: SimpleUser) -> Self {
183    Self {
184      id: user.id,
185      display_name: user.display_name,
186    }
187  }
188}
189
190impl From<CompleteSimpleUser> for ShortUser {
191  fn from(user: CompleteSimpleUser) -> Self {
192    Self {
193      id: user.id,
194      display_name: user.display_name,
195    }
196  }
197}
198
199impl From<User> for ShortUser {
200  fn from(user: User) -> Self {
201    Self {
202      id: user.id,
203      display_name: user.display_name,
204    }
205  }
206}
207
208#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
209#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
210#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
211pub struct ShortUserWithPassword {
212  pub id: UserId,
213  pub display_name: UserDisplayNameVersions,
214  pub password: Option<PasswordHash>,
215}
216
217#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
218#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
219#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
220pub struct SimpleUser {
221  pub id: UserId,
222  pub created_at: Instant,
223  pub display_name: UserDisplayNameVersions,
224  pub is_administrator: bool,
225}
226
227impl From<CompleteSimpleUser> for SimpleUser {
228  fn from(user: CompleteSimpleUser) -> Self {
229    Self {
230      id: user.id,
231      created_at: user.created_at,
232      display_name: user.display_name,
233      is_administrator: user.is_administrator,
234    }
235  }
236}
237
238#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
239#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
240#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
241pub struct User {
242  pub id: UserId,
243  pub created_at: Instant,
244  pub display_name: UserDisplayNameVersions,
245  pub is_administrator: bool,
246  pub links: VersionedLinks,
247}
248
249impl User {
250  pub fn to_short(&self) -> ShortUser {
251    // TODO: Avoid `.clone()`
252    ShortUser::from(self.clone())
253  }
254}
255
256declare_new_string! {
257  pub struct UserDisplayName(String);
258  pub type ParseError = UserDisplayNameParseError;
259  const PATTERN = r"^[\p{Letter}_ ()][\p{Letter}_ ()0-9]*$";
260  const SQL_NAME = "user_display_name";
261}
262
263#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
264#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
265pub struct UserDisplayNameVersion {
266  pub value: UserDisplayName,
267}
268
269#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
270#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
271pub struct UserDisplayNameVersions {
272  pub current: UserDisplayNameVersion,
273}
274
275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "method"))]
276#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum LinkToDinoparcOptions {
278  Credentials(LinkToDinoparcWithCredentialsOptions),
279  Ref(LinkToDinoparcWithRefOptions),
280}
281
282#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
283#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
284pub struct LinkToDinoparcWithCredentialsOptions {
285  pub user_id: UserId,
286  pub dinoparc_server: DinoparcServer,
287  pub dinoparc_username: DinoparcUsername,
288  pub dinoparc_password: DinoparcPassword,
289}
290
291#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
292#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
293pub struct LinkToDinoparcWithRefOptions {
294  pub user_id: UserId,
295  pub dinoparc_server: DinoparcServer,
296  pub dinoparc_user_id: DinoparcUserId,
297}
298
299#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
300#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
301pub struct UnlinkFromDinoparcOptions {
302  pub user_id: UserId,
303  pub dinoparc_server: DinoparcServer,
304  pub dinoparc_user_id: DinoparcUserId,
305}
306
307#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "method"))]
308#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
309pub enum LinkToHammerfestOptions {
310  Credentials(LinkToHammerfestWithCredentialsOptions),
311  SessionKey(LinkToHammerfestWithSessionKeyOptions),
312  Ref(LinkToHammerfestWithRefOptions),
313}
314
315#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
316#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
317pub struct LinkToHammerfestWithCredentialsOptions {
318  pub user_id: UserId,
319  pub hammerfest_server: HammerfestServer,
320  pub hammerfest_username: HammerfestUsername,
321  pub hammerfest_password: HammerfestPassword,
322}
323
324#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
325#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
326pub struct LinkToHammerfestWithSessionKeyOptions {
327  pub user_id: UserId,
328  pub hammerfest_server: HammerfestServer,
329  pub hammerfest_session_key: HammerfestSessionKey,
330}
331
332#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
333#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
334pub struct LinkToHammerfestWithRefOptions {
335  pub user_id: UserId,
336  pub hammerfest_server: HammerfestServer,
337  pub hammerfest_user_id: HammerfestUserId,
338}
339
340#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
341#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
342pub struct UnlinkFromHammerfestOptions {
343  pub user_id: UserId,
344  pub hammerfest_server: HammerfestServer,
345  pub hammerfest_user_id: HammerfestUserId,
346}
347
348#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "method"))]
349#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
350pub enum LinkToTwinoidOptions {
351  Oauth(LinkToTwinoidWithOauthOptions),
352  Ref(LinkToTwinoidWithRefOptions),
353}
354
355#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
356#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
357pub struct LinkToTwinoidWithOauthOptions {
358  pub user_id: UserId,
359  pub access_token: RfcOauthAccessToken,
360}
361
362#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
363#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
364pub struct LinkToTwinoidWithRefOptions {
365  pub user_id: UserId,
366  pub twinoid_user_id: TwinoidUserId,
367}
368
369#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
370#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
371pub struct UnlinkFromTwinoidOptions {
372  pub user_id: UserId,
373  pub twinoid_user_id: TwinoidUserId,
374}
375
376declare_new_uuid! {
377  pub struct UserId(Uuid);
378  pub type ParseError = UserIdParseError;
379  const SQL_NAME = "user_id";
380}
381
382#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
383#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
384#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
385pub struct UserIdRef {
386  pub id: UserId,
387}
388
389impl UserIdRef {
390  pub const fn new(id: UserId) -> Self {
391    Self { id }
392  }
393}
394
395impl From<UserId> for UserIdRef {
396  fn from(id: UserId) -> Self {
397    Self::new(id)
398  }
399}
400
401#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
402#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
403#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
404pub struct UserUsernameRef {
405  pub username: Username,
406}
407
408#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
409#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
410#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
411pub struct UserEmailRef {
412  pub email: EmailAddress,
413}
414
415#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
416#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
417pub enum UserRef {
418  Id(UserIdRef),
419  Username(UserUsernameRef),
420  Email(UserEmailRef),
421}
422
423impl From<UserId> for UserRef {
424  fn from(id: UserId) -> Self {
425    Self::Id(id.into())
426  }
427}
428
429impl From<UserIdRef> for UserRef {
430  fn from(r: UserIdRef) -> Self {
431    Self::Id(r)
432  }
433}
434
435impl From<UserUsernameRef> for UserRef {
436  fn from(r: UserUsernameRef) -> Self {
437    Self::Username(r)
438  }
439}
440
441impl From<UserEmailRef> for UserRef {
442  fn from(r: UserEmailRef) -> Self {
443    Self::Email(r)
444  }
445}
446
447declare_new_string! {
448  pub struct Username(String);
449  pub type ParseError = UsernameParseError;
450  const PATTERN = "^[a-z_][a-z0-9_]{1,31}$";
451  const SQL_NAME = "username";
452}
453
454pub static USERNAME_LOCK_DURATION: Lazy<Duration> = Lazy::new(|| Duration::days(7));
455pub static USER_DISPLAY_NAME_LOCK_DURATION: Lazy<Duration> = Lazy::new(|| Duration::days(30));
456pub static USER_PASSWORD_LOCK_DURATION: Lazy<Duration> = Lazy::new(|| Duration::minutes(10));
457
458#[derive(Debug, thiserror::Error)]
459pub enum RawUpdateUserError {
460  #[error("Failed to find user to update for ref: {:?}", .0)]
461  NotFound(UserIdRef),
462  #[error("Failed to update user {:?}, display_name locked during {:?}, current time is {}", .0, .1, .2)]
463  LockedDisplayName(UserIdRef, FinitePeriod, Instant),
464  #[error("Failed to update user {:?}, username locked during {:?}, current time is {}", .0, .1, .2)]
465  LockedUsername(UserIdRef, FinitePeriod, Instant),
466  #[error("Failed to update user {:?}, password locked during {:?}, current time is {}", .0, .1, .2)]
467  LockedPassword(UserIdRef, FinitePeriod, Instant),
468  #[error(transparent)]
469  Other(AnyError),
470}
471
472impl PartialEq for RawUpdateUserError {
473  fn eq(&self, other: &Self) -> bool {
474    match (self, other) {
475      (RawUpdateUserError::NotFound(l), RawUpdateUserError::NotFound(r)) if l == r => true,
476      (RawUpdateUserError::LockedDisplayName(l0, l1, l2), RawUpdateUserError::LockedDisplayName(r0, r1, r2))
477        if (l0, l1, l2) == (r0, r1, r2) =>
478      {
479        true
480      }
481      (RawUpdateUserError::LockedUsername(l0, l1, l2), RawUpdateUserError::LockedUsername(r0, r1, r2))
482        if (l0, l1, l2) == (r0, r1, r2) =>
483      {
484        true
485      }
486      (RawUpdateUserError::LockedPassword(l0, l1, l2), RawUpdateUserError::LockedPassword(r0, r1, r2))
487        if (l0, l1, l2) == (r0, r1, r2) =>
488      {
489        true
490      }
491      _ => false,
492    }
493  }
494}
495
496impl RawUpdateUserError {
497  pub fn other<E: 'static + Error + Send + Sync>(e: E) -> Self {
498    Self::Other(Box::new(e))
499  }
500}
501
502#[derive(Debug, thiserror::Error)]
503pub enum DeleteUserError {
504  #[error("Failed to find user to delete for ref: {:?}", .0)]
505  NotFound(UserIdRef),
506  #[error(transparent)]
507  Other(AnyError),
508}
509
510impl PartialEq for DeleteUserError {
511  fn eq(&self, other: &Self) -> bool {
512    #[allow(clippy::match_like_matches_macro)]
513    match (self, other) {
514      (DeleteUserError::NotFound(l), DeleteUserError::NotFound(r)) if l == r => true,
515      _ => false,
516    }
517  }
518}
519
520impl DeleteUserError {
521  pub fn other<E: 'static + Error + Send + Sync>(e: E) -> Self {
522    Self::Other(Box::new(e))
523  }
524}
525
526#[async_trait]
527#[auto_impl(&, Arc)]
528pub trait UserStore: Send + Sync {
529  async fn create_user(&self, options: &CreateUserOptions) -> Result<CompleteSimpleUser, AnyError>;
530
531  async fn get_user(&self, options: &GetUserOptions) -> Result<Option<RawGetUserResult>, AnyError>;
532
533  /// TODO: Return `Result<ShortUser, GetShortUserError>` where `NotFound` is one of the error variants
534  async fn get_short_user(&self, options: &GetShortUserOptions) -> Result<Option<ShortUser>, AnyError>;
535
536  async fn get_user_with_password(&self, options: &GetUserOptions) -> Result<Option<ShortUserWithPassword>, AnyError>;
537
538  async fn update_user(&self, options: &RawUpdateUserOptions) -> Result<CompleteSimpleUser, RawUpdateUserError>;
539
540  async fn hard_delete_user(&self, user_ref: UserIdRef) -> Result<(), DeleteUserError>;
541}
542
543#[cfg(test)]
544mod test {
545  #[cfg(feature = "serde")]
546  use crate::core::Instant;
547  #[cfg(feature = "serde")]
548  use crate::password::PasswordHash;
549  #[cfg(feature = "serde")]
550  use crate::user::{
551    CompleteSimpleUser, RawGetUserResult, RawUpdateUserPatch, ShortUser, SimpleUser, UserDisplayNameVersion,
552    UserDisplayNameVersions,
553  };
554  #[cfg(feature = "serde")]
555  use std::fs;
556
557  #[cfg(feature = "serde")]
558  fn get_short_user_demurgos() -> ShortUser {
559    ShortUser {
560      id: "9f310484-963b-446b-af69-797feec6813f".parse().unwrap(),
561      display_name: UserDisplayNameVersions {
562        current: UserDisplayNameVersion {
563          value: "Demurgos".parse().unwrap(),
564        },
565      },
566    }
567  }
568
569  #[cfg(feature = "serde")]
570  #[test]
571  fn read_short_user_demurgos() {
572    let s = fs::read_to_string("../../test-resources/core/user/short-user/demurgos/value.json").unwrap();
573    let actual: ShortUser = serde_json::from_str(&s).unwrap();
574    let expected = get_short_user_demurgos();
575    assert_eq!(actual, expected);
576  }
577
578  #[cfg(feature = "serde")]
579  #[test]
580  fn write_short_user_demurgos() {
581    let value = get_short_user_demurgos();
582    let actual: String = serde_json::to_string_pretty(&value).unwrap();
583    let expected = fs::read_to_string("../../test-resources/core/user/short-user/demurgos/value.json").unwrap();
584    assert_eq!(&actual, expected.trim());
585  }
586
587  #[cfg(feature = "serde")]
588  fn get_get_user_result_short() -> RawGetUserResult {
589    RawGetUserResult::Short(ShortUser {
590      id: "e9c17533-633e-4f60-be9e-72883ae0174a".parse().unwrap(),
591      display_name: UserDisplayNameVersions {
592        current: UserDisplayNameVersion {
593          value: "Alice".parse().unwrap(),
594        },
595      },
596    })
597  }
598
599  #[cfg(feature = "serde")]
600  #[test]
601  fn read_get_user_result_short() {
602    let s = fs::read_to_string("../../test-resources/core/user/get-user-result/short/value.json").unwrap();
603    let actual: RawGetUserResult = serde_json::from_str(&s).unwrap();
604    let expected = get_get_user_result_short();
605    assert_eq!(actual, expected);
606  }
607
608  #[cfg(feature = "serde")]
609  #[test]
610  fn write_get_user_result_short() {
611    let value = get_get_user_result_short();
612    let actual: String = serde_json::to_string_pretty(&value).unwrap();
613    let expected = fs::read_to_string("../../test-resources/core/user/get-user-result/short/value.json").unwrap();
614    assert_eq!(&actual, expected.trim());
615  }
616
617  #[cfg(feature = "serde")]
618  fn get_get_user_result_default() -> RawGetUserResult {
619    RawGetUserResult::Default(SimpleUser {
620      id: "28dbb0bf-0fdc-40fe-ae5a-dde193f9fea8".parse().unwrap(),
621      display_name: UserDisplayNameVersions {
622        current: UserDisplayNameVersion {
623          value: "Alice".parse().unwrap(),
624        },
625      },
626      created_at: Instant::ymd_hms_milli(2021, 1, 15, 14, 17, 14, 15),
627      is_administrator: true,
628    })
629  }
630
631  #[cfg(feature = "serde")]
632  #[test]
633  fn read_get_user_result_default() {
634    let s = fs::read_to_string("../../test-resources/core/user/get-user-result/default/value.json").unwrap();
635    let actual: RawGetUserResult = serde_json::from_str(&s).unwrap();
636    let expected = get_get_user_result_default();
637    assert_eq!(actual, expected);
638  }
639
640  #[cfg(feature = "serde")]
641  #[test]
642  fn write_get_user_result_default() {
643    let value = get_get_user_result_default();
644    let actual: String = serde_json::to_string_pretty(&value).unwrap();
645    let expected = fs::read_to_string("../../test-resources/core/user/get-user-result/default/value.json").unwrap();
646    assert_eq!(&actual, expected.trim());
647  }
648
649  #[cfg(feature = "serde")]
650  fn get_get_user_result_complete() -> RawGetUserResult {
651    RawGetUserResult::Complete(CompleteSimpleUser {
652      id: "abeb9363-2035-4c20-9bb8-21edfb432cbf".parse().unwrap(),
653      display_name: UserDisplayNameVersions {
654        current: UserDisplayNameVersion {
655          value: "Alice".parse().unwrap(),
656        },
657      },
658      created_at: Instant::ymd_hms_milli(2021, 1, 15, 14, 17, 14, 15),
659      is_administrator: true,
660      username: Some("alice".parse().unwrap()),
661      email_address: None,
662    })
663  }
664
665  #[cfg(feature = "serde")]
666  #[test]
667  fn read_get_user_result_complete() {
668    let s = fs::read_to_string("../../test-resources/core/user/get-user-result/complete/value.json").unwrap();
669    let actual: RawGetUserResult = serde_json::from_str(&s).unwrap();
670    let expected = get_get_user_result_complete();
671    assert_eq!(actual, expected);
672  }
673
674  #[cfg(feature = "serde")]
675  #[test]
676  fn write_get_user_result_complete() {
677    let value = get_get_user_result_complete();
678    let actual: String = serde_json::to_string_pretty(&value).unwrap();
679    let expected = fs::read_to_string("../../test-resources/core/user/get-user-result/complete/value.json").unwrap();
680    assert_eq!(&actual, expected.trim());
681  }
682
683  #[cfg(feature = "serde")]
684  fn get_update_user_patch_no_op() -> RawUpdateUserPatch {
685    RawUpdateUserPatch {
686      display_name: None,
687      username: None,
688      password: None,
689    }
690  }
691
692  #[cfg(feature = "serde")]
693  #[test]
694  fn read_update_user_patch_no_op() {
695    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/no-op/value.json").unwrap();
696    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
697    let expected = get_update_user_patch_no_op();
698    assert_eq!(actual, expected);
699  }
700
701  #[cfg(feature = "serde")]
702  #[test]
703  fn write_update_user_patch_no_op() {
704    let value = get_update_user_patch_no_op();
705    let actual: String = serde_json::to_string_pretty(&value).unwrap();
706    let expected = fs::read_to_string("../../test-resources/core/user/update-user-patch/no-op/value.json").unwrap();
707    assert_eq!(&actual, expected.trim());
708  }
709
710  #[cfg(feature = "serde")]
711  fn get_update_user_patch_remove_username() -> RawUpdateUserPatch {
712    RawUpdateUserPatch {
713      display_name: None,
714      username: Some(None),
715      password: None,
716    }
717  }
718
719  #[cfg(feature = "serde")]
720  #[test]
721  fn read_update_user_patch_remove_username() {
722    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/remove-username/value.json").unwrap();
723    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
724    let expected = get_update_user_patch_remove_username();
725    assert_eq!(actual, expected);
726  }
727
728  #[cfg(feature = "serde")]
729  #[test]
730  fn write_update_user_patch_remove_username() {
731    let value = get_update_user_patch_remove_username();
732    let actual: String = serde_json::to_string_pretty(&value).unwrap();
733    let expected =
734      fs::read_to_string("../../test-resources/core/user/update-user-patch/remove-username/value.json").unwrap();
735    assert_eq!(&actual, expected.trim());
736  }
737
738  #[cfg(feature = "serde")]
739  fn get_update_user_patch_set_all() -> RawUpdateUserPatch {
740    let hash: Vec<u8> = hex::decode("736372797074000c0000000800000001c5ec1067adb434a19cb471dcfc13a8cec8c6e935ec7e14eda9f51a386924eeeb9fce39bb3d36f6101cc06189da63e0513a54553efbee9d2a058bafbda5231093c4ae5e9b3f87a2d002fa49ff75b868fd").unwrap();
741    RawUpdateUserPatch {
742      display_name: Some("Demurgos".parse().unwrap()),
743      username: Some(Some("demurgos".parse().unwrap())),
744      password: Some(Some(PasswordHash::from(&hash[..]))),
745    }
746  }
747
748  #[cfg(feature = "serde")]
749  #[test]
750  fn read_update_user_patch_set_all() {
751    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/set-all/value.json").unwrap();
752    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
753    let expected = get_update_user_patch_set_all();
754    assert_eq!(actual, expected);
755  }
756
757  #[cfg(feature = "serde")]
758  #[test]
759  fn write_update_user_patch_set_all() {
760    let value = get_update_user_patch_set_all();
761    let actual: String = serde_json::to_string_pretty(&value).unwrap();
762    let expected = fs::read_to_string("../../test-resources/core/user/update-user-patch/set-all/value.json").unwrap();
763    assert_eq!(&actual, expected.trim());
764  }
765
766  #[cfg(feature = "serde")]
767  fn get_update_user_patch_set_username() -> RawUpdateUserPatch {
768    RawUpdateUserPatch {
769      display_name: None,
770      username: Some(Some("demurgos".parse().unwrap())),
771      password: None,
772    }
773  }
774
775  #[cfg(feature = "serde")]
776  #[test]
777  fn read_update_user_patch_set_username() {
778    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/set-username/value.json").unwrap();
779    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
780    let expected = get_update_user_patch_set_username();
781    assert_eq!(actual, expected);
782  }
783
784  #[cfg(feature = "serde")]
785  #[test]
786  fn write_update_user_patch_set_username() {
787    let value = get_update_user_patch_set_username();
788    let actual: String = serde_json::to_string_pretty(&value).unwrap();
789    let expected =
790      fs::read_to_string("../../test-resources/core/user/update-user-patch/set-username/value.json").unwrap();
791    assert_eq!(&actual, expected.trim());
792  }
793}