libspa/utils/
dict.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! Dictionary types and traits.
5
6use bitflags::bitflags;
7// re-exported as used in the static_dict! macro implementation
8pub use spa_sys::spa_dict_item;
9use std::{convert::TryInto, ffi::CStr, fmt, marker::PhantomData, ptr};
10
11#[repr(transparent)]
12pub struct DictRef(spa_sys::spa_dict);
13
14impl DictRef {
15    /// Returns a reference to the raw [`spa_sys::spa_dict`] this struct represents.
16    pub fn as_raw(&self) -> &spa_sys::spa_dict {
17        &self.0
18    }
19
20    /// Returns the pointer to the raw [`spa_sys::spa_dict`] this struct represents.
21    ///
22    /// # Safety
23    /// The returned pointer must not be used after the [`DictRef`] reference this method is called on becomes invalid.
24    pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_dict {
25        self.as_raw() as *const _ as *mut _
26    }
27
28    /// An iterator over all key-value pairs as `(&CStr, &CStr)` tuples.
29    ///
30    /// Use [`iter`](Self::iter) to iterate over all valid utf-8 pairs as (&str, &str) tuples instead.
31    pub fn iter_cstr(&self) -> CIter<'_> {
32        let items = if self.0.items.is_null() {
33            &[]
34        } else {
35            unsafe { std::slice::from_raw_parts(self.0.items, self.len()) }
36        };
37
38        CIter {
39            items,
40            _phantom: PhantomData,
41        }
42    }
43
44    /// An iterator over all key-value pairs that are valid utf-8.
45    /// The iterator element type is `(&str, &str)`.
46    pub fn iter(&self) -> Iter<'_> {
47        Iter {
48            inner: self.iter_cstr(),
49        }
50    }
51
52    /// An iterator over all keys that are valid utf-8.
53    /// The iterator element type is &str.
54    pub fn keys(&self) -> Keys<'_> {
55        Keys {
56            inner: self.iter_cstr(),
57        }
58    }
59
60    /// An iterator over all values that are valid utf-8.
61    /// The iterator element type is &str.
62    pub fn values(&self) -> Values<'_> {
63        Values {
64            inner: self.iter_cstr(),
65        }
66    }
67
68    /// Returns the number of key-value-pairs in the dict.
69    /// This is the number of all pairs, not only pairs that are valid-utf8.
70    pub fn len(&self) -> usize {
71        self.0.n_items.try_into().unwrap()
72    }
73
74    /// Returns `true` if the dict is empty, `false` if it is not.
75    pub fn is_empty(&self) -> bool {
76        self.len() == 0
77    }
78
79    /// Returns the bitflags that are set for the dict.
80    pub fn flags(&self) -> Flags {
81        Flags::from_bits_retain(self.0.flags)
82    }
83
84    /// Get the value associated with the provided key.
85    ///
86    /// If the dict does not contain the key or the value is non-utf8, `None` is returned.
87    /// Use [`iter_cstr`] if you need a non-utf8 key or value.
88    ///
89    /// [`iter_cstr`]: #method.iter_cstr
90    pub fn get(&self, key: &str) -> Option<&str> {
91        self.iter().find(|(k, _)| *k == key).map(|(_, v)| v)
92    }
93
94    /// Get the value associated with the provided key and convert it to a given type.
95    ///
96    /// If the dict does not contain the key or the value is non-utf8, `None` is returned.
97    ///
98    /// If the value associated with the key cannot be parsed to the requested type,
99    /// `Some(Err(ParseValueError))` is returned.
100    ///
101    /// See [`ParsableValue#foreign-impls`] for all the types which can be produced by this method.
102    ///
103    /// # Examples
104    /// ```
105    /// use libspa::{utils::dict::StaticDict, static_dict};
106    ///
107    /// static DICT: StaticDict = static_dict! {
108    ///     "true" => "true",
109    ///     "ten" => "10",
110    ///     "pi" => "3.14159265359",
111    ///     "pointer" => "pointer:0xdeadbeef"
112    /// };
113    ///
114    /// assert_eq!(DICT.parse("true"), Some(Ok(true)));
115    /// assert_eq!(DICT.parse("ten"), Some(Ok(10)));
116    /// assert_eq!(DICT.parse("ten"), Some(Ok(10.0)));
117    /// assert_eq!(DICT.parse("pi"), Some(Ok(3.14159265359)));
118    ///
119    /// let ptr = DICT.parse::<*const i32>("pointer").unwrap().unwrap();
120    /// assert!(!ptr.is_null());
121    /// ```
122    pub fn parse<T: ParsableValue>(&self, key: &str) -> Option<Result<T, ParseValueError>> {
123        self.iter()
124            .find(|(k, _)| *k == key)
125            .map(|(_, v)| match T::parse_value(v) {
126                Some(v) => Ok(v),
127                None => Err(ParseValueError {
128                    value: v.to_string(),
129                    type_name: std::any::type_name::<T>(),
130                }),
131            })
132    }
133}
134
135impl AsRef<Self> for DictRef {
136    fn as_ref(&self) -> &Self {
137        self
138    }
139}
140
141impl std::fmt::Debug for DictRef {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        struct Entries<'a>(CIter<'a>);
144
145        impl<'a> fmt::Debug for Entries<'a> {
146            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147                f.debug_map().entries(self.0.clone()).finish()
148            }
149        }
150
151        f.debug_struct("DictRef")
152            .field("flags", &self.flags())
153            .field("entries", &Entries(self.iter_cstr()))
154            .finish()
155    }
156}
157
158/// An error raised by [`DictRef::parse`] if the value cannot be converted to the requested type.
159#[derive(Debug, Eq, PartialEq)]
160pub struct ParseValueError {
161    value: String,
162    type_name: &'static str,
163}
164
165impl std::error::Error for ParseValueError {}
166
167impl fmt::Display for ParseValueError {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        write!(f, "'{}' cannot be parsed to {}", self.value, self.type_name)
170    }
171}
172
173/// Trait implemented on types which can be returned by [`DictRef::parse`].
174pub trait ParsableValue: Copy {
175    /// Try parsing `value` to convert it to the requested type.
176    fn parse_value(value: &str) -> Option<Self>;
177}
178
179impl ParsableValue for bool {
180    fn parse_value(value: &str) -> Option<Self> {
181        // Same logic as pw_properties_parse_bool()
182        if value == "true" {
183            Some(true)
184        } else {
185            match value.parse::<i32>() {
186                Ok(1) => Some(true),
187                _ => Some(false),
188            }
189        }
190    }
191}
192
193macro_rules! impl_parsable_value_numeric {
194    ($type_:ty) => {
195        impl ParsableValue for $type_ {
196            fn parse_value(value: &str) -> Option<Self> {
197                value.parse().ok()
198            }
199        }
200    };
201}
202
203impl_parsable_value_numeric!(i32);
204impl_parsable_value_numeric!(i64);
205impl_parsable_value_numeric!(u64);
206impl_parsable_value_numeric!(f32);
207impl_parsable_value_numeric!(f64);
208// not implemented in properties.h but good to have
209impl_parsable_value_numeric!(i8);
210impl_parsable_value_numeric!(u8);
211impl_parsable_value_numeric!(i16);
212impl_parsable_value_numeric!(u16);
213impl_parsable_value_numeric!(u32);
214impl_parsable_value_numeric!(i128);
215impl_parsable_value_numeric!(u128);
216impl_parsable_value_numeric!(isize);
217impl_parsable_value_numeric!(usize);
218
219const POINTER_PREFIX: &str = "pointer:0x";
220
221impl<T> ParsableValue for *const T {
222    fn parse_value(value: &str) -> Option<Self> {
223        match value
224            .strip_prefix(POINTER_PREFIX)
225            .map(|addr| usize::from_str_radix(addr, 16))
226        {
227            Some(Ok(addr)) => Some(addr as *const T),
228            _ => None,
229        }
230    }
231}
232
233bitflags! {
234    /// Dictionary flags
235    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
236    pub struct Flags: u32 {
237        // These flags are redefinitions from
238        // https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/spa/include/spa/utils/dict.h
239        /// Dictionary has been sorted.
240        const SORTED = spa_sys::SPA_DICT_FLAG_SORTED;
241    }
242}
243
244/// Iterator on a dictionary's keys and values exposed as [`CStr`].
245#[derive(Clone)]
246pub struct CIter<'a> {
247    items: &'a [spa_sys::spa_dict_item],
248    _phantom: PhantomData<&'a str>,
249}
250
251impl<'a> Iterator for CIter<'a> {
252    type Item = (&'a CStr, &'a CStr);
253
254    fn next(&mut self) -> Option<Self::Item> {
255        self.items.split_first().map(|(item, rest)| {
256            self.items = rest;
257            let k = unsafe { CStr::from_ptr(item.key) };
258            let v = unsafe { CStr::from_ptr(item.value) };
259            (k, v)
260        })
261    }
262
263    fn size_hint(&self) -> (usize, Option<usize>) {
264        let bound = self.items.len();
265        // We know the exact value, so lower bound and upper bound are the same.
266        (bound, Some(bound))
267    }
268}
269
270/// Iterator on a dictionary's keys and values exposed as [`str`].
271pub struct Iter<'a> {
272    inner: CIter<'a>,
273}
274
275impl<'a> Iterator for Iter<'a> {
276    type Item = (&'a str, &'a str);
277
278    fn next(&mut self) -> Option<Self::Item> {
279        self.inner
280            .find_map(|(k, v)| k.to_str().ok().zip(v.to_str().ok()))
281    }
282
283    fn size_hint(&self) -> (usize, Option<usize>) {
284        // Lower bound is 0, as all keys left might not be valid UTF-8.
285        (0, self.inner.size_hint().1)
286    }
287}
288
289/// Iterator on a dictionary's keys.
290pub struct Keys<'a> {
291    inner: CIter<'a>,
292}
293
294impl<'a> Iterator for Keys<'a> {
295    type Item = &'a str;
296
297    fn next(&mut self) -> Option<Self::Item> {
298        self.inner.find_map(|(k, _)| k.to_str().ok())
299    }
300
301    fn size_hint(&self) -> (usize, Option<usize>) {
302        self.inner.size_hint()
303    }
304}
305
306/// Iterator on a dictionary's values.
307pub struct Values<'a> {
308    inner: CIter<'a>,
309}
310
311impl<'a> Iterator for Values<'a> {
312    type Item = &'a str;
313
314    fn next(&mut self) -> Option<Self::Item> {
315        self.inner.find_map(|(_, v)| v.to_str().ok())
316    }
317
318    fn size_hint(&self) -> (usize, Option<usize>) {
319        self.inner.size_hint()
320    }
321}
322
323/// A collection of static key/value pairs.
324///
325/// # Examples
326/// Create a `StaticDict` and access the stored values by key:
327/// ```rust
328/// use libspa::{utils::dict::StaticDict, static_dict};
329///
330/// static DICT: StaticDict = static_dict!{
331///     "Key" => "Value",
332///     "OtherKey" => "OtherValue"
333/// };
334///
335/// assert_eq!(Some("Value"), DICT.get("Key"));
336/// assert_eq!(Some("OtherValue"), DICT.get("OtherKey"));
337/// ```
338pub struct StaticDict {
339    ptr: ptr::NonNull<spa_sys::spa_dict>,
340}
341
342impl StaticDict {
343    /// Create a [`StaticDict`] from an existing raw `spa_dict` pointer.
344    ///
345    /// # Safety
346    /// - The provided pointer must point to a valid, well-aligned `spa_dict` struct.
347    /// - The struct and its content need to stay alive during the whole lifetime of the `StaticDict`.
348    /// - The keys and values stored in this dict have to be static strings.
349    pub const unsafe fn from_ptr(ptr: ptr::NonNull<spa_sys::spa_dict>) -> Self {
350        Self { ptr }
351    }
352}
353
354/// A macro for creating a new [`StaticDict`] with predefined key-value pairs.
355///
356/// The macro accepts a list of static `Key => Value` pairs, separated by commas.
357///
358/// # Examples:
359/// Create a `StaticDict`.
360/// ```rust
361/// use libspa::{utils::dict::StaticDict, static_dict};
362///
363/// static PROPS: StaticDict = static_dict!{
364///    "Key1" => "Value1",
365///    "Key2" => "Value2",
366/// };
367/// ```
368#[macro_export]
369macro_rules! static_dict {
370    {$($k:expr => $v:expr),+ $(,)?} => {{
371        use $crate::utils::dict::{spa_dict_item, StaticDict, Flags};
372        use std::ptr;
373
374        static mut ITEMS: *const [spa_dict_item] = &[
375            $(
376                spa_dict_item {
377                    key: concat!($k, "\0").as_ptr() as *const std::os::raw::c_char,
378                    value: concat!($v, "\0").as_ptr() as *const std::os::raw::c_char
379                },
380            )+
381        ];
382
383        static mut RAW: spa_sys::spa_dict = unsafe {
384            spa_sys::spa_dict {
385                flags: Flags::empty().bits(),
386                n_items: ITEMS.len() as u32,
387                items: ITEMS as *const spa_dict_item,
388            }
389        };
390
391        unsafe {
392            let ptr = std::ptr::addr_of!(RAW).cast_mut();
393            StaticDict::from_ptr(ptr::NonNull::new_unchecked(ptr))
394        }
395    }};
396}
397
398impl std::ops::Deref for StaticDict {
399    type Target = DictRef;
400
401    fn deref(&self) -> &Self::Target {
402        unsafe { self.ptr.cast::<Self::Target>().as_ref() }
403    }
404}
405
406impl fmt::Debug for StaticDict {
407    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408        let dict: &DictRef = self.as_ref();
409        // FIXME: Debug-print dict keys and values directly
410        f.debug_tuple("StaticDict").field(dict).finish()
411    }
412}
413
414unsafe impl Send for StaticDict {}
415unsafe impl Sync for StaticDict {}
416
417#[cfg(test)]
418mod tests {
419    use super::{DictRef, Flags, StaticDict};
420    use spa_sys::spa_dict;
421    use std::ptr;
422
423    #[test]
424    fn test_empty_dict() {
425        let raw = spa_dict {
426            flags: Flags::empty().bits(),
427            n_items: 0,
428            items: ptr::null(),
429        };
430
431        let dict = DictRef(raw);
432        let iter = dict.iter_cstr();
433
434        assert_eq!(0, dict.len());
435
436        iter.for_each(|_| panic!("Iterated over non-existing item"));
437    }
438
439    #[test]
440    fn test_iter_cstr() {
441        let dict = static_dict! {
442            "K0" => "V0",
443            "K1" => "V1"
444        };
445
446        let mut iter = dict.iter_cstr();
447        assert_eq!((c"K0", c"V0"), iter.next().unwrap());
448        assert_eq!((c"K1", c"V1"), iter.next().unwrap());
449        assert_eq!(None, iter.next());
450    }
451
452    #[test]
453    fn test_iterators() {
454        let dict = static_dict! {
455            "K0" => "V0",
456            "K1" => "V1"
457        };
458
459        let mut iter = dict.iter();
460        assert_eq!(("K0", "V0"), iter.next().unwrap());
461        assert_eq!(("K1", "V1"), iter.next().unwrap());
462        assert_eq!(None, iter.next());
463
464        let mut key_iter = dict.keys();
465        assert_eq!("K0", key_iter.next().unwrap());
466        assert_eq!("K1", key_iter.next().unwrap());
467        assert_eq!(None, key_iter.next());
468
469        let mut val_iter = dict.values();
470        assert_eq!("V0", val_iter.next().unwrap());
471        assert_eq!("V1", val_iter.next().unwrap());
472        assert_eq!(None, val_iter.next());
473    }
474
475    #[test]
476    fn test_get() {
477        let dict = static_dict! {
478            "K0" => "V0"
479        };
480
481        assert_eq!(Some("V0"), dict.get("K0"));
482    }
483
484    #[test]
485    fn test_debug() {
486        let dict = static_dict! {
487            "K0" => "V0"
488        };
489
490        assert_eq!(
491            r#"StaticDict(DictRef { flags: Flags(0x0), entries: {"K0": "V0"} })"#,
492            &format!("{:?}", dict)
493        );
494
495        let raw = spa_dict {
496            flags: Flags::SORTED.bits(),
497            n_items: 0,
498            items: ptr::null(),
499        };
500
501        let dict = DictRef(raw);
502
503        assert_eq!(
504            r#"DictRef { flags: Flags(SORTED), entries: {} }"#,
505            &format!("{:?}", dict)
506        );
507    }
508
509    #[test]
510    fn static_dict() {
511        static DICT: StaticDict = static_dict! {
512            "K0" => "V0",
513            "K1" => "V1"
514        };
515
516        assert_eq!(DICT.len(), 2);
517        assert_eq!(DICT.get("K0"), Some("V0"));
518        assert_eq!(DICT.get("K1"), Some("V1"));
519    }
520
521    #[test]
522    fn parse() {
523        use super::ParseValueError;
524
525        static DICT: StaticDict = static_dict! {
526            "true" => "true",
527            "false" => "false",
528            "1" => "1",
529            "10" => "10",
530            "-10" => "-10",
531            "i64-max" => "9223372036854775807",
532            "1.5" => "1.5",
533            "-1.5" => "-1.5",
534            "pointer" => "pointer:0xdeadbeef",
535            "badger" => "badger"
536        };
537
538        macro_rules! parse_error {
539            ($key:literal, $type_:ty) => {
540                assert!(matches!(
541                    DICT.parse::<$type_>($key),
542                    Some(Err(ParseValueError { .. }))
543                ));
544            };
545        }
546
547        assert_eq!(DICT.parse::<bool>("missing"), None);
548
549        assert_eq!(DICT.parse("true"), Some(Ok(true)));
550        assert_eq!(DICT.parse("1"), Some(Ok(true)));
551        assert_eq!(DICT.parse("false"), Some(Ok(false)));
552        assert_eq!(DICT.parse("10"), Some(Ok(false)));
553        assert_eq!(DICT.parse("badger"), Some(Ok(false)));
554
555        /* integer types */
556        assert_eq!(DICT.parse::<i32>("1"), Some(Ok(1)));
557        assert_eq!(DICT.parse::<i32>("-10"), Some(Ok(-10)));
558        parse_error!("badger", i32);
559        parse_error!("i64-max", i32);
560
561        assert_eq!(DICT.parse::<i64>("1"), Some(Ok(1)));
562        assert_eq!(DICT.parse::<i64>("-10"), Some(Ok(-10)));
563        assert_eq!(DICT.parse::<i64>("i64-max"), Some(Ok(i64::MAX)));
564        parse_error!("badger", i64);
565
566        assert_eq!(DICT.parse::<u64>("1"), Some(Ok(1)));
567        assert_eq!(DICT.parse::<u64>("i64-max"), Some(Ok(i64::MAX as u64)));
568        parse_error!("-10", u64);
569        parse_error!("badger", u64);
570
571        assert_eq!(DICT.parse::<i8>("1"), Some(Ok(1)));
572        assert_eq!(DICT.parse::<i8>("-10"), Some(Ok(-10)));
573
574        assert_eq!(DICT.parse::<u8>("1"), Some(Ok(1)));
575        parse_error!("-10", u8);
576
577        assert_eq!(DICT.parse::<i16>("1"), Some(Ok(1)));
578        assert_eq!(DICT.parse::<i16>("-10"), Some(Ok(-10)));
579
580        assert_eq!(DICT.parse::<u16>("1"), Some(Ok(1)));
581        parse_error!("-10", u16);
582
583        assert_eq!(DICT.parse::<u32>("1"), Some(Ok(1)));
584        parse_error!("-10", u32);
585
586        assert_eq!(DICT.parse::<i128>("1"), Some(Ok(1)));
587        assert_eq!(DICT.parse::<i128>("-10"), Some(Ok(-10)));
588
589        assert_eq!(DICT.parse::<u128>("1"), Some(Ok(1)));
590        parse_error!("-10", u128);
591
592        assert_eq!(DICT.parse::<isize>("1"), Some(Ok(1)));
593        assert_eq!(DICT.parse::<isize>("-10"), Some(Ok(-10)));
594
595        assert_eq!(DICT.parse::<usize>("1"), Some(Ok(1)));
596        parse_error!("-10", usize);
597
598        /* floating-point types */
599        assert_eq!(DICT.parse::<f32>("1"), Some(Ok(1.0)));
600        assert_eq!(DICT.parse::<f32>("-10"), Some(Ok(-10.0)));
601        assert_eq!(DICT.parse::<f32>("1.5"), Some(Ok(1.5)));
602        assert_eq!(DICT.parse::<f32>("-1.5"), Some(Ok(-1.5)));
603        parse_error!("badger", f32);
604
605        assert_eq!(DICT.parse::<f64>("1"), Some(Ok(1.0)));
606        assert_eq!(DICT.parse::<f64>("-10"), Some(Ok(-10.0)));
607        assert_eq!(DICT.parse::<f64>("1.5"), Some(Ok(1.5)));
608        assert_eq!(DICT.parse::<f64>("-1.5"), Some(Ok(-1.5)));
609        parse_error!("badger", f64);
610
611        /* pointer */
612        let ptr = DICT.parse::<*const i32>("pointer").unwrap().unwrap();
613        assert!(!ptr.is_null());
614        parse_error!("badger", *const i32);
615    }
616}