1use 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
46pub 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
53pub 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
63pub 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#[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
87pub 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
95pub fn get<'s>(s: &'s str, k: &str) -> Option<&'s str> {
97 iter(s).find(|(key, _)| *key == k).map(|(_, value)| value)
98}
99
100pub 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
128pub 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
134pub 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
140pub 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
148pub 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#[derive(Clone, PartialEq, Eq, Hash, Default)]
248pub struct Parameters<'s>(Cow<'s, str>);
249
250impl<'s> Parameters<'s> {
251 pub const fn empty() -> Self {
253 Self(Cow::Borrowed(""))
254 }
255
256 pub fn is_empty(&self) -> bool {
258 self.0.is_empty()
259 }
260
261 pub fn as_str(&'s self) -> &'s str {
263 &self.0
264 }
265
266 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 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 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 pub fn iter(&'s self) -> impl DoubleEndedIterator<Item = (&'s str, &'s str)> + Clone {
292 super::parameters::iter(self.as_str())
293 }
294
295 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 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 pub fn extend(&mut self, other: &Parameters) {
322 self.extend_from_iter(other.iter());
323 }
324
325 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 pub fn into_owned(self) -> Parameters<'static> {
341 Parameters(Cow::Owned(self.0.into_owned()))
342 }
343
344 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}