Skip to main content

opentalk_client_data_persistence/
opentalk_account_id.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5use std::str::FromStr;
6
7use serde::Serialize;
8use snafu::{Snafu, ensure};
9
10pub const OPENTALK_ACCOUNT_ID_MIN_LENGTH: usize = 1;
11
12pub const OPENTALK_ACCOUNT_ID_MAX_LENGTH: usize = 100;
13
14/// The id of an OpenTalk account.
15#[derive(
16    Debug,
17    Clone,
18    PartialEq,
19    Eq,
20    PartialOrd,
21    Ord,
22    Hash,
23    Serialize,
24    derive_more::AsRef,
25    derive_more::Deref,
26    serde_with::DeserializeFromStr,
27)]
28pub struct OpenTalkAccountId(String);
29
30/// The error that is returned by [OpenTalkAccountId::from_str] on failure.
31#[derive(Debug, Snafu)]
32pub enum ParseOpenTalkAccountIdError {
33    /// Invalid characters were found in the input data.
34    #[snafu(display("OpenTalk account id may only contain alphanumeric characters and \"-\""))]
35    InvalidCharacters,
36
37    /// The input string was shorter than the minimum length [OPENTALK_ACCOUNT_ID_MIN_LENGTH].
38    #[snafu(display("OpenTalk account id must have at least {min_length} characters"))]
39    TooShort {
40        /// The minimum allowed length.
41        min_length: usize,
42    },
43
44    /// The input string was longer than the maximum length [OPENTALK_ACCOUNT_ID_MAX_LENGTH].
45    #[snafu(display("OpenTalk account id must not be longer than {max_length} characters"))]
46    TooLong {
47        /// The maximum allowed length.
48        max_length: usize,
49    },
50}
51
52impl FromStr for OpenTalkAccountId {
53    type Err = ParseOpenTalkAccountIdError;
54
55    fn from_str(s: &str) -> Result<Self, Self::Err> {
56        ensure_is_valid(s)?;
57
58        Ok(Self(s.to_string()))
59    }
60}
61
62fn ensure_is_valid(s: &str) -> Result<(), ParseOpenTalkAccountIdError> {
63    ensure!(
64        s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-'),
65        InvalidCharactersSnafu
66    );
67
68    ensure!(
69        s.len() >= OPENTALK_ACCOUNT_ID_MIN_LENGTH,
70        TooShortSnafu {
71            min_length: OPENTALK_ACCOUNT_ID_MIN_LENGTH
72        }
73    );
74
75    ensure!(
76        s.len() <= OPENTALK_ACCOUNT_ID_MAX_LENGTH,
77        TooLongSnafu {
78            max_length: OPENTALK_ACCOUNT_ID_MAX_LENGTH
79        }
80    );
81
82    Ok(())
83}
84
85#[cfg(test)]
86mod tests {
87    use pretty_assertions::assert_matches;
88
89    use super::OpenTalkAccountId;
90    use crate::opentalk_account_id::ParseOpenTalkAccountIdError;
91
92    #[test]
93    fn success_from_str() {
94        let id: Result<OpenTalkAccountId, ParseOpenTalkAccountIdError> = "opentalk-id".parse();
95        assert!(id.is_ok());
96    }
97
98    #[test]
99    fn error_to_short_from_str() {
100        let id: Result<OpenTalkAccountId, ParseOpenTalkAccountIdError> = "".parse();
101        assert_matches!(
102            id,
103            Err(ParseOpenTalkAccountIdError::TooShort { min_length: _ })
104        );
105    }
106
107    #[test]
108    fn error_to_long_from_str() {
109        let id: Result<OpenTalkAccountId, ParseOpenTalkAccountIdError> = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".parse();
110        assert_matches!(
111            id,
112            Err(ParseOpenTalkAccountIdError::TooLong { max_length: _ })
113        );
114    }
115
116    #[test]
117    fn error_invalid_char_from_str() {
118        let id: Result<OpenTalkAccountId, ParseOpenTalkAccountIdError> = "*".parse();
119        assert_matches!(id, Err(ParseOpenTalkAccountIdError::InvalidCharacters));
120    }
121}