1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Contains an utility to validate channel names

use thiserror::Error;

/// Validate a given login name. Returns an error detailing the issue
/// if the string is found to be invalid.
pub fn validate_login(channel_login: &str) -> Result<(), Error> {
    let mut length: usize = 0;
    for char in channel_login.chars() {
        if !(matches!(char, 'a'..='z' | '0'..='9' | '_')) {
            return Err(Error::InvalidCharacter {
                login: channel_login.to_owned(),
                position: length,
                character: char,
            });
        }

        length += 1;
        if length > 25 {
            return Err(Error::TooLong {
                login: channel_login.to_owned(),
            });
        }
    }
    if length < 1 {
        return Err(Error::TooShort {
            login: channel_login.to_owned(),
        });
    }

    Ok(())
}

/// Types of errors that can be found as a result of validating a channel login name. See the enum
/// variants for details
#[derive(Error, Debug, PartialEq, Eq)]
pub enum Error {
    /// A character not allowed in login names was found at a certain position in the given string
    #[error("Invalid login name `{login}`: Invalid character `{character}` encountered at position `{position}`")]
    InvalidCharacter {
        /// The login name that failed validation.
        login: String,
        /// Index of the found invalid character in the original string
        position: usize,
        /// The invalid character
        character: char,
    },
    /// Login name exceeds maximum length of 25 characters
    #[error("Invalid login name `{login}`: Login name exceeds maximum length of 25 characters")]
    TooLong {
        /// The login name that failed validation.
        login: String,
    },
    /// Login name is too short (must be at least one character long)
    #[error("Invalid login name `{login}`: Login name is too short (must be at least one character long)")]
    TooShort {
        /// The login name that failed validation.
        login: String,
    },
}

#[cfg(test)]
mod tests {
    use crate::validate::validate_login;
    use crate::validate::Error;

    #[test]
    pub fn test_validate_login() {
        assert_eq!(Ok(()), validate_login("pajlada"));
        assert_eq!(
            Err(Error::InvalidCharacter {
                login: "pajLada".to_owned(),
                position: 3,
                character: 'L',
            }),
            validate_login("pajLada")
        );
        assert_eq!(
            Err(Error::InvalidCharacter {
                login: "pajlada,def".to_owned(),
                position: 7,
                character: ',',
            }),
            validate_login("pajlada,def")
        );
        assert_eq!(
            Err(Error::InvalidCharacter {
                login: "pajlada-def".to_owned(),
                position: 7,
                character: '-',
            }),
            validate_login("pajlada-def")
        );
        assert_eq!(Ok(()), validate_login("1234567890123456789012345"));
        assert_eq!(
            Err(Error::TooLong {
                login: "12345678901234567890123456".to_owned()
            }),
            validate_login("12345678901234567890123456")
        );
        assert_eq!(Ok(()), validate_login("a"));
        assert_eq!(Ok(()), validate_login("abc"));
        assert_eq!(Ok(()), validate_login("xqco"));
        assert_eq!(Ok(()), validate_login("cool_user___"));
        assert_eq!(Ok(()), validate_login("cool_7user___7"));
        assert_eq!(
            Err(Error::TooShort {
                login: "".to_owned()
            }),
            validate_login("")
        );
    }
}