Skip to main content

phc/
ident.rs

1//! Algorithm or parameter identifier.
2//!
3//! Implements the following parts of the [PHC string format specification][1]:
4//!
5//! > The function symbolic name is a sequence of characters in: `[a-z0-9-]`
6//! > (lowercase letters, digits, and the minus sign). No other character is
7//! > allowed. Each function defines its own identifier (or identifiers in case
8//! > of a function family); identifiers should be explicit (human readable,
9//! > not a single digit), with a length of about 5 to 10 characters. An
10//! > identifier name MUST NOT exceed 32 characters in length.
11//! >
12//! > Each parameter name shall be a sequence of characters in: `[a-z0-9-]`
13//! > (lowercase letters, digits, and the minus sign). No other character is
14//! > allowed. Parameter names SHOULD be readable for a human user. A
15//! > parameter name MUST NOT exceed 32 characters in length.
16//!
17//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
18
19use crate::{Error, Result, StringBuf};
20use core::{
21    fmt,
22    ops::Deref,
23    str::{self, FromStr},
24};
25
26/// Algorithm or parameter identifier.
27///
28/// This type encompasses both the "function symbolic name" and "parameter name"
29/// use cases as described in the [PHC string format specification][1].
30///
31/// # Constraints
32/// - ASCII-encoded string consisting of the characters `[a-z0-9-]`
33///   (lowercase letters, digits, and the minus sign)
34/// - Minimum length: 1 ASCII character (i.e. 1-byte)
35/// - Maximum length: 32 ASCII characters (i.e. 32-bytes)
36///
37/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
38#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
39pub struct Ident(StringBuf<{ Ident::MAX_LENGTH }>);
40
41impl Ident {
42    /// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
43    ///
44    /// This value corresponds to the maximum size of a function symbolic names
45    /// and parameter names according to the PHC string format.
46    /// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
47    ///
48    /// This value corresponds to the maximum size of a function symbolic names
49    /// and parameter names according to the PHC string format.
50    const MAX_LENGTH: usize = 32;
51
52    /// Parse an [`Ident`] from a string.
53    ///
54    /// String must conform to the constraints given in the type-level
55    /// documentation.
56    pub const fn new(s: &str) -> Result<Self> {
57        let input = s.as_bytes();
58
59        match input.len() {
60            1..=Self::MAX_LENGTH => {
61                let mut i = 0;
62
63                while i < input.len() {
64                    if !matches!(input[i], b'a'..=b'z' | b'0'..=b'9' | b'-') {
65                        return Err(Error::ParamNameInvalid);
66                    }
67
68                    i += 1;
69                }
70
71                match StringBuf::new(s) {
72                    Ok(buf) => Ok(Self(buf)),
73                    Err(e) => Err(e),
74                }
75            }
76            _ => Err(Error::ParamNameInvalid),
77        }
78    }
79
80    /// Parse an [`Ident`] from a string, panicking on parse errors.
81    ///
82    /// This function exists as a workaround for `unwrap` not yet being
83    /// stable in `const fn` contexts, and is intended to allow the result to
84    /// be bound to a constant value.
85    pub const fn new_unwrap(s: &str) -> Self {
86        assert!(!s.is_empty(), "PHC ident string can't be empty");
87        assert!(s.len() <= Self::MAX_LENGTH, "PHC ident string too long");
88
89        match Self::new(s) {
90            Ok(ident) => ident,
91            Err(_) => panic!("invalid PHC string format identifier"),
92        }
93    }
94
95    /// Borrow this ident as a `str`
96    pub fn as_str(&self) -> &str {
97        &self.0
98    }
99}
100
101impl AsRef<str> for Ident {
102    fn as_ref(&self) -> &str {
103        self.as_str()
104    }
105}
106
107impl Deref for Ident {
108    type Target = str;
109
110    fn deref(&self) -> &str {
111        self.as_str()
112    }
113}
114
115impl FromStr for Ident {
116    type Err = Error;
117
118    fn from_str(s: &str) -> Result<Self> {
119        Self::new(s)
120    }
121}
122
123impl TryFrom<&str> for Ident {
124    type Error = Error;
125
126    fn try_from(s: &str) -> Result<Self> {
127        Self::new(s)
128    }
129}
130
131impl fmt::Display for Ident {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.write_str(self)
134    }
135}
136
137impl fmt::Debug for Ident {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        f.debug_tuple("Ident").field(&self.as_ref()).finish()
140    }
141}
142
143#[cfg(test)]
144#[allow(clippy::unwrap_used)]
145mod tests {
146    use super::{Error, Ident};
147
148    // Invalid ident examples
149    const INVALID_EMPTY: &str = "";
150    const INVALID_CHAR: &str = "argon2;d";
151    const INVALID_TOO_LONG: &str = "012345678911234567892123456789312";
152    const INVALID_CHAR_AND_TOO_LONG: &str = "0!2345678911234567892123456789312";
153
154    #[test]
155    fn parse_valid() {
156        let valid_examples = ["6", "x", "argon2d", "01234567891123456789212345678931"];
157
158        for &example in &valid_examples {
159            assert_eq!(example, &*Ident::new(example).unwrap());
160        }
161    }
162
163    #[test]
164    fn reject_empty() {
165        assert_eq!(Ident::new(INVALID_EMPTY), Err(Error::ParamNameInvalid));
166    }
167
168    #[test]
169    fn reject_invalid() {
170        assert_eq!(Ident::new(INVALID_CHAR), Err(Error::ParamNameInvalid));
171    }
172
173    #[test]
174    fn reject_too_long() {
175        assert_eq!(Ident::new(INVALID_TOO_LONG), Err(Error::ParamNameInvalid));
176    }
177
178    #[test]
179    fn reject_invalid_char_and_too_long() {
180        assert_eq!(
181            Ident::new(INVALID_CHAR_AND_TOO_LONG),
182            Err(Error::ParamNameInvalid)
183        );
184    }
185}