zenoh_protocol/core/
parameters.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15/// Module provides a set of utility functions which allows to manipulate  &str` which follows the format `a=b;c=d|e;f=g`.
16/// and structure `Parameters` which provides `HashMap<&str, &str>`-like view over a string of such format.
17///
18/// `;` is the separator between the key-value `(&str, &str)` elements.
19///
20/// `=` is the separator between the `&str`-key and `&str`-value
21///
22/// `|` is the separator between multiple elements of the values.
23use alloc::{
24    borrow::Cow,
25    string::{String, ToString},
26    vec::Vec,
27};
28use core::{borrow::Borrow, fmt};
29#[cfg(feature = "std")]
30use std::collections::HashMap;
31
32pub(super) const LIST_SEPARATOR: char = ';';
33pub(super) const FIELD_SEPARATOR: char = '=';
34pub(super) const VALUE_SEPARATOR: char = '|';
35
36fn split_once(s: &str, c: char) -> (&str, &str) {
37    match s.find(c) {
38        Some(index) => {
39            let (l, r) = s.split_at(index);
40            (l, &r[1..])
41        }
42        None => (s, ""),
43    }
44}
45
46/// Returns an iterator of key-value `(&str, &str)` pairs according to the parameters format.
47pub fn iter(s: &str) -> impl DoubleEndedIterator<Item = (&str, &str)> + Clone {
48    s.split(LIST_SEPARATOR)
49        .filter(|p| !p.is_empty())
50        .map(|p| split_once(p, FIELD_SEPARATOR))
51}
52
53/// Same as [`from_iter_into`] but keys are sorted in alphabetical order.
54pub fn sort<'s, I>(iter: I) -> impl Iterator<Item = (&'s str, &'s str)>
55where
56    I: Iterator<Item = (&'s str, &'s str)>,
57{
58    let mut from = iter.collect::<Vec<(&str, &str)>>();
59    from.sort_unstable_by(|(k1, _), (k2, _)| k1.cmp(k2));
60    from.into_iter()
61}
62
63/// Joins two key-value `(&str, &str)` iterators removing from `current` any element whose key is present in `new`.
64pub fn join<'s, C, N>(current: C, new: N) -> impl Iterator<Item = (&'s str, &'s str)> + Clone
65where
66    C: Iterator<Item = (&'s str, &'s str)> + Clone,
67    N: Iterator<Item = (&'s str, &'s str)> + Clone + 's,
68{
69    let n = new.clone();
70    let current = current
71        .clone()
72        .filter(move |(kc, _)| !n.clone().any(|(kn, _)| kn == *kc));
73    current.chain(new)
74}
75
76/// Builds a string from an iterator preserving the order.
77#[allow(clippy::should_implement_trait)]
78pub fn from_iter<'s, I>(iter: I) -> String
79where
80    I: Iterator<Item = (&'s str, &'s str)>,
81{
82    let mut into = String::new();
83    from_iter_into(iter, &mut into);
84    into
85}
86
87/// Same as [`from_iter`] but it writes into a user-provided string instead of allocating a new one.
88pub fn from_iter_into<'s, I>(iter: I, into: &mut String)
89where
90    I: Iterator<Item = (&'s str, &'s str)>,
91{
92    concat_into(iter, into);
93}
94
95/// Get the a `&str`-value for a `&str`-key according to the parameters format.
96pub fn get<'s>(s: &'s str, k: &str) -> Option<&'s str> {
97    iter(s).find(|(key, _)| *key == k).map(|(_, value)| value)
98}
99
100/// Get the a `&str`-value iterator for a `&str`-key according to the parameters format.
101pub fn values<'s>(s: &'s str, k: &str) -> impl DoubleEndedIterator<Item = &'s str> {
102    match get(s, k) {
103        Some(v) => v.split(VALUE_SEPARATOR),
104        None => {
105            let mut i = "".split(VALUE_SEPARATOR);
106            i.next();
107            i
108        }
109    }
110}
111
112fn _insert<'s, I>(
113    i: I,
114    k: &'s str,
115    v: &'s str,
116) -> (impl Iterator<Item = (&'s str, &'s str)>, Option<&'s str>)
117where
118    I: Iterator<Item = (&'s str, &'s str)> + Clone,
119{
120    let mut iter = i.clone();
121    let item = iter.find(|(key, _)| *key == k).map(|(_, v)| v);
122
123    let current = i.filter(move |x| x.0 != k);
124    let new = Some((k, v)).into_iter();
125    (current.chain(new), item)
126}
127
128/// Insert a key-value `(&str, &str)` pair by appending it at the end of `s` preserving the insertion order.
129pub fn insert<'s>(s: &'s str, k: &'s str, v: &'s str) -> (String, Option<&'s str>) {
130    let (iter, item) = _insert(iter(s), k, v);
131    (from_iter(iter), item)
132}
133
134/// Same as [`insert`] but keys are sorted in alphabetical order.
135pub fn insert_sort<'s>(s: &'s str, k: &'s str, v: &'s str) -> (String, Option<&'s str>) {
136    let (iter, item) = _insert(iter(s), k, v);
137    (from_iter(sort(iter)), item)
138}
139
140/// Remove a key-value `(&str, &str)` pair from `s` preserving the insertion order.
141pub fn remove<'s>(s: &'s str, k: &str) -> (String, Option<&'s str>) {
142    let mut iter = iter(s);
143    let item = iter.find(|(key, _)| *key == k).map(|(_, v)| v);
144    let iter = iter.filter(|x| x.0 != k);
145    (concat(iter), item)
146}
147
148/// Returns `true` if all keys are sorted in alphabetical order
149pub fn is_ordered(s: &str) -> bool {
150    let mut prev = None;
151    for (k, _) in iter(s) {
152        match prev.take() {
153            Some(p) if k < p => return false,
154            _ => prev = Some(k),
155        }
156    }
157    true
158}
159
160fn concat<'s, I>(iter: I) -> String
161where
162    I: Iterator<Item = (&'s str, &'s str)>,
163{
164    let mut into = String::new();
165    concat_into(iter, &mut into);
166    into
167}
168
169fn concat_into<'s, I>(iter: I, into: &mut String)
170where
171    I: Iterator<Item = (&'s str, &'s str)>,
172{
173    let mut first = true;
174    for (k, v) in iter.filter(|(k, _)| !k.is_empty()) {
175        if !first {
176            into.push(LIST_SEPARATOR);
177        }
178        into.push_str(k);
179        if !v.is_empty() {
180            into.push(FIELD_SEPARATOR);
181            into.push_str(v);
182        }
183        first = false;
184    }
185}
186
187#[cfg(feature = "test")]
188#[doc(hidden)]
189pub fn rand(into: &mut String) {
190    use rand::{
191        distributions::{Alphanumeric, DistString},
192        Rng,
193    };
194
195    const MIN: usize = 2;
196    const MAX: usize = 8;
197
198    let mut rng = rand::thread_rng();
199
200    let num = rng.gen_range(MIN..MAX);
201    for i in 0..num {
202        if i != 0 {
203            into.push(LIST_SEPARATOR);
204        }
205        let len = rng.gen_range(MIN..MAX);
206        let key = Alphanumeric.sample_string(&mut rng, len);
207        into.push_str(key.as_str());
208
209        into.push(FIELD_SEPARATOR);
210
211        let len = rng.gen_range(MIN..MAX);
212        let value = Alphanumeric.sample_string(&mut rng, len);
213        into.push_str(value.as_str());
214    }
215}
216
217/// A map of key/value (String,String) parameters.
218/// It can be parsed from a String, using `;` or `<newline>` as separator between each parameters
219/// and `=` as separator between a key and its value. Keys and values are trimmed.
220///
221/// Example:
222/// ```
223/// use zenoh_protocol::core::Parameters;
224///
225/// let a = "a=1;b=2;c=3|4|5;d=6";
226/// let p = Parameters::from(a);
227///
228/// // Retrieve values
229/// assert!(!p.is_empty());
230/// assert_eq!(p.get("a").unwrap(), "1");
231/// assert_eq!(p.get("b").unwrap(), "2");
232/// assert_eq!(p.get("c").unwrap(), "3|4|5");
233/// assert_eq!(p.get("d").unwrap(), "6");
234/// assert_eq!(p.values("c").collect::<Vec<&str>>(), vec!["3", "4", "5"]);
235///
236/// // Iterate over parameters
237/// let mut iter = p.iter();
238/// assert_eq!(iter.next().unwrap(), ("a", "1"));
239/// assert_eq!(iter.next().unwrap(), ("b", "2"));
240/// assert_eq!(iter.next().unwrap(), ("c", "3|4|5"));
241/// assert_eq!(iter.next().unwrap(), ("d", "6"));
242/// assert!(iter.next().is_none());
243///
244/// // Create parameters from iterators
245/// let pi = Parameters::from_iter(vec![("a", "1"), ("b", "2"), ("c", "3|4|5"), ("d", "6")]);
246/// assert_eq!(p, pi);
247/// ```
248#[derive(Clone, PartialEq, Eq, Hash, Default)]
249pub struct Parameters<'s>(Cow<'s, str>);
250
251impl<'s> Parameters<'s> {
252    /// Create empty parameters.
253    pub const fn empty() -> Self {
254        Self(Cow::Borrowed(""))
255    }
256
257    /// Returns `true` if parameters does not contain anything.
258    pub fn is_empty(&self) -> bool {
259        self.0.is_empty()
260    }
261
262    /// Returns parameters as [`str`].
263    pub fn as_str(&'s self) -> &'s str {
264        &self.0
265    }
266
267    /// Returns `true` if parameters contains the specified key.
268    pub fn contains_key<K>(&self, k: K) -> bool
269    where
270        K: Borrow<str>,
271    {
272        super::parameters::get(self.as_str(), k.borrow()).is_some()
273    }
274
275    /// Returns a reference to the `&str`-value corresponding to the key.
276    pub fn get<K>(&'s self, k: K) -> Option<&'s str>
277    where
278        K: Borrow<str>,
279    {
280        super::parameters::get(self.as_str(), k.borrow())
281    }
282
283    /// Returns an iterator to the `&str`-values corresponding to the key.
284    pub fn values<K>(&'s self, k: K) -> impl DoubleEndedIterator<Item = &'s str>
285    where
286        K: Borrow<str>,
287    {
288        super::parameters::values(self.as_str(), k.borrow())
289    }
290
291    /// Returns an iterator on the key-value pairs as `(&str, &str)`.
292    pub fn iter(&'s self) -> impl DoubleEndedIterator<Item = (&'s str, &'s str)> + Clone {
293        super::parameters::iter(self.as_str())
294    }
295
296    /// Inserts a key-value pair into the map.
297    /// If the map did not have this key present, [`None`]` is returned.
298    /// If the map did have this key present, the value is updated, and the old value is returned.
299    pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<String>
300    where
301        K: Borrow<str>,
302        V: Borrow<str>,
303    {
304        let (inner, item) = super::parameters::insert(self.as_str(), k.borrow(), v.borrow());
305        let item = item.map(|i| i.to_string());
306        self.0 = Cow::Owned(inner);
307        item
308    }
309
310    /// Removes a key from the map, returning the value at the key if the key was previously in the parameters.
311    pub fn remove<K>(&mut self, k: K) -> Option<String>
312    where
313        K: Borrow<str>,
314    {
315        let (inner, item) = super::parameters::remove(self.as_str(), k.borrow());
316        let item = item.map(|i| i.to_string());
317        self.0 = Cow::Owned(inner);
318        item
319    }
320
321    /// Extend these parameters with other parameters.
322    pub fn extend(&mut self, other: &Parameters) {
323        self.extend_from_iter(other.iter());
324    }
325
326    /// Extend these parameters from an iterator.
327    pub fn extend_from_iter<'e, I, K, V>(&mut self, iter: I)
328    where
329        I: Iterator<Item = (&'e K, &'e V)> + Clone,
330        K: Borrow<str> + 'e + ?Sized,
331        V: Borrow<str> + 'e + ?Sized,
332    {
333        let inner = super::parameters::from_iter(super::parameters::join(
334            self.iter(),
335            iter.map(|(k, v)| (k.borrow(), v.borrow())),
336        ));
337        self.0 = Cow::Owned(inner);
338    }
339
340    /// Convert these parameters into owned parameters.
341    pub fn into_owned(self) -> Parameters<'static> {
342        Parameters(Cow::Owned(self.0.into_owned()))
343    }
344
345    /// Returns `true`` if all keys are sorted in alphabetical order.
346    pub fn is_ordered(&self) -> bool {
347        super::parameters::is_ordered(self.as_str())
348    }
349}
350
351impl<'s> From<&'s str> for Parameters<'s> {
352    fn from(mut value: &'s str) -> Self {
353        value = value.trim_end_matches(|c| {
354            c == LIST_SEPARATOR || c == FIELD_SEPARATOR || c == VALUE_SEPARATOR
355        });
356        Self(Cow::Borrowed(value))
357    }
358}
359
360impl From<String> for Parameters<'_> {
361    fn from(mut value: String) -> Self {
362        let s = value.trim_end_matches(|c| {
363            c == LIST_SEPARATOR || c == FIELD_SEPARATOR || c == VALUE_SEPARATOR
364        });
365        value.truncate(s.len());
366        Self(Cow::Owned(value))
367    }
368}
369
370impl<'s> From<Cow<'s, str>> for Parameters<'s> {
371    fn from(value: Cow<'s, str>) -> Self {
372        match value {
373            Cow::Borrowed(s) => Parameters::from(s),
374            Cow::Owned(s) => Parameters::from(s),
375        }
376    }
377}
378
379impl<'a> From<Parameters<'a>> for Cow<'_, Parameters<'a>> {
380    fn from(props: Parameters<'a>) -> Self {
381        Cow::Owned(props)
382    }
383}
384
385impl<'a> From<&'a Parameters<'a>> for Cow<'a, Parameters<'a>> {
386    fn from(props: &'a Parameters<'a>) -> Self {
387        Cow::Borrowed(props)
388    }
389}
390
391impl<'s, K, V> FromIterator<(&'s K, &'s V)> for Parameters<'_>
392where
393    K: Borrow<str> + 's + ?Sized,
394    V: Borrow<str> + 's + ?Sized,
395{
396    fn from_iter<T: IntoIterator<Item = (&'s K, &'s V)>>(iter: T) -> Self {
397        let iter = iter.into_iter();
398        let inner = super::parameters::from_iter(iter.map(|(k, v)| (k.borrow(), v.borrow())));
399        Self(Cow::Owned(inner))
400    }
401}
402
403impl<'s, K, V> FromIterator<&'s (K, V)> for Parameters<'_>
404where
405    K: Borrow<str> + 's,
406    V: Borrow<str> + 's,
407{
408    fn from_iter<T: IntoIterator<Item = &'s (K, V)>>(iter: T) -> Self {
409        Self::from_iter(iter.into_iter().map(|(k, v)| (k.borrow(), v.borrow())))
410    }
411}
412
413impl<'s, K, V> From<&'s [(K, V)]> for Parameters<'_>
414where
415    K: Borrow<str> + 's,
416    V: Borrow<str> + 's,
417{
418    fn from(value: &'s [(K, V)]) -> Self {
419        Self::from_iter(value.iter())
420    }
421}
422
423#[cfg(feature = "std")]
424impl<K, V> From<HashMap<K, V>> for Parameters<'_>
425where
426    K: Borrow<str>,
427    V: Borrow<str>,
428{
429    fn from(map: HashMap<K, V>) -> Self {
430        Self::from_iter(map.iter())
431    }
432}
433
434#[cfg(feature = "std")]
435impl<'s> From<&'s Parameters<'s>> for HashMap<&'s str, &'s str> {
436    fn from(props: &'s Parameters<'s>) -> Self {
437        HashMap::from_iter(props.iter())
438    }
439}
440
441#[cfg(feature = "std")]
442impl From<&Parameters<'_>> for HashMap<String, String> {
443    fn from(props: &Parameters<'_>) -> Self {
444        HashMap::from_iter(props.iter().map(|(k, v)| (k.to_string(), v.to_string())))
445    }
446}
447
448#[cfg(feature = "std")]
449impl<'s> From<&'s Parameters<'s>> for HashMap<Cow<'s, str>, Cow<'s, str>> {
450    fn from(props: &'s Parameters<'s>) -> Self {
451        HashMap::from_iter(props.iter().map(|(k, v)| (Cow::from(k), Cow::from(v))))
452    }
453}
454
455#[cfg(feature = "std")]
456impl From<Parameters<'_>> for HashMap<String, String> {
457    fn from(props: Parameters) -> Self {
458        HashMap::from(&props)
459    }
460}
461
462impl fmt::Display for Parameters<'_> {
463    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
464        write!(f, "{}", self.0)
465    }
466}
467
468impl fmt::Debug for Parameters<'_> {
469    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
470        write!(f, "{self}")
471    }
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477
478    #[test]
479    fn test_parameters() {
480        assert!(Parameters::from("").0.is_empty());
481
482        assert_eq!(Parameters::from("p1"), Parameters::from(&[("p1", "")][..]));
483
484        assert_eq!(
485            Parameters::from("p1=v1"),
486            Parameters::from(&[("p1", "v1")][..])
487        );
488
489        assert_eq!(
490            Parameters::from("p1=v1;p2=v2;"),
491            Parameters::from(&[("p1", "v1"), ("p2", "v2")][..])
492        );
493
494        assert_eq!(
495            Parameters::from("p1=v1;p2=v2;|="),
496            Parameters::from(&[("p1", "v1"), ("p2", "v2")][..])
497        );
498
499        assert_eq!(
500            Parameters::from("p1=v1;p2;p3=v3"),
501            Parameters::from(&[("p1", "v1"), ("p2", ""), ("p3", "v3")][..])
502        );
503
504        assert_eq!(
505            Parameters::from("p1=v 1;p 2=v2"),
506            Parameters::from(&[("p1", "v 1"), ("p 2", "v2")][..])
507        );
508
509        assert_eq!(
510            Parameters::from("p1=x=y;p2=a==b"),
511            Parameters::from(&[("p1", "x=y"), ("p2", "a==b")][..])
512        );
513
514        let mut hm: HashMap<String, String> = HashMap::new();
515        hm.insert("p1".to_string(), "v1".to_string());
516        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
517
518        let mut hm: HashMap<&str, &str> = HashMap::new();
519        hm.insert("p1", "v1");
520        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
521
522        let mut hm: HashMap<Cow<str>, Cow<str>> = HashMap::new();
523        hm.insert(Cow::from("p1"), Cow::from("v1"));
524        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
525    }
526}