Skip to main content

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_key(|(k1, _)| *k1);
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            // Create an empty iterator of the same type as the `Some` case to make the compiler happy.
106            let mut i = "".split(VALUE_SEPARATOR);
107            // Need to skip the first element, as splitting `""` by `"|"` returns `vec![""]`.
108            i.next();
109            i
110        }
111    }
112}
113
114/// Returns `true` if the parameter string contains at least one valid entry
115/// and none of its keys are empty.
116pub fn is_well_formed(s: &str) -> bool {
117    let mut iter = iter(s);
118    iter.clone().next().is_some() && iter.all(|(k, _)| !k.is_empty())
119}
120
121fn _insert<'s, I>(
122    i: I,
123    k: &'s str,
124    v: &'s str,
125) -> (impl Iterator<Item = (&'s str, &'s str)>, Option<&'s str>)
126where
127    I: Iterator<Item = (&'s str, &'s str)> + Clone,
128{
129    let mut iter = i.clone();
130    let item = iter.find(|(key, _)| *key == k).map(|(_, v)| v);
131
132    let current = i.filter(move |x| x.0 != k);
133    let new = Some((k, v)).into_iter();
134    (current.chain(new), item)
135}
136
137/// Insert a key-value `(&str, &str)` pair by appending it at the end of `s` preserving the insertion order.
138pub fn insert<'s>(s: &'s str, k: &'s str, v: &'s str) -> (String, Option<&'s str>) {
139    let (iter, item) = _insert(iter(s), k, v);
140    (from_iter(iter), item)
141}
142
143/// Same as [`insert`] but keys are sorted in alphabetical order.
144pub fn insert_sort<'s>(s: &'s str, k: &'s str, v: &'s str) -> (String, Option<&'s str>) {
145    let (iter, item) = _insert(iter(s), k, v);
146    (from_iter(sort(iter)), item)
147}
148
149/// Remove every entry for a `&str`-key from `s` preserving the insertion order of the
150/// remaining entries. Returns the value of the first removed entry, if any.
151pub fn remove<'s>(s: &'s str, k: &str) -> (String, Option<&'s str>) {
152    // NOTE: `item` must be looked up on a fresh iterator — calling `find` on the
153    // iterator that later feeds `filter` would consume every entry up to and
154    // including the first match, dropping the entries preceding it from the result
155    // (and dropping everything when the key is absent).
156    let item = get(s, k);
157    let iter = iter(s).filter(|x| x.0 != k);
158    (concat(iter), item)
159}
160
161/// Returns `true` if all keys are sorted in alphabetical order
162pub fn is_ordered(s: &str) -> bool {
163    let mut prev = None;
164    for (k, _) in iter(s) {
165        match prev.take() {
166            Some(p) if k < p => return false,
167            _ => prev = Some(k),
168        }
169    }
170    true
171}
172
173fn concat<'s, I>(iter: I) -> String
174where
175    I: Iterator<Item = (&'s str, &'s str)>,
176{
177    let mut into = String::new();
178    concat_into(iter, &mut into);
179    into
180}
181
182fn concat_into<'s, I>(iter: I, into: &mut String)
183where
184    I: Iterator<Item = (&'s str, &'s str)>,
185{
186    let mut first = true;
187    for (k, v) in iter.filter(|(k, _)| !k.is_empty()) {
188        if !first {
189            into.push(LIST_SEPARATOR);
190        }
191        into.push_str(k);
192        if !v.is_empty() {
193            into.push(FIELD_SEPARATOR);
194            into.push_str(v);
195        }
196        first = false;
197    }
198}
199
200#[cfg(feature = "test")]
201#[doc(hidden)]
202pub fn rand(into: &mut String) {
203    use rand::{
204        distributions::{Alphanumeric, DistString},
205        Rng,
206    };
207
208    const MIN: usize = 2;
209    const MAX: usize = 8;
210
211    let mut rng = rand::thread_rng();
212
213    let num = rng.gen_range(MIN..MAX);
214    for i in 0..num {
215        if i != 0 {
216            into.push(LIST_SEPARATOR);
217        }
218        let len = rng.gen_range(MIN..MAX);
219        let key = Alphanumeric.sample_string(&mut rng, len);
220        into.push_str(key.as_str());
221
222        into.push(FIELD_SEPARATOR);
223
224        let len = rng.gen_range(MIN..MAX);
225        let value = Alphanumeric.sample_string(&mut rng, len);
226        into.push_str(value.as_str());
227    }
228}
229
230/// A map of key/value `(String, Vec<String>)` parameters.
231///
232/// It can be parsed from a `String`, using `;` as separator between each parameter and `=` as separator between a key and its value.
233///
234/// Keys can have multiple values, using `|` as a separator between them. An iterator for these can be obtained with [`Parameters::values`].
235///
236/// Construction from a string accepts ANY input: trailing `;`, `=`, and `|` characters are
237/// trimmed, the rest is stored verbatim. Empty `;`-separated chunks are skipped on iteration,
238/// a chunk is split into key and value on its FIRST `=` (a chunk without `=` has the empty
239/// string as its value), and no percent-decoding is applied. Duplicate keys are allowed:
240/// [`Parameters::get`] returns the value of the first occurrence, and duplicates are preserved
241/// (including across mutations of other keys). Mutating that key via [`Parameters::insert`] or
242/// [`Parameters::remove`] rebuilds the string and thus collapses/removes its entries.
243///
244/// Example:
245/// ```
246/// use zenoh_protocol::core::Parameters;
247///
248/// let a = "a=1;b=2;c=3|4|5;d=6";
249/// let p = Parameters::from(a);
250///
251/// // Retrieve values
252/// assert!(!p.is_empty());
253/// assert_eq!(p.get("a").unwrap(), "1");
254/// assert_eq!(p.get("b").unwrap(), "2");
255/// assert_eq!(p.get("c").unwrap(), "3|4|5");
256/// assert_eq!(p.get("d").unwrap(), "6");
257/// assert_eq!(p.values("c").collect::<Vec<&str>>(), vec!["3", "4", "5"]);
258///
259/// // Iterate over parameters
260/// let mut iter = p.iter();
261/// assert_eq!(iter.next().unwrap(), ("a", "1"));
262/// assert_eq!(iter.next().unwrap(), ("b", "2"));
263/// assert_eq!(iter.next().unwrap(), ("c", "3|4|5"));
264/// assert_eq!(iter.next().unwrap(), ("d", "6"));
265/// assert!(iter.next().is_none());
266///
267/// // Create parameters from iterators
268/// let pi = Parameters::from_iter(vec![("a", "1"), ("b", "2"), ("c", "3|4|5"), ("d", "6")]);
269/// assert_eq!(p, pi);
270/// ```
271#[derive(Clone, PartialEq, Eq, Hash, Default)]
272pub struct Parameters<'s>(Cow<'s, str>);
273
274impl<'s> Parameters<'s> {
275    /// Create empty parameters.
276    pub const fn empty() -> Self {
277        Self(Cow::Borrowed(""))
278    }
279
280    /// Returns `true` if parameters does not contain anything.
281    pub fn is_empty(&self) -> bool {
282        self.0.is_empty()
283    }
284
285    /// Returns parameters as [`str`].
286    pub fn as_str(&'s self) -> &'s str {
287        &self.0
288    }
289
290    /// Returns `true` if parameters contains the specified key.
291    pub fn contains_key<K>(&self, k: K) -> bool
292    where
293        K: Borrow<str>,
294    {
295        super::parameters::get(self.as_str(), k.borrow()).is_some()
296    }
297
298    /// Returns a reference to the `&str`-value corresponding to the key.
299    pub fn get<K>(&'s self, k: K) -> Option<&'s str>
300    where
301        K: Borrow<str>,
302    {
303        super::parameters::get(self.as_str(), k.borrow())
304    }
305
306    /// Returns an iterator to the `&str`-values corresponding to the key.
307    pub fn values<K>(&'s self, k: K) -> impl DoubleEndedIterator<Item = &'s str>
308    where
309        K: Borrow<str>,
310    {
311        super::parameters::values(self.as_str(), k.borrow())
312    }
313
314    /// Returns an iterator on the key-value pairs as `(&str, &str)`.
315    pub fn iter(&'s self) -> impl DoubleEndedIterator<Item = (&'s str, &'s str)> + Clone {
316        super::parameters::iter(self.as_str())
317    }
318
319    /// Inserts a key-value pair into the map.
320    /// If the map did not have this key present, [`None`] is returned.
321    /// If the map did have this key present, the value is updated, and the old value is returned.
322    pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<String>
323    where
324        K: Borrow<str>,
325        V: Borrow<str>,
326    {
327        let (inner, item) = super::parameters::insert(self.as_str(), k.borrow(), v.borrow());
328        let item = item.map(|i| i.to_string());
329        self.0 = Cow::Owned(inner);
330        item
331    }
332
333    /// Removes a key from the map, returning the value at the key if the key was previously in the parameters.
334    pub fn remove<K>(&mut self, k: K) -> Option<String>
335    where
336        K: Borrow<str>,
337    {
338        let (inner, item) = super::parameters::remove(self.as_str(), k.borrow());
339        let item = item.map(|i| i.to_string());
340        self.0 = Cow::Owned(inner);
341        item
342    }
343
344    /// Extend these parameters with other parameters.
345    pub fn extend(&mut self, other: &Parameters) {
346        self.extend_from_iter(other.iter());
347    }
348
349    /// Extend these parameters from an iterator.
350    pub fn extend_from_iter<'e, I, K, V>(&mut self, iter: I)
351    where
352        I: Iterator<Item = (&'e K, &'e V)> + Clone,
353        K: Borrow<str> + 'e + ?Sized,
354        V: Borrow<str> + 'e + ?Sized,
355    {
356        let inner = super::parameters::from_iter(super::parameters::join(
357            self.iter(),
358            iter.map(|(k, v)| (k.borrow(), v.borrow())),
359        ));
360        self.0 = Cow::Owned(inner);
361    }
362
363    /// Convert these parameters into owned parameters.
364    pub fn into_owned(self) -> Parameters<'static> {
365        Parameters(Cow::Owned(self.0.into_owned()))
366    }
367
368    /// Returns `true` if all keys are sorted in alphabetical order.
369    pub fn is_ordered(&self) -> bool {
370        super::parameters::is_ordered(self.as_str())
371    }
372}
373
374impl<'s> From<&'s str> for Parameters<'s> {
375    /// Infallible: trailing `;`, `=`, and `|` characters are trimmed, the rest is
376    /// stored verbatim (no validation, no percent-decoding).
377    fn from(mut value: &'s str) -> Self {
378        value = value.trim_end_matches(|c| {
379            c == LIST_SEPARATOR || c == FIELD_SEPARATOR || c == VALUE_SEPARATOR
380        });
381        Self(Cow::Borrowed(value))
382    }
383}
384
385impl From<String> for Parameters<'_> {
386    /// Infallible: trailing `;`, `=`, and `|` characters are trimmed, the rest is
387    /// stored verbatim (no validation, no percent-decoding).
388    fn from(mut value: String) -> Self {
389        let s = value.trim_end_matches(|c| {
390            c == LIST_SEPARATOR || c == FIELD_SEPARATOR || c == VALUE_SEPARATOR
391        });
392        value.truncate(s.len());
393        Self(Cow::Owned(value))
394    }
395}
396
397impl<'s> From<Cow<'s, str>> for Parameters<'s> {
398    fn from(value: Cow<'s, str>) -> Self {
399        match value {
400            Cow::Borrowed(s) => Parameters::from(s),
401            Cow::Owned(s) => Parameters::from(s),
402        }
403    }
404}
405
406impl<'a> From<Parameters<'a>> for Cow<'_, Parameters<'a>> {
407    fn from(props: Parameters<'a>) -> Self {
408        Cow::Owned(props)
409    }
410}
411
412impl<'a> From<&'a Parameters<'a>> for Cow<'a, Parameters<'a>> {
413    fn from(props: &'a Parameters<'a>) -> Self {
414        Cow::Borrowed(props)
415    }
416}
417
418impl<'s, K, V> FromIterator<(&'s K, &'s V)> for Parameters<'_>
419where
420    K: Borrow<str> + 's + ?Sized,
421    V: Borrow<str> + 's + ?Sized,
422{
423    fn from_iter<T: IntoIterator<Item = (&'s K, &'s V)>>(iter: T) -> Self {
424        let iter = iter.into_iter();
425        let inner = super::parameters::from_iter(iter.map(|(k, v)| (k.borrow(), v.borrow())));
426        Self(Cow::Owned(inner))
427    }
428}
429
430impl<'s, K, V> FromIterator<&'s (K, V)> for Parameters<'_>
431where
432    K: Borrow<str> + 's,
433    V: Borrow<str> + 's,
434{
435    fn from_iter<T: IntoIterator<Item = &'s (K, V)>>(iter: T) -> Self {
436        Self::from_iter(iter.into_iter().map(|(k, v)| (k.borrow(), v.borrow())))
437    }
438}
439
440impl<'s, K, V> From<&'s [(K, V)]> for Parameters<'_>
441where
442    K: Borrow<str> + 's,
443    V: Borrow<str> + 's,
444{
445    fn from(value: &'s [(K, V)]) -> Self {
446        Self::from_iter(value.iter())
447    }
448}
449
450#[cfg(feature = "std")]
451impl<K, V> From<HashMap<K, V>> for Parameters<'_>
452where
453    K: Borrow<str>,
454    V: Borrow<str>,
455{
456    fn from(map: HashMap<K, V>) -> Self {
457        Self::from_iter(map.iter())
458    }
459}
460
461#[cfg(feature = "std")]
462impl<'s> From<&'s Parameters<'s>> for HashMap<&'s str, &'s str> {
463    fn from(props: &'s Parameters<'s>) -> Self {
464        HashMap::from_iter(props.iter())
465    }
466}
467
468#[cfg(feature = "std")]
469impl From<&Parameters<'_>> for HashMap<String, String> {
470    fn from(props: &Parameters<'_>) -> Self {
471        HashMap::from_iter(props.iter().map(|(k, v)| (k.to_string(), v.to_string())))
472    }
473}
474
475#[cfg(feature = "std")]
476impl<'s> From<&'s Parameters<'s>> for HashMap<Cow<'s, str>, Cow<'s, str>> {
477    fn from(props: &'s Parameters<'s>) -> Self {
478        HashMap::from_iter(props.iter().map(|(k, v)| (Cow::from(k), Cow::from(v))))
479    }
480}
481
482#[cfg(feature = "std")]
483impl From<Parameters<'_>> for HashMap<String, String> {
484    fn from(props: Parameters) -> Self {
485        HashMap::from(&props)
486    }
487}
488
489impl fmt::Display for Parameters<'_> {
490    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
491        write!(f, "{}", self.0)
492    }
493}
494
495impl fmt::Debug for Parameters<'_> {
496    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
497        write!(f, "{self}")
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    #[test]
506    fn test_parameters() {
507        assert!(Parameters::from("").0.is_empty());
508
509        assert_eq!(Parameters::from("p1"), Parameters::from(&[("p1", "")][..]));
510
511        assert_eq!(
512            Parameters::from("p1=v1"),
513            Parameters::from(&[("p1", "v1")][..])
514        );
515
516        assert_eq!(
517            Parameters::from("p1=v1;p2=v2;"),
518            Parameters::from(&[("p1", "v1"), ("p2", "v2")][..])
519        );
520
521        assert_eq!(
522            Parameters::from("p1=v1;p2=v2;|="),
523            Parameters::from(&[("p1", "v1"), ("p2", "v2")][..])
524        );
525
526        assert_eq!(
527            Parameters::from("p1=v1;p2;p3=v3"),
528            Parameters::from(&[("p1", "v1"), ("p2", ""), ("p3", "v3")][..])
529        );
530
531        assert_eq!(
532            Parameters::from("p1=v 1;p 2=v2"),
533            Parameters::from(&[("p1", "v 1"), ("p 2", "v2")][..])
534        );
535
536        assert_eq!(
537            Parameters::from("p1=x=y;p2=a==b"),
538            Parameters::from(&[("p1", "x=y"), ("p2", "a==b")][..])
539        );
540
541        let mut hm: HashMap<String, String> = HashMap::new();
542        hm.insert("p1".to_string(), "v1".to_string());
543        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
544
545        let mut hm: HashMap<&str, &str> = HashMap::new();
546        hm.insert("p1", "v1");
547        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
548
549        let mut hm: HashMap<Cow<str>, Cow<str>> = HashMap::new();
550        hm.insert(Cow::from("p1"), Cow::from("v1"));
551        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
552    }
553
554    #[test]
555    fn values_iterator_for_non_existing_key_is_empty() {
556        let params = Parameters::from("p1=1");
557
558        assert_eq!(params.values("p2").next(), None);
559    }
560
561    #[test]
562    fn test_remove() {
563        // Entries preceding the removed key are preserved.
564        assert_eq!(remove("b=2;a=1;c=3", "a"), ("b=2;c=3".into(), Some("1")));
565        // Every entry for the key is removed; the first value is returned.
566        assert_eq!(remove("a=1;b=2;a=3", "a"), ("b=2".into(), Some("1")));
567        // Removing an absent key leaves the parameters untouched.
568        assert_eq!(remove("x=1;y=2", "missing"), ("x=1;y=2".into(), None));
569        // Removing the only entry empties the parameters.
570        assert_eq!(remove("a=1", "a"), ("".into(), Some("1")));
571        // A value-less entry is removed and reported with an empty value.
572        assert_eq!(remove("flag;a=1", "flag"), ("a=1".into(), Some("")));
573
574        let mut params = Parameters::from("b=2;a=1;c=3");
575        assert_eq!(params.remove("a"), Some("1".to_string()));
576        assert_eq!(params.as_str(), "b=2;c=3");
577        assert_eq!(params.remove("missing"), None);
578        assert_eq!(params.as_str(), "b=2;c=3");
579    }
580}