use bitflags::bitflags;
pub use spa_sys::spa_dict_item;
use std::{convert::TryInto, ffi::CStr, fmt, marker::PhantomData, ptr};
#[repr(transparent)]
pub struct DictRef(spa_sys::spa_dict);
impl DictRef {
pub fn as_raw(&self) -> &spa_sys::spa_dict {
&self.0
}
pub fn as_raw_ptr(&self) -> *mut spa_sys::spa_dict {
self.as_raw() as *const _ as *mut _
}
pub fn iter_cstr(&self) -> CIter {
let items = if self.0.items.is_null() {
&[]
} else {
unsafe { std::slice::from_raw_parts(self.0.items, self.len()) }
};
CIter {
items,
_phantom: PhantomData,
}
}
pub fn iter(&self) -> Iter {
Iter {
inner: self.iter_cstr(),
}
}
pub fn keys(&self) -> Keys {
Keys {
inner: self.iter_cstr(),
}
}
pub fn values(&self) -> Values {
Values {
inner: self.iter_cstr(),
}
}
pub fn len(&self) -> usize {
self.0.n_items.try_into().unwrap()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn flags(&self) -> Flags {
Flags::from_bits_retain(self.0.flags)
}
pub fn get(&self, key: &str) -> Option<&str> {
self.iter().find(|(k, _)| *k == key).map(|(_, v)| v)
}
pub fn parse<T: ParsableValue>(&self, key: &str) -> Option<Result<T, ParseValueError>> {
self.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| match T::parse_value(v) {
Some(v) => Ok(v),
None => Err(ParseValueError {
value: v.to_string(),
type_name: std::any::type_name::<T>(),
}),
})
}
}
impl AsRef<Self> for DictRef {
fn as_ref(&self) -> &Self {
self
}
}
impl std::fmt::Debug for DictRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct Entries<'a>(CIter<'a>);
impl<'a> fmt::Debug for Entries<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.0.clone()).finish()
}
}
f.debug_struct("DictRef")
.field("flags", &self.flags())
.field("entries", &Entries(self.iter_cstr()))
.finish()
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct ParseValueError {
value: String,
type_name: &'static str,
}
impl std::error::Error for ParseValueError {}
impl fmt::Display for ParseValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "'{}' cannot be parsed to {}", self.value, self.type_name)
}
}
pub trait ParsableValue: Copy {
fn parse_value(value: &str) -> Option<Self>;
}
impl ParsableValue for bool {
fn parse_value(value: &str) -> Option<Self> {
if value == "true" {
Some(true)
} else {
match value.parse::<i32>() {
Ok(1) => Some(true),
_ => Some(false),
}
}
}
}
macro_rules! impl_parsable_value_numeric {
($type_:ty) => {
impl ParsableValue for $type_ {
fn parse_value(value: &str) -> Option<Self> {
value.parse().ok()
}
}
};
}
impl_parsable_value_numeric!(i32);
impl_parsable_value_numeric!(i64);
impl_parsable_value_numeric!(u64);
impl_parsable_value_numeric!(f32);
impl_parsable_value_numeric!(f64);
impl_parsable_value_numeric!(i8);
impl_parsable_value_numeric!(u8);
impl_parsable_value_numeric!(i16);
impl_parsable_value_numeric!(u16);
impl_parsable_value_numeric!(u32);
impl_parsable_value_numeric!(i128);
impl_parsable_value_numeric!(u128);
impl_parsable_value_numeric!(isize);
impl_parsable_value_numeric!(usize);
const POINTER_PREFIX: &str = "pointer:0x";
impl<T> ParsableValue for *const T {
fn parse_value(value: &str) -> Option<Self> {
match value
.strip_prefix(POINTER_PREFIX)
.map(|addr| usize::from_str_radix(addr, 16))
{
Some(Ok(addr)) => Some(addr as *const T),
_ => None,
}
}
}
bitflags! {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Flags: u32 {
const SORTED = spa_sys::SPA_DICT_FLAG_SORTED;
}
}
#[derive(Clone)]
pub struct CIter<'a> {
items: &'a [spa_sys::spa_dict_item],
_phantom: PhantomData<&'a str>,
}
impl<'a> Iterator for CIter<'a> {
type Item = (&'a CStr, &'a CStr);
fn next(&mut self) -> Option<Self::Item> {
self.items.split_first().map(|(item, rest)| {
self.items = rest;
let k = unsafe { CStr::from_ptr(item.key) };
let v = unsafe { CStr::from_ptr(item.value) };
(k, v)
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let bound = self.items.len();
(bound, Some(bound))
}
}
pub struct Iter<'a> {
inner: CIter<'a>,
}
impl<'a> Iterator for Iter<'a> {
type Item = (&'a str, &'a str);
fn next(&mut self) -> Option<Self::Item> {
self.inner
.find_map(|(k, v)| k.to_str().ok().zip(v.to_str().ok()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.inner.size_hint().1)
}
}
pub struct Keys<'a> {
inner: CIter<'a>,
}
impl<'a> Iterator for Keys<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.inner.find_map(|(k, _)| k.to_str().ok())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
pub struct Values<'a> {
inner: CIter<'a>,
}
impl<'a> Iterator for Values<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.inner.find_map(|(_, v)| v.to_str().ok())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
pub struct StaticDict {
ptr: ptr::NonNull<spa_sys::spa_dict>,
}
impl StaticDict {
pub const unsafe fn from_ptr(ptr: ptr::NonNull<spa_sys::spa_dict>) -> Self {
Self { ptr }
}
}
#[macro_export]
macro_rules! static_dict {
{$($k:expr => $v:expr),+ $(,)?} => {{
use $crate::utils::dict::{spa_dict_item, StaticDict, Flags};
use std::ptr;
static mut ITEMS: &[spa_dict_item] = &[
$(
spa_dict_item {
key: concat!($k, "\0").as_ptr() as *const std::os::raw::c_char,
value: concat!($v, "\0").as_ptr() as *const std::os::raw::c_char
},
)+
];
static mut RAW: spa_sys::spa_dict = unsafe {
spa_sys::spa_dict {
flags: Flags::empty().bits(),
n_items: ITEMS.len() as u32,
items: ITEMS.as_ptr(),
}
};
unsafe {
let ptr = std::ptr::addr_of!(RAW).cast_mut();
StaticDict::from_ptr(ptr::NonNull::new_unchecked(ptr))
}
}};
}
impl std::ops::Deref for StaticDict {
type Target = DictRef;
fn deref(&self) -> &Self::Target {
unsafe { self.ptr.cast::<Self::Target>().as_ref() }
}
}
impl fmt::Debug for StaticDict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dict: &DictRef = self.as_ref();
f.debug_tuple("StaticDict").field(dict).finish()
}
}
unsafe impl Send for StaticDict {}
unsafe impl Sync for StaticDict {}
#[cfg(test)]
mod tests {
use super::{DictRef, Flags, StaticDict};
use spa_sys::spa_dict;
use std::ptr;
#[test]
fn test_empty_dict() {
let raw = spa_dict {
flags: Flags::empty().bits(),
n_items: 0,
items: ptr::null(),
};
let dict = DictRef(raw);
let iter = dict.iter_cstr();
assert_eq!(0, dict.len());
iter.for_each(|_| panic!("Iterated over non-existing item"));
}
#[test]
fn test_iter_cstr() {
let dict = static_dict! {
"K0" => "V0",
"K1" => "V1"
};
let mut iter = dict.iter_cstr();
assert_eq!((c"K0", c"V0"), iter.next().unwrap());
assert_eq!((c"K1", c"V1"), iter.next().unwrap());
assert_eq!(None, iter.next());
}
#[test]
fn test_iterators() {
let dict = static_dict! {
"K0" => "V0",
"K1" => "V1"
};
let mut iter = dict.iter();
assert_eq!(("K0", "V0"), iter.next().unwrap());
assert_eq!(("K1", "V1"), iter.next().unwrap());
assert_eq!(None, iter.next());
let mut key_iter = dict.keys();
assert_eq!("K0", key_iter.next().unwrap());
assert_eq!("K1", key_iter.next().unwrap());
assert_eq!(None, key_iter.next());
let mut val_iter = dict.values();
assert_eq!("V0", val_iter.next().unwrap());
assert_eq!("V1", val_iter.next().unwrap());
assert_eq!(None, val_iter.next());
}
#[test]
fn test_get() {
let dict = static_dict! {
"K0" => "V0"
};
assert_eq!(Some("V0"), dict.get("K0"));
}
#[test]
fn test_debug() {
let dict = static_dict! {
"K0" => "V0"
};
assert_eq!(
r#"StaticDict(DictRef { flags: Flags(0x0), entries: {"K0": "V0"} })"#,
&format!("{:?}", dict)
);
let raw = spa_dict {
flags: Flags::SORTED.bits(),
n_items: 0,
items: ptr::null(),
};
let dict = DictRef(raw);
assert_eq!(
r#"DictRef { flags: Flags(SORTED), entries: {} }"#,
&format!("{:?}", dict)
);
}
#[test]
fn static_dict() {
static DICT: StaticDict = static_dict! {
"K0" => "V0",
"K1" => "V1"
};
assert_eq!(DICT.len(), 2);
assert_eq!(DICT.get("K0"), Some("V0"));
assert_eq!(DICT.get("K1"), Some("V1"));
}
#[test]
fn parse() {
use super::ParseValueError;
static DICT: StaticDict = static_dict! {
"true" => "true",
"false" => "false",
"1" => "1",
"10" => "10",
"-10" => "-10",
"i64-max" => "9223372036854775807",
"1.5" => "1.5",
"-1.5" => "-1.5",
"pointer" => "pointer:0xdeadbeef",
"badger" => "badger"
};
macro_rules! parse_error {
($key:literal, $type_:ty) => {
assert!(matches!(
DICT.parse::<$type_>($key),
Some(Err(ParseValueError { .. }))
));
};
}
assert_eq!(DICT.parse::<bool>("missing"), None);
assert_eq!(DICT.parse("true"), Some(Ok(true)));
assert_eq!(DICT.parse("1"), Some(Ok(true)));
assert_eq!(DICT.parse("false"), Some(Ok(false)));
assert_eq!(DICT.parse("10"), Some(Ok(false)));
assert_eq!(DICT.parse("badger"), Some(Ok(false)));
assert_eq!(DICT.parse::<i32>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<i32>("-10"), Some(Ok(-10)));
parse_error!("badger", i32);
parse_error!("i64-max", i32);
assert_eq!(DICT.parse::<i64>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<i64>("-10"), Some(Ok(-10)));
assert_eq!(DICT.parse::<i64>("i64-max"), Some(Ok(i64::MAX)));
parse_error!("badger", i64);
assert_eq!(DICT.parse::<u64>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<u64>("i64-max"), Some(Ok(i64::MAX as u64)));
parse_error!("-10", u64);
parse_error!("badger", u64);
assert_eq!(DICT.parse::<i8>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<i8>("-10"), Some(Ok(-10)));
assert_eq!(DICT.parse::<u8>("1"), Some(Ok(1)));
parse_error!("-10", u8);
assert_eq!(DICT.parse::<i16>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<i16>("-10"), Some(Ok(-10)));
assert_eq!(DICT.parse::<u16>("1"), Some(Ok(1)));
parse_error!("-10", u16);
assert_eq!(DICT.parse::<u32>("1"), Some(Ok(1)));
parse_error!("-10", u32);
assert_eq!(DICT.parse::<i128>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<i128>("-10"), Some(Ok(-10)));
assert_eq!(DICT.parse::<u128>("1"), Some(Ok(1)));
parse_error!("-10", u128);
assert_eq!(DICT.parse::<isize>("1"), Some(Ok(1)));
assert_eq!(DICT.parse::<isize>("-10"), Some(Ok(-10)));
assert_eq!(DICT.parse::<usize>("1"), Some(Ok(1)));
parse_error!("-10", usize);
assert_eq!(DICT.parse::<f32>("1"), Some(Ok(1.0)));
assert_eq!(DICT.parse::<f32>("-10"), Some(Ok(-10.0)));
assert_eq!(DICT.parse::<f32>("1.5"), Some(Ok(1.5)));
assert_eq!(DICT.parse::<f32>("-1.5"), Some(Ok(-1.5)));
parse_error!("badger", f32);
assert_eq!(DICT.parse::<f64>("1"), Some(Ok(1.0)));
assert_eq!(DICT.parse::<f64>("-10"), Some(Ok(-10.0)));
assert_eq!(DICT.parse::<f64>("1.5"), Some(Ok(1.5)));
assert_eq!(DICT.parse::<f64>("-1.5"), Some(Ok(-1.5)));
parse_error!("badger", f64);
let ptr = DICT.parse::<*const i32>("pointer").unwrap().unwrap();
assert!(!ptr.is_null());
parse_error!("badger", *const i32);
}
}