Skip to main content

phc/
params.rs

1//! Algorithm parameters.
2
3use crate::{Decimal, Error, Ident, Result, StringBuf, Value};
4use base64ct::{Base64Unpadded as B64, Encoding};
5use core::{
6    fmt::{self, Write as _},
7    str::{self, FromStr},
8};
9
10/// Individual parameter name/value pair.
11pub type Pair<'a> = (Ident, Value<'a>);
12
13/// Delimiter character between name/value pairs.
14pub(crate) const PAIR_DELIMITER: char = '=';
15
16/// Delimiter character between parameters.
17pub(crate) const PARAMS_DELIMITER: char = ',';
18
19/// Maximum serialized length of parameters.
20const MAX_LENGTH: usize = 127;
21
22/// Error message used with `expect` for when internal invariants are violated
23/// (i.e. the contents of a [`ParamsString`] should always be valid)
24const INVARIANT_VIOLATED_MSG: &str = "PHC params invariant violated";
25
26/// Algorithm parameter string.
27///
28/// The [PHC string format specification][1] defines a set of optional
29/// algorithm-specific name/value pairs which can be encoded into a
30/// PHC-formatted parameter string as follows:
31///
32/// ```text
33/// $<param>=<value>(,<param>=<value>)*
34/// ```
35///
36/// This type represents that set of parameters.
37///
38/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
39#[derive(Clone, Default, Eq, PartialEq)]
40pub struct ParamsString(StringBuf<MAX_LENGTH>);
41
42impl ParamsString {
43    /// Create new empty [`ParamsString`].
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Add the given byte value to the [`ParamsString`], encoding it as "B64".
49    pub fn add_b64_bytes(&mut self, name: impl TryInto<Ident>, bytes: &[u8]) -> Result<()> {
50        if !self.is_empty() {
51            self.0
52                .write_char(PARAMS_DELIMITER)
53                .map_err(|_| Error::ParamsMaxExceeded)?
54        }
55
56        let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
57
58        // Add param name
59        let offset = self.0.length;
60        if write!(self.0, "{name}=").is_err() {
61            self.0.length = offset;
62            return Err(Error::ParamsMaxExceeded);
63        }
64
65        // Encode B64 value
66        let offset = self.0.length as usize;
67        let written = B64::encode(bytes, &mut self.0.bytes[offset..])?.len();
68
69        self.0.length += written as u8;
70        Ok(())
71    }
72
73    /// Add a key/value pair with a decimal value to the [`ParamsString`].
74    pub fn add_decimal(&mut self, name: impl TryInto<Ident>, value: Decimal) -> Result<()> {
75        let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
76        self.add(name, value)
77    }
78
79    /// Add a key/value pair with a string value to the [`ParamsString`].
80    pub fn add_str<'a>(
81        &mut self,
82        name: impl TryInto<Ident>,
83        value: impl TryInto<Value<'a>>,
84    ) -> Result<()> {
85        let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
86        let value = value.try_into().map_err(|_| Error::ParamValueInvalid)?;
87        self.add(name, value)
88    }
89
90    /// Borrow the contents of this [`ParamsString`] as a byte slice.
91    pub fn as_bytes(&self) -> &[u8] {
92        self.as_str().as_bytes()
93    }
94
95    /// Borrow the contents of this [`ParamsString`] as a `str`.
96    pub fn as_str(&self) -> &str {
97        self.0.as_ref()
98    }
99
100    /// Get the count of the number ASCII characters in this [`ParamsString`].
101    pub fn len(&self) -> usize {
102        self.as_str().len()
103    }
104
105    /// Is this set of parameters empty?
106    pub fn is_empty(&self) -> bool {
107        self.len() == 0
108    }
109
110    /// Iterate over the parameters.
111    pub fn iter(&self) -> Iter<'_> {
112        Iter::new(self.as_str())
113    }
114
115    /// Get a parameter [`Value`] by name.
116    pub fn get(&self, name: impl TryInto<Ident>) -> Option<Value<'_>> {
117        let name = name.try_into().ok()?;
118
119        for (n, v) in self.iter() {
120            if name == n {
121                return Some(v);
122            }
123        }
124
125        None
126    }
127
128    /// Get a parameter as a `str`.
129    pub fn get_str(&self, name: impl TryInto<Ident>) -> Option<&str> {
130        self.get(name).map(|value| value.as_str())
131    }
132
133    /// Get a parameter as a [`Decimal`].
134    ///
135    /// See [`Value::decimal`] for format information.
136    pub fn get_decimal(&self, name: impl TryInto<Ident>) -> Option<Decimal> {
137        self.get(name).and_then(|value| value.decimal().ok())
138    }
139
140    /// Add a value to this [`ParamsString`] using the provided callback.
141    fn add(&mut self, name: Ident, value: impl fmt::Display) -> Result<()> {
142        if self.get(name).is_some() {
143            return Err(Error::ParamNameDuplicated);
144        }
145
146        let orig_len = self.0.length;
147
148        if !self.is_empty() {
149            self.0
150                .write_char(PARAMS_DELIMITER)
151                .map_err(|_| Error::ParamsMaxExceeded)?
152        }
153
154        if write!(self.0, "{name}={value}").is_err() {
155            self.0.length = orig_len;
156            return Err(Error::ParamsMaxExceeded);
157        }
158
159        Ok(())
160    }
161}
162
163impl FromStr for ParamsString {
164    type Err = Error;
165
166    fn from_str(s: &str) -> Result<Self> {
167        if s.len() > MAX_LENGTH {
168            return Err(Error::ParamsMaxExceeded);
169        }
170
171        if s.is_empty() {
172            return Ok(ParamsString::new());
173        }
174
175        // Validate the string is well-formed
176        for mut param in s.split(PARAMS_DELIMITER).map(|p| p.split(PAIR_DELIMITER)) {
177            // Validate name
178            param
179                .next()
180                .ok_or(Error::ParamNameInvalid)
181                .and_then(Ident::from_str)?;
182
183            // Validate value
184            param
185                .next()
186                .ok_or(Error::ParamValueInvalid)
187                .and_then(Value::try_from)?;
188
189            if param.next().is_some() {
190                return Err(Error::ParamValueInvalid);
191            }
192        }
193
194        let mut bytes = [0u8; MAX_LENGTH];
195        bytes[..s.len()].copy_from_slice(s.as_bytes());
196
197        Ok(Self(StringBuf {
198            bytes,
199            length: s.len() as u8,
200        }))
201    }
202}
203
204impl<'a> FromIterator<Pair<'a>> for ParamsString {
205    fn from_iter<I>(iter: I) -> Self
206    where
207        I: IntoIterator<Item = Pair<'a>>,
208    {
209        let mut params = ParamsString::new();
210
211        for pair in iter {
212            params.add_str(pair.0, pair.1).expect("PHC params error");
213        }
214
215        params
216    }
217}
218
219impl fmt::Display for ParamsString {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        f.write_str(self.as_str())
222    }
223}
224
225impl fmt::Debug for ParamsString {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        f.debug_map().entries(self.iter()).finish()
228    }
229}
230
231/// Iterator over algorithm parameters stored in a [`ParamsString`] struct.
232#[derive(Debug)]
233pub struct Iter<'a> {
234    inner: Option<str::Split<'a, char>>,
235}
236
237impl<'a> Iter<'a> {
238    /// Create a new [`Iter`].
239    fn new(s: &'a str) -> Self {
240        if s.is_empty() {
241            Self { inner: None }
242        } else {
243            Self {
244                inner: Some(s.split(PARAMS_DELIMITER)),
245            }
246        }
247    }
248}
249
250impl<'a> Iterator for Iter<'a> {
251    type Item = Pair<'a>;
252
253    fn next(&mut self) -> Option<Pair<'a>> {
254        let mut param = self.inner.as_mut()?.next()?.split(PAIR_DELIMITER);
255
256        let name = param
257            .next()
258            .and_then(|id| Ident::from_str(id).ok())
259            .expect(INVARIANT_VIOLATED_MSG);
260
261        let value = param
262            .next()
263            .and_then(|value| Value::try_from(value).ok())
264            .expect(INVARIANT_VIOLATED_MSG);
265
266        debug_assert_eq!(param.next(), None);
267        Some((name, value))
268    }
269}
270
271#[cfg(test)]
272#[allow(clippy::unwrap_used)]
273mod tests {
274    use super::{Error, Ident, ParamsString, Value};
275
276    #[cfg(feature = "alloc")]
277    use alloc::string::ToString;
278    use core::str::FromStr;
279
280    #[test]
281    fn add() {
282        let mut params = ParamsString::new();
283        params.add_str("a", "1").unwrap();
284        params.add_decimal("b", 2).unwrap();
285        params.add_str("c", "3").unwrap();
286
287        assert_eq!(params.iter().count(), 3);
288        assert_eq!(params.get_decimal("a").unwrap(), 1);
289        assert_eq!(params.get_decimal("b").unwrap(), 2);
290        assert_eq!(params.get_decimal("c").unwrap(), 3);
291    }
292
293    #[test]
294    #[cfg(feature = "alloc")]
295    fn add_b64_bytes() {
296        let mut params = ParamsString::new();
297        params.add_b64_bytes("a", &[1]).unwrap();
298        params.add_b64_bytes("b", &[2, 3]).unwrap();
299        params.add_b64_bytes("c", &[4, 5, 6]).unwrap();
300        assert_eq!(params.to_string(), "a=AQ,b=AgM,c=BAUG");
301    }
302
303    #[test]
304    fn duplicate_names() {
305        let name = Ident::new("a").unwrap();
306        let mut params = ParamsString::new();
307        params.add_decimal(name, 1).unwrap();
308
309        let err = params.add_decimal(name, 2u32).err().unwrap();
310        assert_eq!(err, Error::ParamNameDuplicated);
311    }
312
313    #[test]
314    fn from_iter() {
315        let params = ParamsString::from_iter(
316            [
317                (Ident::new("a").unwrap(), Value::try_from("1").unwrap()),
318                (Ident::new("b").unwrap(), Value::try_from("2").unwrap()),
319                (Ident::new("c").unwrap(), Value::try_from("3").unwrap()),
320            ]
321            .iter()
322            .cloned(),
323        );
324
325        assert_eq!(params.iter().count(), 3);
326        assert_eq!(params.get_decimal("a").unwrap(), 1);
327        assert_eq!(params.get_decimal("b").unwrap(), 2);
328        assert_eq!(params.get_decimal("c").unwrap(), 3);
329    }
330
331    #[test]
332    fn iter() {
333        let mut params = ParamsString::new();
334        params.add_str("a", "1").unwrap();
335        params.add_str("b", "2").unwrap();
336        params.add_str("c", "3").unwrap();
337
338        let mut i = params.iter();
339
340        for (name, value) in &[("a", "1"), ("b", "2"), ("c", "3")] {
341            let name = Ident::new(name).unwrap();
342            let value = Value::try_from(*value).unwrap();
343            assert_eq!(i.next(), Some((name, value)));
344        }
345
346        assert_eq!(i.next(), None);
347    }
348
349    //
350    // `FromStr` tests
351    //
352
353    #[test]
354    fn parse_empty() {
355        let params = ParamsString::from_str("").unwrap();
356        assert!(params.is_empty());
357    }
358
359    #[test]
360    fn parse_one() {
361        let params = ParamsString::from_str("a=1").unwrap();
362        assert_eq!(params.iter().count(), 1);
363        assert_eq!(params.get("a").unwrap().decimal().unwrap(), 1);
364    }
365
366    #[test]
367    fn parse_many() {
368        let params = ParamsString::from_str("a=1,b=2,c=3").unwrap();
369        assert_eq!(params.iter().count(), 3);
370        assert_eq!(params.get_decimal("a").unwrap(), 1);
371        assert_eq!(params.get_decimal("b").unwrap(), 2);
372        assert_eq!(params.get_decimal("c").unwrap(), 3);
373    }
374
375    //
376    // `Display` tests
377    //
378
379    #[test]
380    #[cfg(feature = "alloc")]
381    fn display_empty() {
382        let params = ParamsString::new();
383        assert_eq!(params.to_string(), "");
384    }
385
386    #[test]
387    #[cfg(feature = "alloc")]
388    fn display_one() {
389        let params = ParamsString::from_str("a=1").unwrap();
390        assert_eq!(params.to_string(), "a=1");
391    }
392
393    #[test]
394    #[cfg(feature = "alloc")]
395    fn display_many() {
396        let params = ParamsString::from_str("a=1,b=2,c=3").unwrap();
397        assert_eq!(params.to_string(), "a=1,b=2,c=3");
398    }
399}