1use 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
10pub type Pair<'a> = (Ident, Value<'a>);
12
13pub(crate) const PAIR_DELIMITER: char = '=';
15
16pub(crate) const PARAMS_DELIMITER: char = ',';
18
19const MAX_LENGTH: usize = 127;
21
22const INVARIANT_VIOLATED_MSG: &str = "PHC params invariant violated";
25
26#[derive(Clone, Default, Eq, PartialEq)]
40pub struct ParamsString(StringBuf<MAX_LENGTH>);
41
42impl ParamsString {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 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 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 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 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 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 pub fn as_bytes(&self) -> &[u8] {
92 self.as_str().as_bytes()
93 }
94
95 pub fn as_str(&self) -> &str {
97 self.0.as_ref()
98 }
99
100 pub fn len(&self) -> usize {
102 self.as_str().len()
103 }
104
105 pub fn is_empty(&self) -> bool {
107 self.len() == 0
108 }
109
110 pub fn iter(&self) -> Iter<'_> {
112 Iter::new(self.as_str())
113 }
114
115 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 pub fn get_str(&self, name: impl TryInto<Ident>) -> Option<&str> {
130 self.get(name).map(|value| value.as_str())
131 }
132
133 pub fn get_decimal(&self, name: impl TryInto<Ident>) -> Option<Decimal> {
137 self.get(name).and_then(|value| value.decimal().ok())
138 }
139
140 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 for mut param in s.split(PARAMS_DELIMITER).map(|p| p.split(PAIR_DELIMITER)) {
177 param
179 .next()
180 .ok_or(Error::ParamNameInvalid)
181 .and_then(Ident::from_str)?;
182
183 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#[derive(Debug)]
233pub struct Iter<'a> {
234 inner: Option<str::Split<'a, char>>,
235}
236
237impl<'a> Iter<'a> {
238 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 #[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 #[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}