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}