blather/
params.rs

1//! The `Params` buffer is a set of unorderded key/value pairs, with unique
2//! keys.
3//!
4//! It's similar to a `HashMap`, but has constraints on key names values, due
5//! to having a defined serialization format.
6
7use std::{collections::HashMap, fmt, str::FromStr};
8
9use bytes::{BufMut, BytesMut};
10
11use super::validators::{validate_param_key, validate_param_value};
12
13use crate::err::{Error, ParamError};
14
15/// Key/value parameters storage with helper methods to make adding and getting
16/// common value types slightly more ergonomic and using a plain `HashMap`.
17///
18/// Uses `String`s for both keys and values internally.
19// ToDo: Rename `Params` to `Fields`, and call a key/value pair a `field`.
20#[repr(transparent)]
21#[derive(Debug, Clone, Default)]
22pub struct Params(HashMap<String, String>);
23
24impl Params {
25  /// Create a new empty parameters object.
26  #[must_use]
27  pub fn new() -> Self {
28    Self::default()
29  }
30
31  /// Reset all the key/values in `Params` object.
32  pub fn clear(&mut self) {
33    self.0.clear();
34  }
35
36  /// Return the number of key/value pairs in the parameter buffer.
37  #[must_use]
38  pub fn len(&self) -> usize {
39    self.0.len()
40  }
41
42  /// Returns `true` if the `Params` collection does not contain any key/value
43  /// pairs.
44  #[must_use]
45  pub fn is_empty(&self) -> bool {
46    self.0.is_empty()
47  }
48
49  /// Return reference to inner `HashMap`.
50  #[must_use]
51  pub const fn inner(&self) -> &HashMap<String, String> {
52    &self.0
53  }
54
55  /// Add a parameter.
56  ///
57  /// ```
58  /// use blather::{Params, Error, ParamError};
59  ///
60  /// let mut params = Params::new();
61  ///
62  /// // Add a parameter
63  /// params.add("hello".into(), "world".into()).unwrap();
64  ///
65  /// // Attempt to add a parameter with an invalid key
66  /// let res = params.add("hell o".into(), "world".into());
67  /// let Err(Error::Param(ParamError::Key(msg))) = res else {
68  ///   panic!("Unexpectedly not ParamError::Key(_)");
69  /// };
70  /// assert_eq!(msg, "invalid character");
71  /// ```
72  ///
73  /// # Errors
74  /// [`Error::Param`] means the input parameters are invalid.  It will
75  /// contains a [`ParamError`] indicating whether the key or the value is
76  /// invalid.
77  ///
78  /// # See also
79  /// For more ergonomics with automatic conversion to strings, see
80  /// [`Params::add_param`].
81  pub fn add(&mut self, key: String, value: String) -> Result<(), Error> {
82    validate_param_key(&key)?;
83    validate_param_value(&value)?;
84
85    self.0.insert(key, value);
86
87    Ok(())
88  }
89
90  /// Add a parameter to the parameter.
91  ///
92  /// Both the `key` and `value` are anything convertable using the `ToString`
93  /// trait.
94  ///
95  /// # Examples
96  /// ```
97  /// use blather::Params;
98  ///
99  /// let mut params = Params::new();
100  /// params.add_param("integer", 42).unwrap();
101  /// params.add_param("string", "hello").unwrap();
102  /// ```
103  ///
104  /// # Errors
105  /// [`Error::Param`] means the input parameters are invalid.  It will
106  /// contains a [`ParamError`] indicating whether the key or the value is
107  /// invalid.
108  ///
109  /// # See also
110  /// If the value is a string, prefer to use [`Params::add_str()`].
111  #[inline]
112  #[allow(clippy::needless_pass_by_value)]
113  pub fn add_param(
114    &mut self,
115    key: impl ToString,
116    value: impl ToString
117  ) -> Result<(), Error> {
118    let key = key.to_string();
119    let value = value.to_string();
120
121    validate_param_key(&key)?;
122    validate_param_value(&value)?;
123
124    self.0.insert(key, value);
125
126    Ok(())
127  }
128
129  /// Add a string parameter to the parameter.
130  ///
131  /// This function can, under some circumstances, be a little more effecient
132  /// than [`Params::add_param()`], but offers less flexibility.
133  ///
134  /// # Errors
135  /// If either the key or the value are invalid, [`Error::BadFormat`] means
136  /// the input parameters are invalid.
137  #[inline]
138  #[allow(clippy::needless_pass_by_value)]
139  pub fn add_str(
140    &mut self,
141    key: impl ToString,
142    value: impl Into<String>
143  ) -> Result<(), Error> {
144    let key = key.to_string();
145    let value = value.into();
146
147    validate_param_key(&key)?;
148    validate_param_value(&value)?;
149
150    self.0.insert(key, value);
151
152    Ok(())
153  }
154
155  /// Add a boolean parameter.
156  ///
157  /// # Examples
158  /// ```
159  /// use blather::Params;
160  ///
161  /// let mut params = Params::new();
162  /// params.add_bool("should_be_true", true).unwrap();
163  /// params.add_bool("should_be_false", false).unwrap();
164  /// assert!(matches!(params.get_bool("should_be_true"), Ok(Some(true))));
165  /// assert!(matches!(params.get_bool("should_be_false"), Ok(Some(false))));
166  /// ```
167  ///
168  /// # Notes
169  /// - Applications should not make assumptions about the specific string
170  ///   value added by this function.  Do not treat boolean values as strings;
171  ///   use the [`get_bool()`](Self::get_bool) method instead.
172  ///
173  /// # Errors
174  /// [`Error::Param`] with [`ParamError::Key`] indicates that the key format
175  /// is invalid.
176  #[inline]
177  #[allow(clippy::needless_pass_by_value)]
178  pub fn add_bool(
179    &mut self,
180    key: impl ToString,
181    value: bool
182  ) -> Result<(), Error> {
183    let key = key.to_string();
184    let v = if value { "true" } else { "false" };
185
186    validate_param_key(&key)?;
187
188    self.add_str(key, v)
189  }
190
191  /// Returns `true` if the parameter with `key` exists.  Returns `false`
192  /// otherwise.
193  ///
194  /// ```
195  /// use blather::Params;
196  ///
197  /// let mut params = Params::new();
198  ///
199  /// assert!(!params.contains("nonexistent"));
200  ///
201  /// params.add_str("Exists", "Very much").unwrap();
202  /// assert!(params.contains("Exists"));
203  /// ```
204  #[must_use]
205  pub fn contains(&self, key: &str) -> bool {
206    self.0.contains_key(key)
207  }
208
209  /// Call `FromStr` implementation to extract a value given a key.
210  ///
211  /// # Examples
212  /// ```
213  /// use blather::{Params, Error, ParamError};
214  ///
215  /// let mut params = Params::new();
216  ///
217  /// params.add_param("arthur", 42);
218  /// let fourtytwo = params.get_fromstr::<u32, _>("arthur").unwrap();
219  /// assert_eq!(fourtytwo, Some(42));
220  ///
221  /// let nonexist = params.get_fromstr::<u32, _>("ford");
222  /// let Ok(None) = nonexist else {
223  ///   panic!("Unexpectedly not `Ok(None)`");
224  /// };
225  ///
226  /// params.add_param("notint", "hello");
227  /// let res = params.get_fromstr::<u32, _>("notint");
228  /// let Err(Error::Param(ParamError::Value(msg))) = res else {
229  ///   panic!("Unexpectedly not Error::Param(ParamError::Value(_))");
230  /// };
231  /// assert_eq!(msg, "invalid digit found in string");
232  /// ```
233  ///
234  /// # Errors
235  /// Returns [`Error::Param`] containing [`ParamError::Value`] if the value
236  /// can not be parsed.
237  pub fn get_fromstr<T, E>(&self, key: &str) -> Result<Option<T>, Error>
238  where
239    T: FromStr<Err = E>,
240    E: std::fmt::Display
241  {
242    self.get_str(key).map_or_else(
243      || Ok(None),
244      |val| {
245        T::from_str(val).map_or_else(
246          |e| Err(Error::Param(ParamError::Value(e.to_string()))),
247          |v| Ok(Some(v))
248        )
249      }
250    )
251  }
252
253  /// Get string representation of a value for a requested key.
254  ///
255  /// Returns `None` if the key is not found in the inner storage.  Returns
256  /// `Some(&str)` if parameter exists.
257  #[must_use]
258  pub fn get_str(&self, key: &str) -> Option<&str> {
259    let kv = self.0.get_key_value(key);
260    if let Some((_k, v)) = kv {
261      return Some(v);
262    }
263    None
264  }
265
266  /// Get a boolean value; return error if key wasn't found.
267  ///
268  /// # Errors
269  /// [`Error::BadFormat`] means the input parameters are invalid.
270  pub fn get_bool(&self, key: &str) -> Result<Option<bool>, Error> {
271    let Some(v) = self.get_str(key) else {
272      return Ok(None);
273    };
274
275    let v = v.to_ascii_lowercase();
276    match v.as_ref() {
277      "y" | "yes" | "t" | "true" | "1" | "on" => Ok(Some(true)),
278      "n" | "no" | "f" | "false" | "0" | "off" => Ok(Some(false)),
279      _ => Err(Error::Param(ParamError::Value("not boolean".into())))
280    }
281  }
282
283  /// Consume the `Params` buffer and return its internal `HashMap`.
284  #[must_use]
285  pub fn into_inner(self) -> HashMap<String, String> {
286    self.0
287  }
288
289  /// Calculate the size of the buffer in serialized form.
290  /// Each entry will be a newline terminated utf-8 line.
291  /// Last line will be a single newline character.
292  #[must_use]
293  pub fn calc_buf_size(&self) -> usize {
294    let mut size = 0;
295    for (key, value) in &self.0 {
296      size += key.len() + 1; // including ' '
297      size += value.len() + 1; // including '\n'
298    }
299    size + 1 // terminating '\n'
300  }
301
302  /// Serialize `Params` buffer into a vector of bytes for transmission.
303  #[must_use]
304  pub fn serialize(&self) -> Vec<u8> {
305    let mut buf = Vec::new();
306
307    for (key, value) in &self.0 {
308      let k = key.as_bytes();
309      let v = value.as_bytes();
310      for a in k {
311        buf.push(*a);
312      }
313      buf.push(b' ');
314      for a in v {
315        buf.push(*a);
316      }
317      buf.push(b'\n');
318    }
319
320    buf.push(b'\n');
321
322    buf
323  }
324
325  /// Write the Params to a buffer.
326  pub fn encoder_write(&self, buf: &mut BytesMut) {
327    // Calculate the required buffer size
328    let size = self.calc_buf_size();
329
330    // Reserve space
331    buf.reserve(size);
332
333    // Write data to output buffer
334    for (key, value) in &self.0 {
335      buf.put(key.as_bytes());
336      buf.put_u8(b' ');
337      buf.put(value.as_bytes());
338      buf.put_u8(b'\n');
339    }
340    buf.put_u8(b'\n');
341  }
342}
343
344
345impl Params {
346  /// Encode a binary buffer into a parameter value.
347  ///
348  /// The value is stored as a base85 (RFC1924 variant) string.
349  ///
350  /// # Errors
351  /// [`Error::Param`] is returned if the key is invalid, or if the base85
352  /// encoder inserts newlines.
353  #[cfg(feature = "bin")]
354  #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
355  pub fn encode_buf(
356    &mut self,
357    key: impl Into<String>,
358    buf: &[u8]
359  ) -> Result<(), Error> {
360    self.add(key.into(), base85::encode(buf))?;
361    Ok(())
362  }
363
364  /// Decode a binary buffer from a parameter value.
365  ///
366  /// The value is assumed to be base85 (RFC1924 variant) string.
367  ///
368  /// # Errors
369  /// [`Error::Param`] with [`ParamError::Value`] is returned if the value is
370  /// not RFC1924-compliant base85.
371  #[cfg(feature = "bin")]
372  #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
373  pub fn decode_buf(&self, key: &str) -> Result<Option<Vec<u8>>, Error> {
374    let Some(b85) = self.get_str(key) else {
375      return Ok(None);
376    };
377    let buf = base85::decode(b85)
378      .map_err(|_| Error::Param(ParamError::Value("invalid base85".into())))?;
379    Ok(Some(buf))
380  }
381
382  /// Serialize a type (using bincode), transform into base85 and store as a
383  /// parameter.
384  ///
385  /// # Errors
386  /// [`Error::Param`] with [`ParamError::Key`] means the key name is
387  /// invalid.  [`Error::SerializeError`] means `data` could not be serialized.
388  #[cfg(feature = "bincode")]
389  #[cfg_attr(docsrs, doc(cfg(feature = "bincode")))]
390  pub fn encode<E>(
391    &mut self,
392    key: impl Into<String>,
393    data: E
394  ) -> Result<(), Error>
395  where
396    E: bincode::Encode
397  {
398    let conf = bincode::config::standard();
399
400    let buf = bincode::encode_to_vec(data, conf)
401      .map_err(|e| Error::SerializeError(e.to_string()))?;
402
403    self.encode_buf(key, &buf)?;
404
405    Ok(())
406  }
407
408  /// Decode a parameter base85 value, then deserialize it into requested type.
409  ///
410  /// # Errors
411  /// [`Error::Param`] with [`ParamError::Value`] means the parameter value
412  /// could not be deserialized.
413  #[cfg(feature = "bincode")]
414  #[cfg_attr(docsrs, doc(cfg(feature = "bincode")))]
415  pub fn decode<D>(&self, key: &str) -> Result<Option<D>, Error>
416  where
417    D: bincode::Decode<()>
418  {
419    let Some(buf) = self.decode_buf(key)? else {
420      return Ok(None);
421    };
422
423    let conf = bincode::config::standard();
424
425    let (data, _) = bincode::decode_from_slice(&buf, conf)
426      .map_err(|e| Error::Param(ParamError::Value(e.to_string())))?;
427
428    Ok(Some(data))
429  }
430}
431
432#[cfg(test)]
433#[cfg(feature = "bin")]
434mod buf_tests {
435  use super::Params;
436
437  #[test]
438  fn enc_dec() {
439    let buf = orphanage::buf::random(2048);
440
441    let mut params = Params::new();
442    params.encode_buf("Bytes", &buf).unwrap();
443
444    let buf2 = params.decode_buf("Bytes").unwrap().unwrap();
445    assert_eq!(buf, buf2);
446  }
447}
448
449
450impl Params {
451  /// Add an anonymous vector of records.
452  ///
453  /// Only one list of elements should be added to a `Params` buffer.  If a
454  /// single `Params` needs to contain multiple lists, use
455  /// [`Params::encode_named_list()`] instead.
456  ///
457  /// ```
458  /// use blather::{Params, Error};
459  ///
460  /// struct Agent {
461  ///   name: String,
462  ///   age: u8
463  /// }
464  ///
465  /// let mut params = Params::default();
466  ///
467  /// let mut agents = vec![
468  ///   Agent {
469  ///     name: "frank".into(),
470  ///     age: 42
471  ///   },
472  ///   Agent {
473  ///     name: "anon".into(),
474  ///     age: 32
475  ///   }
476  /// ];
477  ///
478  /// params.encode_anon_list(agents.into_iter(), |rec, v| {
479  ///   v.push(("Name".into(), rec.name));
480  ///   v.push(("Age".into(), rec.age.to_string()));
481  ///   Ok::<(), Error>(())
482  /// });
483  ///
484  /// assert_eq!(params.get_fromstr::<usize, _>("Count").unwrap(), Some(2));
485  ///
486  /// assert_eq!(params.get_str("Name.0"), Some("frank"));
487  /// assert_eq!(params.get_fromstr::<u8, _>("Age.0").unwrap(), Some(42));
488  ///
489  /// assert_eq!(params.get_str("Name.1"), Some("anon"));
490  /// assert_eq!(params.get_fromstr::<u8, _>("Age.1").unwrap(), Some(32));
491  /// ```
492  ///
493  /// # Errors
494  /// Returns an application-defined error `E`, with the constraint that it
495  /// must be able to convert a `blather::Error` into `E`.
496  pub fn encode_anon_list<T, I, F, E>(&mut self, it: I, f: F) -> Result<(), E>
497  where
498    I: IntoIterator<Item = T>,
499    F: Fn(T, &mut Vec<(String, String)>) -> Result<(), E>,
500    E: From<Error>
501  {
502    let mut count = 0;
503
504    // Used for capacity
505    let mut recs = 0;
506
507    for (idx, rec) in it.into_iter().enumerate() {
508      let mut rec_kv = Vec::with_capacity(recs);
509
510      // Call closure to generate records
511      f(rec, &mut rec_kv)?;
512
513      // Predict how many records to preallocate
514      recs = std::cmp::max(recs, rec_kv.len());
515
516      // Move indexed record fields into Params
517      for (k, v) in rec_kv {
518        let key = format!("{k}.{idx}");
519        self.add(key, v)?;
520      }
521
522      // Count record
523      count += 1;
524    }
525
526    self.add_param("Count", count)?;
527
528    Ok(())
529  }
530
531  /// Decode an anonymous list previous generated using
532  /// [`Params::encode_anon_list()`].
533  ///
534  /// ```
535  /// use blather::{Params, Error};
536  ///
537  /// struct Agent {
538  ///   name: String,
539  ///   age: u8
540  /// }
541  ///
542  /// let mut params = Params::new();
543  /// params.add_str("Name.0", "frank").unwrap();
544  /// params.add_param("Age.0", 42).unwrap();
545  /// params.add_str("Name.1", "anon").unwrap();
546  /// params.add_param("Age.1", 32).unwrap();
547  /// params.add_param("Count", 2).unwrap();
548  ///
549  /// let v = params
550  ///   .decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
551  ///     let name = (*recmap.get("Name").unwrap()).to_string();
552  ///     let age = recmap.get("Age").unwrap();
553  ///     let age = age.parse::<u8>().unwrap();
554  ///     Ok(Agent { name, age })
555  ///   })
556  ///   .unwrap();
557  ///
558  /// assert_eq!(v[0].name, "frank");
559  /// assert_eq!(v[0].age, 42);
560  /// assert_eq!(v[1].name, "anon");
561  /// assert_eq!(v[1].age, 32);
562  /// assert_eq!(v.len(), 2);
563  /// ```
564  ///
565  /// # Errors
566  /// Returns an application-defined error `E`, with the constraint that it
567  /// must be able to convert a `blather::Error` into `E`.
568  pub fn decode_anon_list<T, F, E>(
569    &self,
570    keys: &[&str],
571    f: F
572  ) -> Result<Vec<T>, E>
573  where
574    F: Fn(&HashMap<&str, &str>) -> Result<T, E>,
575    E: From<Error>
576  {
577    let mut ret = Vec::new();
578    let mut recmap: HashMap<&str, &str> = HashMap::new();
579
580    // Determine how many records are in the list
581    let count = self.get_fromstr::<usize, _>("Count")?.ok_or_else(|| {
582      Error::Param(ParamError::Key("Missing 'Count'".into()))
583    })?;
584
585    for idx in 0..count {
586      recmap.clear();
587
588      for (k, ki) in keys.iter().map(|k| (k, format!("{k}.{idx}",))) {
589        let Some(v) = self.0.get(&ki) else {
590          continue;
591        };
592        recmap.insert(k, v);
593      }
594
595      let vecrec = f(&recmap)?;
596      ret.push(vecrec);
597    }
598
599    Ok(ret)
600  }
601
602  /// Add a named vector of records.
603  ///
604  /// This function mostly works like [`Params::encode_anon_list()`], but it
605  /// puts all its parameters into a namespace, specified in `name`.  This
606  /// allows multiple lists to be added to the same `Params`.
607  ///
608  /// # Errors
609  /// Returns an application-defined error `E`, with the constraint that it
610  /// must be able to convert a `blather::Error` into `E`.
611  pub fn encode_named_list<T, I, F, E>(
612    &mut self,
613    name: &str,
614    it: I,
615    f: F
616  ) -> Result<(), E>
617  where
618    I: IntoIterator<Item = T>,
619    F: Fn(T, &mut Vec<(String, String)>) -> Result<(), E>,
620    E: From<Error>
621  {
622    let mut count = 0;
623
624    // Used for capacity
625    let mut recs = 0;
626
627    for (idx, rec) in it.into_iter().enumerate() {
628      let mut rec_kv = Vec::with_capacity(recs);
629
630      // Call closure to generate records
631      f(rec, &mut rec_kv)?;
632
633      // Predict how many records to preallocate
634      recs = std::cmp::max(recs, rec_kv.len());
635
636      // Move indexed record fields into Params
637      for (k, v) in rec_kv {
638        let key = format!("{name}.{k}.{idx}");
639        self.add(key, v)?;
640      }
641
642      // Count record
643      count += 1;
644    }
645
646    let key = format!("{name}.Count");
647    self.add_param(key, count)?;
648
649    Ok(())
650  }
651
652  /// Decode an anonymous list previous generated using
653  /// [`Params::encode_anon_list()`].
654  ///
655  /// # Errors
656  /// Returns an application-defined error `E`, with the constraint that it
657  /// must be able to convert a `blather::Error` into `E`.
658  pub fn decode_named_list<T, F, E>(
659    &self,
660    name: &str,
661    keys: &[&str],
662    f: F
663  ) -> Result<Vec<T>, E>
664  where
665    F: Fn(&HashMap<&str, &str>) -> Result<T, E>,
666    E: From<Error>
667  {
668    let mut ret = Vec::new();
669    let mut recmap: HashMap<&str, &str> = HashMap::new();
670
671    // Determine how many records are in the list
672    let key = format!("{name}.Count");
673    let count = self.get_fromstr::<usize, _>(&key)?.ok_or_else(|| {
674      Error::Param(ParamError::Key("Missing 'Count'".into()))
675    })?;
676
677    for idx in 0..count {
678      recmap.clear();
679
680      for (k, ki) in keys.iter().map(|k| (k, format!("{name}.{k}.{idx}",))) {
681        let Some(v) = self.0.get(&ki) else {
682          continue;
683        };
684        recmap.insert(k, v);
685      }
686
687      let vecrec = f(&recmap)?;
688      ret.push(vecrec);
689    }
690
691    Ok(ret)
692  }
693}
694
695
696// ToDo: Use TryFrom, and validate all the fields
697impl TryFrom<HashMap<String, String>> for Params {
698  type Error = Error;
699
700  fn try_from(hm: HashMap<String, String>) -> Result<Self, Self::Error> {
701    for (k, v) in &hm {
702      validate_param_key(k)?;
703      validate_param_value(v)?;
704    }
705    Ok(Self(hm))
706  }
707}
708
709// ToDo: Just forward to inner type, and make the application use {:#?}
710// instead.
711impl fmt::Display for Params {
712  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
713    let mut kvlist = Vec::new();
714    for (key, value) in &self.0 {
715      kvlist.push(format!("{key}={value}"));
716    }
717    write!(f, "{{{}}}", kvlist.join(","))
718  }
719}
720
721
722#[cfg(test)]
723mod tests {
724  use super::Params;
725
726  #[test]
727  fn string() {
728    let mut msg = Params::new();
729
730    msg.add_str("Foo", "bar").unwrap();
731    assert_eq!(msg.get_str("Foo").unwrap(), "bar");
732
733    assert_eq!(msg.get_str("Moo"), None);
734  }
735
736  #[test]
737  fn exists() {
738    let mut params = Params::new();
739
740    params.add_str("foo", "bar").unwrap();
741    assert!(params.contains("foo"));
742
743    assert!(!params.contains("nonexistent"));
744  }
745
746  #[test]
747  fn empty_value() {
748    let mut params = Params::new();
749
750    params.add_str("foo", "").unwrap();
751
752    assert_eq!(params.get_str("foo"), Some(""));
753  }
754
755  #[test]
756  fn integer() {
757    let mut msg = Params::new();
758
759    msg.add_str("Num", "64").unwrap();
760    assert_eq!(msg.get_fromstr::<u16, _>("Num").unwrap().unwrap(), 64);
761  }
762
763  #[test]
764  fn size() {
765    let mut msg = Params::new();
766
767    msg.add_param("Num", 7_usize).unwrap();
768    assert_eq!(msg.get_fromstr::<usize, _>("Num").unwrap().unwrap(), 7);
769  }
770
771  #[test]
772  fn intoparams() {
773    let mut msg = Params::new();
774
775    msg.add_str("Foo", "bar").unwrap();
776    assert_eq!(msg.get_str("Foo").unwrap(), "bar");
777    assert_eq!(msg.get_str("Moo"), None);
778
779    let hm = msg.into_inner();
780    let kv = hm.get_key_value("Foo");
781    if let Some((_k, v)) = kv {
782      assert_eq!(v, "bar");
783    }
784  }
785
786  #[test]
787  fn display() {
788    let mut params = Params::new();
789
790    params.add_str("foo", "bar").unwrap();
791    let s = format!("{params}");
792    assert_eq!(s, "{foo=bar}");
793  }
794
795  #[test]
796  fn ser_size() {
797    let mut params = Params::new();
798
799    params.add_str("foo", "bar").unwrap();
800    params.add_str("moo", "cow").unwrap();
801
802    let sz = params.calc_buf_size();
803
804    assert_eq!(sz, 8 + 8 + 1);
805  }
806
807  #[test]
808  #[should_panic(expected = "Param(Key(\"invalid character\"))")]
809  fn key_invalid_char() {
810    let mut param = Params::new();
811    param.add_str("hell o", "world").unwrap();
812  }
813
814  #[test]
815  #[should_panic(expected = "Param(Key(\"empty\"))")]
816  fn empty_key() {
817    let mut param = Params::new();
818    param.add_str("", "world").unwrap();
819  }
820
821  #[test]
822  #[should_panic(expected = "Param(Value(\"contains newline\"))")]
823  fn value_newline() {
824    let mut param = Params::new();
825    param.add_str("greeting", "hello\nworld").unwrap();
826  }
827
828  #[test]
829  fn boolvals() {
830    let mut params = Params::new();
831
832    params.add_bool("abool", true).unwrap();
833    params.add_bool("abooltoo", false).unwrap();
834
835    let Ok(Some(true)) = params.get_bool("abool") else {
836      panic!("Unexpectedly not Ok(true)");
837    };
838    let Ok(Some(false)) = params.get_bool("abooltoo") else {
839      panic!("Unexpectedly not Ok(false)");
840    };
841  }
842
843  #[test]
844  #[should_panic(expected = "Param(Value(\"not boolean\"))")]
845  fn bad_bool() {
846    let mut params = Params::new();
847
848    params.add_str("fool", "uncertain").unwrap();
849
850    params.get_bool("fool").unwrap();
851  }
852}
853
854
855#[cfg(test)]
856mod recvec_tests {
857  use super::{Error, Params};
858
859  struct Agent {
860    name: String,
861    age: u8
862  }
863
864  impl Agent {
865    fn new(name: impl Into<String>, age: u8) -> Self {
866      Self {
867        name: name.into(),
868        age
869      }
870    }
871  }
872
873  #[test]
874  fn encode_anon() {
875    let mut params = Params::new();
876
877    let agents = vec![Agent::new("frank", 42), Agent::new("anon", 32)];
878
879    params
880      .encode_anon_list::<_, _, _, Error>(agents, |agent, v| {
881        v.push(("Name".to_string(), agent.name));
882        v.push(("Age".to_string(), agent.age.to_string()));
883        Ok(())
884      })
885      .unwrap();
886
887    assert_eq!(params.get_fromstr::<usize, _>("Count").unwrap().unwrap(), 2);
888
889    assert_eq!(params.get_str("Name.0"), Some("frank"));
890    assert_eq!(params.get_str("Age.0"), Some("42"));
891    assert_eq!(params.get_str("Name.1"), Some("anon"));
892    assert_eq!(params.get_str("Age.1"), Some("32"));
893  }
894
895  #[test]
896  fn decode_anon() {
897    let mut params = Params::new();
898    params.add_param("Name.0", "frank").unwrap();
899    params.add_param("Age.0", "42").unwrap();
900    params.add_param("Name.1", "anon").unwrap();
901    params.add_param("Age.1", "32").unwrap();
902    params.add_param("Count", "2").unwrap();
903
904    let v = params
905      .decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
906        let name = (*recmap.get("Name").unwrap()).to_string();
907        let age = recmap.get("Age").unwrap();
908        let age = age.parse::<u8>().unwrap();
909        Ok(Agent { name, age })
910      })
911      .unwrap();
912
913    assert_eq!(v.len(), 2);
914    assert_eq!(&v[0].name, "frank");
915    assert_eq!(v[0].age, 42);
916    assert_eq!(&v[1].name, "anon");
917    assert_eq!(v[1].age, 32);
918  }
919
920  #[test]
921  #[should_panic(expected = "Param(Key(\"Missing 'Count'\"))")]
922  fn decode_no_count() {
923    let params = Params::new();
924    let _v = params
925      .decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
926        let name = (*recmap.get("Name").unwrap()).to_string();
927        let age = recmap.get("Age").unwrap();
928        let age = age.parse::<u8>().unwrap();
929        Ok(Agent { name, age })
930      })
931      .unwrap();
932  }
933
934  #[test]
935  #[should_panic(expected = "Param(Value(\"invalid digit found in string\"))")]
936  fn decode_bad_count() {
937    let mut params = Params::new();
938    params.add_param("Count", "moo").unwrap();
939    let _v = params
940      .decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
941        let name = (*recmap.get("Name").unwrap()).to_string();
942        let age = recmap.get("Age").unwrap();
943        let age = age.parse::<u8>().unwrap();
944        Ok(Agent { name, age })
945      })
946      .unwrap();
947  }
948
949  #[test]
950  fn encode_named() {
951    let mut params = Params::new();
952
953    let agents = vec![Agent::new("frank", 42), Agent::new("anon", 32)];
954
955    params
956      .encode_named_list::<_, _, _, Error>("Agents", agents, |agent, v| {
957        v.push(("Name".to_string(), agent.name));
958        v.push(("Age".to_string(), agent.age.to_string()));
959        Ok(())
960      })
961      .unwrap();
962
963    assert_eq!(
964      params
965        .get_fromstr::<usize, _>("Agents.Count")
966        .unwrap()
967        .unwrap(),
968      2
969    );
970
971    assert_eq!(params.get_str("Agents.Name.0"), Some("frank"));
972    assert_eq!(params.get_str("Agents.Age.0"), Some("42"));
973    assert_eq!(params.get_str("Agents.Name.1"), Some("anon"));
974    assert_eq!(params.get_str("Agents.Age.1"), Some("32"));
975  }
976
977  #[test]
978  fn decode_named() {
979    let mut params = Params::new();
980    params.add_param("Agents.Name.0", "frank").unwrap();
981    params.add_param("Agents.Age.0", "42").unwrap();
982    params.add_param("Agents.Name.1", "anon").unwrap();
983    params.add_param("Agents.Age.1", "32").unwrap();
984    params.add_param("Agents.Count", "2").unwrap();
985
986    let v = params
987      .decode_named_list::<_, _, Error>("Agents", &["Name", "Age"], |recmap| {
988        let name = (*recmap.get("Name").unwrap()).to_string();
989        let age = recmap.get("Age").unwrap();
990        let age = age.parse::<u8>().unwrap();
991        Ok(Agent { name, age })
992      })
993      .unwrap();
994
995    assert_eq!(v.len(), 2);
996    assert_eq!(&v[0].name, "frank");
997    assert_eq!(v[0].age, 42);
998    assert_eq!(&v[1].name, "anon");
999    assert_eq!(v[1].age, 32);
1000  }
1001}
1002
1003// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :