Coverage Report

Created: 2025-11-15 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/password-hash-0.4.2/src/ident.rs
Line
Count
Source
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
19
use crate::{Error, Result};
20
use core::{fmt, ops::Deref, str};
21
22
/// Algorithm or parameter identifier.
23
///
24
/// This type encompasses both the "function symbolic name" and "parameter name"
25
/// use cases as described in the [PHC string format specification][1].
26
///
27
/// # Constraints
28
/// - ASCII-encoded string consisting of the characters `[a-z0-9-]`
29
///   (lowercase letters, digits, and the minus sign)
30
/// - Minimum length: 1 ASCII character (i.e. 1-byte)
31
/// - Maximum length: 32 ASCII characters (i.e. 32-bytes)
32
///
33
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
34
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
35
pub struct Ident<'a>(&'a str);
36
37
impl<'a> Ident<'a> {
38
    /// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
39
    ///
40
    /// This value corresponds to the maximum size of a function symbolic names
41
    /// and parameter names according to the PHC string format.
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
    const MAX_LENGTH: usize = 32;
47
48
    /// Parse an [`Ident`] from a string.
49
    ///
50
    /// String must conform to the constraints given in the type-level
51
    /// documentation.
52
0
    pub const fn new(s: &'a str) -> Result<Self> {
53
0
        let input = s.as_bytes();
54
55
0
        match input.len() {
56
0
            1..=Self::MAX_LENGTH => {
57
0
                let mut i = 0;
58
59
0
                while i < input.len() {
60
0
                    if !matches!(input[i], b'a'..=b'z' | b'0'..=b'9' | b'-') {
61
0
                        return Err(Error::ParamNameInvalid);
62
0
                    }
63
64
0
                    i += 1;
65
                }
66
67
0
                Ok(Self(s))
68
            }
69
0
            _ => Err(Error::ParamNameInvalid),
70
        }
71
0
    }
72
73
    /// Parse an [`Ident`] from a string, panicking on parse errors.
74
    ///
75
    /// This function exists as a workaround for `unwrap` not yet being
76
    /// stable in `const fn` contexts, and is intended to allow the result to
77
    /// be bound to a constant value.
78
0
    pub const fn new_unwrap(s: &'a str) -> Self {
79
0
        assert!(!s.is_empty(), "PHC ident string can't be empty");
80
0
        assert!(s.len() <= Self::MAX_LENGTH, "PHC ident string too long");
81
82
0
        match Self::new(s) {
83
0
            Ok(ident) => ident,
84
0
            Err(_) => panic!("invalid PHC string format identifier"),
85
        }
86
0
    }
87
88
    /// Borrow this ident as a `str`
89
0
    pub fn as_str(&self) -> &'a str {
90
0
        self.0
91
0
    }
92
}
93
94
impl<'a> AsRef<str> for Ident<'a> {
95
0
    fn as_ref(&self) -> &str {
96
0
        self.as_str()
97
0
    }
98
}
99
100
impl<'a> Deref for Ident<'a> {
101
    type Target = str;
102
103
0
    fn deref(&self) -> &str {
104
0
        self.as_str()
105
0
    }
106
}
107
108
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
109
// the `str` the value is being parsed from.
110
impl<'a> TryFrom<&'a str> for Ident<'a> {
111
    type Error = Error;
112
113
0
    fn try_from(s: &'a str) -> Result<Self> {
114
0
        Self::new(s)
115
0
    }
116
}
117
118
impl<'a> fmt::Display for Ident<'a> {
119
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120
0
        f.write_str(&*self)
121
0
    }
122
}
123
124
impl<'a> fmt::Debug for Ident<'a> {
125
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126
0
        f.debug_tuple("Ident").field(&self.as_ref()).finish()
127
0
    }
128
}
129
130
#[cfg(test)]
131
mod tests {
132
    use super::{Error, Ident};
133
134
    // Invalid ident examples
135
    const INVALID_EMPTY: &str = "";
136
    const INVALID_CHAR: &str = "argon2;d";
137
    const INVALID_TOO_LONG: &str = "012345678911234567892123456789312";
138
    const INVALID_CHAR_AND_TOO_LONG: &str = "0!2345678911234567892123456789312";
139
140
    #[test]
141
    fn parse_valid() {
142
        let valid_examples = ["6", "x", "argon2d", "01234567891123456789212345678931"];
143
144
        for &example in &valid_examples {
145
            assert_eq!(example, &*Ident::new(example).unwrap());
146
        }
147
    }
148
149
    #[test]
150
    fn reject_empty() {
151
        assert_eq!(Ident::new(INVALID_EMPTY), Err(Error::ParamNameInvalid));
152
    }
153
154
    #[test]
155
    fn reject_invalid() {
156
        assert_eq!(Ident::new(INVALID_CHAR), Err(Error::ParamNameInvalid));
157
    }
158
159
    #[test]
160
    fn reject_too_long() {
161
        assert_eq!(Ident::new(INVALID_TOO_LONG), Err(Error::ParamNameInvalid));
162
    }
163
164
    #[test]
165
    fn reject_invalid_char_and_too_long() {
166
        assert_eq!(
167
            Ident::new(INVALID_CHAR_AND_TOO_LONG),
168
            Err(Error::ParamNameInvalid)
169
        );
170
    }
171
}