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")]
188pub fn rand(into: &mut String) {
189    use rand::{
190        distributions::{Alphanumeric, DistString},
191        Rng,
192    };
193
194    const MIN: usize = 2;
195    const MAX: usize = 8;
196
197    let mut rng = rand::thread_rng();
198
199    let num = rng.gen_range(MIN..MAX);
200    for i in 0..num {
201        if i != 0 {
202            into.push(LIST_SEPARATOR);
203        }
204        let len = rng.gen_range(MIN..MAX);
205        let key = Alphanumeric.sample_string(&mut rng, len);
206        into.push_str(key.as_str());
207
208        into.push(FIELD_SEPARATOR);
209
210        let len = rng.gen_range(MIN..MAX);
211        let value = Alphanumeric.sample_string(&mut rng, len);
212        into.push_str(value.as_str());
213    }
214}
215
216/// A map of key/value (String,String) parameters.
217/// It can be parsed from a String, using `;` or `<newline>` as separator between each parameters
218/// and `=` as separator between a key and its value. Keys and values are trimmed.
219///
220/// Example:
221/// ```
222/// use zenoh_protocol::core::Parameters;
223///
224/// let a = "a=1;b=2;c=3|4|5;d=6";
225/// let p = Parameters::from(a);
226///
227/// // Retrieve values
228/// assert!(!p.is_empty());
229/// assert_eq!(p.get("a").unwrap(), "1");
230/// assert_eq!(p.get("b").unwrap(), "2");
231/// assert_eq!(p.get("c").unwrap(), "3|4|5");
232/// assert_eq!(p.get("d").unwrap(), "6");
233/// assert_eq!(p.values("c").collect::<Vec<&str>>(), vec!["3", "4", "5"]);
234///
235/// // Iterate over parameters
236/// let mut iter = p.iter();
237/// assert_eq!(iter.next().unwrap(), ("a", "1"));
238/// assert_eq!(iter.next().unwrap(), ("b", "2"));
239/// assert_eq!(iter.next().unwrap(), ("c", "3|4|5"));
240/// assert_eq!(iter.next().unwrap(), ("d", "6"));
241/// assert!(iter.next().is_none());
242///
243/// // Create parameters from iterators
244/// let pi = Parameters::from_iter(vec![("a", "1"), ("b", "2"), ("c", "3|4|5"), ("d", "6")]);
245/// assert_eq!(p, pi);
246/// ```
247#[derive(Clone, PartialEq, Eq, Hash, Default)]
248pub struct Parameters<'s>(Cow<'s, str>);
249
250impl<'s> Parameters<'s> {
251    /// Create empty parameters.
252    pub const fn empty() -> Self {
253        Self(Cow::Borrowed(""))
254    }
255
256    /// Returns `true` if parameters does not contain anything.
257    pub fn is_empty(&self) -> bool {
258        self.0.is_empty()
259    }
260
261    /// Returns parameters as [`str`].
262    pub fn as_str(&'s self) -> &'s str {
263        &self.0
264    }
265
266    /// Returns `true` if parameters contains the specified key.
267    pub fn contains_key<K>(&self, k: K) -> bool
268    where
269        K: Borrow<str>,
270    {
271        super::parameters::get(self.as_str(), k.borrow()).is_some()
272    }
273
274    /// Returns a reference to the `&str`-value corresponding to the key.
275    pub fn get<K>(&'s self, k: K) -> Option<&'s str>
276    where
277        K: Borrow<str>,
278    {
279        super::parameters::get(self.as_str(), k.borrow())
280    }
281
282    /// Returns an iterator to the `&str`-values corresponding to the key.
283    pub fn values<K>(&'s self, k: K) -> impl DoubleEndedIterator<Item = &'s str>
284    where
285        K: Borrow<str>,
286    {
287        super::parameters::values(self.as_str(), k.borrow())
288    }
289
290    /// Returns an iterator on the key-value pairs as `(&str, &str)`.
291    pub fn iter(&'s self) -> impl DoubleEndedIterator<Item = (&'s str, &'s str)> + Clone {
292        super::parameters::iter(self.as_str())
293    }
294
295    /// Inserts a key-value pair into the map.
296    /// If the map did not have this key present, [`None`]` is returned.
297    /// If the map did have this key present, the value is updated, and the old value is returned.
298    pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<String>
299    where
300        K: Borrow<str>,
301        V: Borrow<str>,
302    {
303        let (inner, item) = super::parameters::insert(self.as_str(), k.borrow(), v.borrow());
304        let item = item.map(|i| i.to_string());
305        self.0 = Cow::Owned(inner);
306        item
307    }
308
309    /// Removes a key from the map, returning the value at the key if the key was previously in the parameters.
310    pub fn remove<K>(&mut self, k: K) -> Option<String>
311    where
312        K: Borrow<str>,
313    {
314        let (inner, item) = super::parameters::remove(self.as_str(), k.borrow());
315        let item = item.map(|i| i.to_string());
316        self.0 = Cow::Owned(inner);
317        item
318    }
319
320    /// Extend these parameters with other parameters.
321    pub fn extend(&mut self, other: &Parameters) {
322        self.extend_from_iter(other.iter());
323    }
324
325    /// Extend these parameters from an iterator.
326    pub fn extend_from_iter<'e, I, K, V>(&mut self, iter: I)
327    where
328        I: Iterator<Item = (&'e K, &'e V)> + Clone,
329        K: Borrow<str> + 'e + ?Sized,
330        V: Borrow<str> + 'e + ?Sized,
331    {
332        let inner = super::parameters::from_iter(super::parameters::join(
333            self.iter(),
334            iter.map(|(k, v)| (k.borrow(), v.borrow())),
335        ));
336        self.0 = Cow::Owned(inner);
337    }
338
339    /// Convert these parameters into owned parameters.
340    pub fn into_owned(self) -> Parameters<'static> {
341        Parameters(Cow::Owned(self.0.into_owned()))
342    }
343
344    /// Returns `true`` if all keys are sorted in alphabetical order.
345    pub fn is_ordered(&self) -> bool {
346        super::parameters::is_ordered(self.as_str())
347    }
348}
349
350impl<'s> From<&'s str> for Parameters<'s> {
351    fn from(mut value: &'s str) -> Self {
352        value = value.trim_end_matches(|c| {
353            c == LIST_SEPARATOR || c == FIELD_SEPARATOR || c == VALUE_SEPARATOR
354        });
355        Self(Cow::Borrowed(value))
356    }
357}
358
359impl From<String> for Parameters<'_> {
360    fn from(mut value: String) -> Self {
361        let s = value.trim_end_matches(|c| {
362            c == LIST_SEPARATOR || c == FIELD_SEPARATOR || c == VALUE_SEPARATOR
363        });
364        value.truncate(s.len());
365        Self(Cow::Owned(value))
366    }
367}
368
369impl<'s> From<Cow<'s, str>> for Parameters<'s> {
370    fn from(value: Cow<'s, str>) -> Self {
371        match value {
372            Cow::Borrowed(s) => Parameters::from(s),
373            Cow::Owned(s) => Parameters::from(s),
374        }
375    }
376}
377
378impl<'a> From<Parameters<'a>> for Cow<'_, Parameters<'a>> {
379    fn from(props: Parameters<'a>) -> Self {
380        Cow::Owned(props)
381    }
382}
383
384impl<'a> From<&'a Parameters<'a>> for Cow<'a, Parameters<'a>> {
385    fn from(props: &'a Parameters<'a>) -> Self {
386        Cow::Borrowed(props)
387    }
388}
389
390impl<'s, K, V> FromIterator<(&'s K, &'s V)> for Parameters<'_>
391where
392    K: Borrow<str> + 's + ?Sized,
393    V: Borrow<str> + 's + ?Sized,
394{
395    fn from_iter<T: IntoIterator<Item = (&'s K, &'s V)>>(iter: T) -> Self {
396        let iter = iter.into_iter();
397        let inner = super::parameters::from_iter(iter.map(|(k, v)| (k.borrow(), v.borrow())));
398        Self(Cow::Owned(inner))
399    }
400}
401
402impl<'s, K, V> FromIterator<&'s (K, V)> for Parameters<'_>
403where
404    K: Borrow<str> + 's,
405    V: Borrow<str> + 's,
406{
407    fn from_iter<T: IntoIterator<Item = &'s (K, V)>>(iter: T) -> Self {
408        Self::from_iter(iter.into_iter().map(|(k, v)| (k.borrow(), v.borrow())))
409    }
410}
411
412impl<'s, K, V> From<&'s [(K, V)]> for Parameters<'_>
413where
414    K: Borrow<str> + 's,
415    V: Borrow<str> + 's,
416{
417    fn from(value: &'s [(K, V)]) -> Self {
418        Self::from_iter(value.iter())
419    }
420}
421
422#[cfg(feature = "std")]
423impl<K, V> From<HashMap<K, V>> for Parameters<'_>
424where
425    K: Borrow<str>,
426    V: Borrow<str>,
427{
428    fn from(map: HashMap<K, V>) -> Self {
429        Self::from_iter(map.iter())
430    }
431}
432
433#[cfg(feature = "std")]
434impl<'s> From<&'s Parameters<'s>> for HashMap<&'s str, &'s str> {
435    fn from(props: &'s Parameters<'s>) -> Self {
436        HashMap::from_iter(props.iter())
437    }
438}
439
440#[cfg(feature = "std")]
441impl From<&Parameters<'_>> for HashMap<String, String> {
442    fn from(props: &Parameters<'_>) -> Self {
443        HashMap::from_iter(props.iter().map(|(k, v)| (k.to_string(), v.to_string())))
444    }
445}
446
447#[cfg(feature = "std")]
448impl<'s> From<&'s Parameters<'s>> for HashMap<Cow<'s, str>, Cow<'s, str>> {
449    fn from(props: &'s Parameters<'s>) -> Self {
450        HashMap::from_iter(props.iter().map(|(k, v)| (Cow::from(k), Cow::from(v))))
451    }
452}
453
454#[cfg(feature = "std")]
455impl From<Parameters<'_>> for HashMap<String, String> {
456    fn from(props: Parameters) -> Self {
457        HashMap::from(&props)
458    }
459}
460
461impl fmt::Display for Parameters<'_> {
462    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
463        write!(f, "{}", self.0)
464    }
465}
466
467impl fmt::Debug for Parameters<'_> {
468    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
469        write!(f, "{self}")
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    #[test]
478    fn test_parameters() {
479        assert!(Parameters::from("").0.is_empty());
480
481        assert_eq!(Parameters::from("p1"), Parameters::from(&[("p1", "")][..]));
482
483        assert_eq!(
484            Parameters::from("p1=v1"),
485            Parameters::from(&[("p1", "v1")][..])
486        );
487
488        assert_eq!(
489            Parameters::from("p1=v1;p2=v2;"),
490            Parameters::from(&[("p1", "v1"), ("p2", "v2")][..])
491        );
492
493        assert_eq!(
494            Parameters::from("p1=v1;p2=v2;|="),
495            Parameters::from(&[("p1", "v1"), ("p2", "v2")][..])
496        );
497
498        assert_eq!(
499            Parameters::from("p1=v1;p2;p3=v3"),
500            Parameters::from(&[("p1", "v1"), ("p2", ""), ("p3", "v3")][..])
501        );
502
503        assert_eq!(
504            Parameters::from("p1=v 1;p 2=v2"),
505            Parameters::from(&[("p1", "v 1"), ("p 2", "v2")][..])
506        );
507
508        assert_eq!(
509            Parameters::from("p1=x=y;p2=a==b"),
510            Parameters::from(&[("p1", "x=y"), ("p2", "a==b")][..])
511        );
512
513        let mut hm: HashMap<String, String> = HashMap::new();
514        hm.insert("p1".to_string(), "v1".to_string());
515        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
516
517        let mut hm: HashMap<&str, &str> = HashMap::new();
518        hm.insert("p1", "v1");
519        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
520
521        let mut hm: HashMap<Cow<str>, Cow<str>> = HashMap::new();
522        hm.insert(Cow::from("p1"), Cow::from("v1"));
523        assert_eq!(Parameters::from(hm), Parameters::from("p1=v1"));
524    }
525}