etwin_core 0.11.0

Core crate for Eternal-Twin
Documentation
use crate::core::{FinitePeriod, Instant};
use crate::dinoparc::{DinoparcPassword, DinoparcServer, DinoparcUserId, DinoparcUsername};
use crate::email::EmailAddress;
use crate::hammerfest::{
  HammerfestPassword, HammerfestServer, HammerfestSessionKey, HammerfestUserId, HammerfestUsername,
};
use crate::link::VersionedLinks;
use crate::oauth::RfcOauthAccessToken;
use crate::password::{Password, PasswordHash};
use crate::twinoid::TwinoidUserId;
use crate::types::AnyError;
use async_trait::async_trait;
use auto_impl::auto_impl;
use chrono::Duration;
#[cfg(feature = "serde")]
use etwin_serde_tools::{deserialize_explicit_option, deserialize_nested_option, Deserialize, Serialize};
use once_cell::sync::Lazy;
use std::error::Error;

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CompleteSimpleUser {
  pub id: UserId,
  pub created_at: Instant,
  pub display_name: UserDisplayNameVersions,
  pub is_administrator: bool,
  #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_explicit_option"))]
  pub username: Option<Username>,
  #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_explicit_option"))]
  pub email_address: Option<EmailAddress>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CompleteUser {
  pub id: UserId,
  pub created_at: Instant,
  pub display_name: UserDisplayNameVersions,
  pub is_administrator: bool,
  pub links: VersionedLinks,
  pub username: Option<Username>,
  pub email_address: Option<EmailAddress>,
  pub has_password: bool,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CreateUserOptions {
  pub display_name: UserDisplayName,
  pub email: Option<EmailAddress>,
  pub username: Option<Username>,
  pub password: Option<PasswordHash>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UpdateUserOptions {
  pub r#ref: UserIdRef,
  pub patch: UpdateUserPatch,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UpdateUserPatch {
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub display_name: Option<UserDisplayName>,
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
  pub username: Option<Option<Username>>,
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
  pub password: Option<Option<Password>>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawUpdateUserOptions {
  pub actor: UserIdRef,
  pub r#ref: UserIdRef,
  pub patch: RawUpdateUserPatch,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawUpdateUserPatch {
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub display_name: Option<UserDisplayName>,
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
  pub username: Option<Option<Username>>,
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_nested_option"))]
  pub password: Option<Option<PasswordHash>>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "type"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UserFields {
  CompleteIfSelf { self_user_id: UserId },
  Complete,
  Default,
  Short,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetUserOptions {
  pub r#ref: UserRef,
  pub fields: UserFields,
  pub time: Option<Instant>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetShortUserOptions {
  pub r#ref: UserRef,
  pub time: Option<Instant>,
}

impl From<UserRef> for GetShortUserOptions {
  fn from(r#ref: UserRef) -> Self {
    Self { r#ref, time: None }
  }
}

impl From<UserIdRef> for GetShortUserOptions {
  fn from(r: UserIdRef) -> Self {
    let r: UserRef = r.into();
    r.into()
  }
}

impl From<UserId> for GetShortUserOptions {
  fn from(id: UserId) -> Self {
    let r: UserRef = id.into();
    r.into()
  }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum GetUserResult {
  Complete(CompleteUser),
  Default(User),
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RawGetUserResult {
  Complete(CompleteSimpleUser),
  Default(SimpleUser),
  Short(ShortUser),
}

impl RawGetUserResult {
  pub fn id(&self) -> UserId {
    match self {
      Self::Complete(u) => u.id,
      Self::Default(u) => u.id,
      Self::Short(u) => u.id,
    }
  }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShortUser {
  pub id: UserId,
  pub display_name: UserDisplayNameVersions,
}

impl ShortUser {
  pub const fn as_ref(&self) -> UserIdRef {
    UserIdRef { id: self.id }
  }
}

impl From<SimpleUser> for ShortUser {
  fn from(user: SimpleUser) -> Self {
    Self {
      id: user.id,
      display_name: user.display_name,
    }
  }
}

impl From<CompleteSimpleUser> for ShortUser {
  fn from(user: CompleteSimpleUser) -> Self {
    Self {
      id: user.id,
      display_name: user.display_name,
    }
  }
}

impl From<User> for ShortUser {
  fn from(user: User) -> Self {
    Self {
      id: user.id,
      display_name: user.display_name,
    }
  }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ShortUserWithPassword {
  pub id: UserId,
  pub display_name: UserDisplayNameVersions,
  pub password: Option<PasswordHash>,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SimpleUser {
  pub id: UserId,
  pub created_at: Instant,
  pub display_name: UserDisplayNameVersions,
  pub is_administrator: bool,
}

impl From<CompleteSimpleUser> for SimpleUser {
  fn from(user: CompleteSimpleUser) -> Self {
    Self {
      id: user.id,
      created_at: user.created_at,
      display_name: user.display_name,
      is_administrator: user.is_administrator,
    }
  }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct User {
  pub id: UserId,
  pub created_at: Instant,
  pub display_name: UserDisplayNameVersions,
  pub is_administrator: bool,
  pub links: VersionedLinks,
}

impl User {
  pub fn to_short(&self) -> ShortUser {
    // TODO: Avoid `.clone()`
    ShortUser::from(self.clone())
  }
}

declare_new_string! {
  pub struct UserDisplayName(String);
  pub type ParseError = UserDisplayNameParseError;
  const PATTERN = r"^[\p{Letter}_ ()][\p{Letter}_ ()0-9]*$";
  const SQL_NAME = "user_display_name";
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserDisplayNameVersion {
  pub value: UserDisplayName,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserDisplayNameVersions {
  pub current: UserDisplayNameVersion,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "method"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LinkToDinoparcOptions {
  Credentials(LinkToDinoparcWithCredentialsOptions),
  Ref(LinkToDinoparcWithRefOptions),
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToDinoparcWithCredentialsOptions {
  pub user_id: UserId,
  pub dinoparc_server: DinoparcServer,
  pub dinoparc_username: DinoparcUsername,
  pub dinoparc_password: DinoparcPassword,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToDinoparcWithRefOptions {
  pub user_id: UserId,
  pub dinoparc_server: DinoparcServer,
  pub dinoparc_user_id: DinoparcUserId,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnlinkFromDinoparcOptions {
  pub user_id: UserId,
  pub dinoparc_server: DinoparcServer,
  pub dinoparc_user_id: DinoparcUserId,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "method"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LinkToHammerfestOptions {
  Credentials(LinkToHammerfestWithCredentialsOptions),
  SessionKey(LinkToHammerfestWithSessionKeyOptions),
  Ref(LinkToHammerfestWithRefOptions),
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToHammerfestWithCredentialsOptions {
  pub user_id: UserId,
  pub hammerfest_server: HammerfestServer,
  pub hammerfest_username: HammerfestUsername,
  pub hammerfest_password: HammerfestPassword,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToHammerfestWithSessionKeyOptions {
  pub user_id: UserId,
  pub hammerfest_server: HammerfestServer,
  pub hammerfest_session_key: HammerfestSessionKey,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToHammerfestWithRefOptions {
  pub user_id: UserId,
  pub hammerfest_server: HammerfestServer,
  pub hammerfest_user_id: HammerfestUserId,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnlinkFromHammerfestOptions {
  pub user_id: UserId,
  pub hammerfest_server: HammerfestServer,
  pub hammerfest_user_id: HammerfestUserId,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(tag = "method"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LinkToTwinoidOptions {
  Oauth(LinkToTwinoidWithOauthOptions),
  Ref(LinkToTwinoidWithRefOptions),
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToTwinoidWithOauthOptions {
  pub user_id: UserId,
  pub access_token: RfcOauthAccessToken,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LinkToTwinoidWithRefOptions {
  pub user_id: UserId,
  pub twinoid_user_id: TwinoidUserId,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnlinkFromTwinoidOptions {
  pub user_id: UserId,
  pub twinoid_user_id: TwinoidUserId,
}

declare_new_uuid! {
  pub struct UserId(Uuid);
  pub type ParseError = UserIdParseError;
  const SQL_NAME = "user_id";
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserIdRef {
  pub id: UserId,
}

impl UserIdRef {
  pub const fn new(id: UserId) -> Self {
    Self { id }
  }
}

impl From<UserId> for UserIdRef {
  fn from(id: UserId) -> Self {
    Self::new(id)
  }
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserUsernameRef {
  pub username: Username,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename = "User"))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserEmailRef {
  pub email: EmailAddress,
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum UserRef {
  Id(UserIdRef),
  Username(UserUsernameRef),
  Email(UserEmailRef),
}

impl From<UserId> for UserRef {
  fn from(id: UserId) -> Self {
    Self::Id(id.into())
  }
}

impl From<UserIdRef> for UserRef {
  fn from(r: UserIdRef) -> Self {
    Self::Id(r)
  }
}

impl From<UserUsernameRef> for UserRef {
  fn from(r: UserUsernameRef) -> Self {
    Self::Username(r)
  }
}

impl From<UserEmailRef> for UserRef {
  fn from(r: UserEmailRef) -> Self {
    Self::Email(r)
  }
}

declare_new_string! {
  pub struct Username(String);
  pub type ParseError = UsernameParseError;
  const PATTERN = "^[a-z_][a-z0-9_]{1,31}$";
  const SQL_NAME = "username";
}

pub static USERNAME_LOCK_DURATION: Lazy<Duration> = Lazy::new(|| Duration::days(7));
pub static USER_DISPLAY_NAME_LOCK_DURATION: Lazy<Duration> = Lazy::new(|| Duration::days(30));
pub static USER_PASSWORD_LOCK_DURATION: Lazy<Duration> = Lazy::new(|| Duration::minutes(10));

#[derive(Debug, thiserror::Error)]
pub enum RawUpdateUserError {
  #[error("Failed to find user to update for ref: {:?}", .0)]
  NotFound(UserIdRef),
  #[error("Failed to update user {:?}, display_name locked during {:?}, current time is {}", .0, .1, .2)]
  LockedDisplayName(UserIdRef, FinitePeriod, Instant),
  #[error("Failed to update user {:?}, username locked during {:?}, current time is {}", .0, .1, .2)]
  LockedUsername(UserIdRef, FinitePeriod, Instant),
  #[error("Failed to update user {:?}, password locked during {:?}, current time is {}", .0, .1, .2)]
  LockedPassword(UserIdRef, FinitePeriod, Instant),
  #[error(transparent)]
  Other(AnyError),
}

impl PartialEq for RawUpdateUserError {
  fn eq(&self, other: &Self) -> bool {
    match (self, other) {
      (RawUpdateUserError::NotFound(l), RawUpdateUserError::NotFound(r)) if l == r => true,
      (RawUpdateUserError::LockedDisplayName(l0, l1, l2), RawUpdateUserError::LockedDisplayName(r0, r1, r2))
        if (l0, l1, l2) == (r0, r1, r2) =>
      {
        true
      }
      (RawUpdateUserError::LockedUsername(l0, l1, l2), RawUpdateUserError::LockedUsername(r0, r1, r2))
        if (l0, l1, l2) == (r0, r1, r2) =>
      {
        true
      }
      (RawUpdateUserError::LockedPassword(l0, l1, l2), RawUpdateUserError::LockedPassword(r0, r1, r2))
        if (l0, l1, l2) == (r0, r1, r2) =>
      {
        true
      }
      _ => false,
    }
  }
}

impl RawUpdateUserError {
  pub fn other<E: 'static + Error + Send + Sync>(e: E) -> Self {
    Self::Other(Box::new(e))
  }
}

#[derive(Debug, thiserror::Error)]
pub enum DeleteUserError {
  #[error("Failed to find user to delete for ref: {:?}", .0)]
  NotFound(UserIdRef),
  #[error(transparent)]
  Other(AnyError),
}

impl PartialEq for DeleteUserError {
  fn eq(&self, other: &Self) -> bool {
    #[allow(clippy::match_like_matches_macro)]
    match (self, other) {
      (DeleteUserError::NotFound(l), DeleteUserError::NotFound(r)) if l == r => true,
      _ => false,
    }
  }
}

impl DeleteUserError {
  pub fn other<E: 'static + Error + Send + Sync>(e: E) -> Self {
    Self::Other(Box::new(e))
  }
}

#[async_trait]
#[auto_impl(&, Arc)]
pub trait UserStore: Send + Sync {
  async fn create_user(&self, options: &CreateUserOptions) -> Result<CompleteSimpleUser, AnyError>;

  async fn get_user(&self, options: &GetUserOptions) -> Result<Option<RawGetUserResult>, AnyError>;

  /// TODO: Return `Result<ShortUser, GetShortUserError>` where `NotFound` is one of the error variants
  async fn get_short_user(&self, options: &GetShortUserOptions) -> Result<Option<ShortUser>, AnyError>;

  async fn get_user_with_password(&self, options: &GetUserOptions) -> Result<Option<ShortUserWithPassword>, AnyError>;

  async fn update_user(&self, options: &RawUpdateUserOptions) -> Result<CompleteSimpleUser, RawUpdateUserError>;

  async fn hard_delete_user(&self, user_ref: UserIdRef) -> Result<(), DeleteUserError>;
}

#[cfg(test)]
mod test {
  #[cfg(feature = "serde")]
  use crate::core::Instant;
  #[cfg(feature = "serde")]
  use crate::password::PasswordHash;
  #[cfg(feature = "serde")]
  use crate::user::{
    CompleteSimpleUser, RawGetUserResult, RawUpdateUserPatch, ShortUser, SimpleUser, UserDisplayNameVersion,
    UserDisplayNameVersions,
  };
  #[cfg(feature = "serde")]
  use std::fs;

  #[cfg(feature = "serde")]
  fn get_short_user_demurgos() -> ShortUser {
    ShortUser {
      id: "9f310484-963b-446b-af69-797feec6813f".parse().unwrap(),
      display_name: UserDisplayNameVersions {
        current: UserDisplayNameVersion {
          value: "Demurgos".parse().unwrap(),
        },
      },
    }
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_short_user_demurgos() {
    let s = fs::read_to_string("../../test-resources/core/user/short-user/demurgos/value.json").unwrap();
    let actual: ShortUser = serde_json::from_str(&s).unwrap();
    let expected = get_short_user_demurgos();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_short_user_demurgos() {
    let value = get_short_user_demurgos();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected = fs::read_to_string("../../test-resources/core/user/short-user/demurgos/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_get_user_result_short() -> RawGetUserResult {
    RawGetUserResult::Short(ShortUser {
      id: "e9c17533-633e-4f60-be9e-72883ae0174a".parse().unwrap(),
      display_name: UserDisplayNameVersions {
        current: UserDisplayNameVersion {
          value: "Alice".parse().unwrap(),
        },
      },
    })
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_get_user_result_short() {
    let s = fs::read_to_string("../../test-resources/core/user/get-user-result/short/value.json").unwrap();
    let actual: RawGetUserResult = serde_json::from_str(&s).unwrap();
    let expected = get_get_user_result_short();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_get_user_result_short() {
    let value = get_get_user_result_short();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected = fs::read_to_string("../../test-resources/core/user/get-user-result/short/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_get_user_result_default() -> RawGetUserResult {
    RawGetUserResult::Default(SimpleUser {
      id: "28dbb0bf-0fdc-40fe-ae5a-dde193f9fea8".parse().unwrap(),
      display_name: UserDisplayNameVersions {
        current: UserDisplayNameVersion {
          value: "Alice".parse().unwrap(),
        },
      },
      created_at: Instant::ymd_hms_milli(2021, 1, 15, 14, 17, 14, 15),
      is_administrator: true,
    })
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_get_user_result_default() {
    let s = fs::read_to_string("../../test-resources/core/user/get-user-result/default/value.json").unwrap();
    let actual: RawGetUserResult = serde_json::from_str(&s).unwrap();
    let expected = get_get_user_result_default();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_get_user_result_default() {
    let value = get_get_user_result_default();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected = fs::read_to_string("../../test-resources/core/user/get-user-result/default/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_get_user_result_complete() -> RawGetUserResult {
    RawGetUserResult::Complete(CompleteSimpleUser {
      id: "abeb9363-2035-4c20-9bb8-21edfb432cbf".parse().unwrap(),
      display_name: UserDisplayNameVersions {
        current: UserDisplayNameVersion {
          value: "Alice".parse().unwrap(),
        },
      },
      created_at: Instant::ymd_hms_milli(2021, 1, 15, 14, 17, 14, 15),
      is_administrator: true,
      username: Some("alice".parse().unwrap()),
      email_address: None,
    })
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_get_user_result_complete() {
    let s = fs::read_to_string("../../test-resources/core/user/get-user-result/complete/value.json").unwrap();
    let actual: RawGetUserResult = serde_json::from_str(&s).unwrap();
    let expected = get_get_user_result_complete();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_get_user_result_complete() {
    let value = get_get_user_result_complete();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected = fs::read_to_string("../../test-resources/core/user/get-user-result/complete/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_update_user_patch_no_op() -> RawUpdateUserPatch {
    RawUpdateUserPatch {
      display_name: None,
      username: None,
      password: None,
    }
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_update_user_patch_no_op() {
    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/no-op/value.json").unwrap();
    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
    let expected = get_update_user_patch_no_op();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_update_user_patch_no_op() {
    let value = get_update_user_patch_no_op();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected = fs::read_to_string("../../test-resources/core/user/update-user-patch/no-op/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_update_user_patch_remove_username() -> RawUpdateUserPatch {
    RawUpdateUserPatch {
      display_name: None,
      username: Some(None),
      password: None,
    }
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_update_user_patch_remove_username() {
    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/remove-username/value.json").unwrap();
    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
    let expected = get_update_user_patch_remove_username();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_update_user_patch_remove_username() {
    let value = get_update_user_patch_remove_username();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected =
      fs::read_to_string("../../test-resources/core/user/update-user-patch/remove-username/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_update_user_patch_set_all() -> RawUpdateUserPatch {
    let hash: Vec<u8> = hex::decode("736372797074000c0000000800000001c5ec1067adb434a19cb471dcfc13a8cec8c6e935ec7e14eda9f51a386924eeeb9fce39bb3d36f6101cc06189da63e0513a54553efbee9d2a058bafbda5231093c4ae5e9b3f87a2d002fa49ff75b868fd").unwrap();
    RawUpdateUserPatch {
      display_name: Some("Demurgos".parse().unwrap()),
      username: Some(Some("demurgos".parse().unwrap())),
      password: Some(Some(PasswordHash::from(&hash[..]))),
    }
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_update_user_patch_set_all() {
    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/set-all/value.json").unwrap();
    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
    let expected = get_update_user_patch_set_all();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_update_user_patch_set_all() {
    let value = get_update_user_patch_set_all();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected = fs::read_to_string("../../test-resources/core/user/update-user-patch/set-all/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }

  #[cfg(feature = "serde")]
  fn get_update_user_patch_set_username() -> RawUpdateUserPatch {
    RawUpdateUserPatch {
      display_name: None,
      username: Some(Some("demurgos".parse().unwrap())),
      password: None,
    }
  }

  #[cfg(feature = "serde")]
  #[test]
  fn read_update_user_patch_set_username() {
    let s = fs::read_to_string("../../test-resources/core/user/update-user-patch/set-username/value.json").unwrap();
    let actual: RawUpdateUserPatch = serde_json::from_str(&s).unwrap();
    let expected = get_update_user_patch_set_username();
    assert_eq!(actual, expected);
  }

  #[cfg(feature = "serde")]
  #[test]
  fn write_update_user_patch_set_username() {
    let value = get_update_user_patch_set_username();
    let actual: String = serde_json::to_string_pretty(&value).unwrap();
    let expected =
      fs::read_to_string("../../test-resources/core/user/update-user-patch/set-username/value.json").unwrap();
    assert_eq!(&actual, expected.trim());
  }
}