Skip to main content

acta_maker_sdk/types/
invite.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3pub const REFERRAL_CODE_MIN_LEN: usize = 4;
4pub const REFERRAL_CODE_MAX_LEN: usize = 16;
5
6#[derive(Debug, thiserror::Error, PartialEq, Eq)]
7pub enum ReferralCodeFormatError {
8    #[error("code length must be between {min} and {max}")]
9    Length { min: usize, max: usize },
10    #[error("code must contain only ASCII letters and digits")]
11    Charset,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct ReferralCode(String);
16
17impl ReferralCode {
18    /// Trim, uppercase and validate a backend referral code.
19    ///
20    /// # Errors
21    /// Returns [`ReferralCodeFormatError::Length`] for a trimmed length outside
22    /// the supported range and [`ReferralCodeFormatError::Charset`] for any
23    /// non-ASCII-alphanumeric character.
24    pub fn parse(input: &str) -> Result<Self, ReferralCodeFormatError> {
25        let normalized = input.trim().to_ascii_uppercase();
26        if !(REFERRAL_CODE_MIN_LEN..=REFERRAL_CODE_MAX_LEN).contains(&normalized.len()) {
27            return Err(ReferralCodeFormatError::Length {
28                min: REFERRAL_CODE_MIN_LEN,
29                max: REFERRAL_CODE_MAX_LEN,
30            });
31        }
32        if !normalized
33            .chars()
34            .all(|character| character.is_ascii_alphanumeric())
35        {
36            return Err(ReferralCodeFormatError::Charset);
37        }
38        Ok(Self(normalized))
39    }
40
41    #[must_use]
42    pub fn as_str(&self) -> &str {
43        &self.0
44    }
45
46    #[must_use]
47    pub fn into_string(self) -> String {
48        self.0
49    }
50}
51
52impl std::fmt::Display for ReferralCode {
53    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        formatter.write_str(&self.0)
55    }
56}
57
58impl AsRef<str> for ReferralCode {
59    fn as_ref(&self) -> &str {
60        &self.0
61    }
62}
63
64impl TryFrom<String> for ReferralCode {
65    type Error = ReferralCodeFormatError;
66
67    fn try_from(value: String) -> Result<Self, Self::Error> {
68        Self::parse(&value)
69    }
70}
71
72impl TryFrom<&str> for ReferralCode {
73    type Error = ReferralCodeFormatError;
74
75    fn try_from(value: &str) -> Result<Self, Self::Error> {
76        Self::parse(value)
77    }
78}
79
80impl Serialize for ReferralCode {
81    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
82        serializer.serialize_str(&self.0)
83    }
84}
85
86impl<'de> Deserialize<'de> for ReferralCode {
87    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
88        let raw = String::deserialize(deserializer)?;
89        Self::parse(&raw).map_err(serde::de::Error::custom)
90    }
91}
92
93#[derive(
94    Debug,
95    Clone,
96    Copy,
97    PartialEq,
98    Eq,
99    Hash,
100    Serialize,
101    Deserialize,
102    strum::IntoStaticStr,
103    strum::EnumString,
104)]
105#[serde(rename_all = "snake_case")]
106#[strum(serialize_all = "snake_case")]
107pub enum TakerStatus {
108    Pending,
109    Active,
110}
111
112impl TryFrom<String> for TakerStatus {
113    type Error = strum::ParseError;
114
115    fn try_from(value: String) -> Result<Self, Self::Error> {
116        std::str::FromStr::from_str(value.as_str())
117    }
118}
119
120pub const RESERVED_CODES: &[&str] = &["ACTA", "ADMIN", "SYSTEM", "SUPPORT", "NULL", "ROOT", "API"];
121
122#[must_use]
123pub fn is_reserved(code: &str) -> bool {
124    RESERVED_CODES
125        .iter()
126        .any(|reserved| reserved.eq_ignore_ascii_case(code))
127}