use std::collections::HashMap;
use tokio_dbus::{BodyBuf, Signature};
use crate::{Decode, Encode, Result, Value};
#[track_caller]
fn round_trip<T>(signature: &str, value: T) -> Result<()>
where
T: Encode + Decode + PartialEq + std::fmt::Debug,
{
let signature = Signature::new(signature)?;
let mut buf = BodyBuf::new();
buf.extend_signature(signature)?;
value.encode(&mut buf.raw());
assert_eq!(buf.signature(), signature);
let mut body = buf.as_body();
let actual = T::decode(&mut body)?;
assert_eq!(actual, value);
assert!(body.is_empty(), "Body was not fully consumed");
Ok(())
}
#[test]
fn scalars() -> Result<()> {
round_trip("y", 42u8)?;
round_trip("b", true)?;
round_trip("n", -1i16)?;
round_trip("q", 1u16)?;
round_trip("i", -1i32)?;
round_trip("u", 1u32)?;
round_trip("x", -1i64)?;
round_trip("t", 1u64)?;
round_trip("d", 1.5f64)?;
round_trip("s", String::from("Hello World!"))?;
Ok(())
}
#[test]
fn arrays() -> Result<()> {
round_trip("as", vec![String::from("a"), String::from("b")])?;
round_trip("ay", vec![1u8, 2, 3])?;
round_trip("at", vec![1u64, 2])?;
round_trip("at", Vec::<u64>::new())?;
round_trip("aas", vec![vec![String::from("a")], Vec::new()])?;
Ok(())
}
#[test]
fn structs() -> Result<()> {
round_trip("(is)", (1i32, String::from("one")))?;
round_trip("a(iiay)", vec![(1i32, 2i32, vec![3u8, 4])])?;
round_trip("(y(us))", (1u8, (2u32, String::from("two"))))?;
Ok(())
}
#[test]
fn dicts() -> Result<()> {
let mut map = HashMap::new();
map.insert(String::from("a"), 1u32);
map.insert(String::from("b"), 2u32);
round_trip("a{su}", map)?;
round_trip("a{su}", HashMap::<String, u32>::new())?;
Ok(())
}
#[test]
fn variants() -> Result<()> {
round_trip("v", Value::U8(1))?;
round_trip("v", Value::String("Hello".into()))?;
round_trip("v", Value::Bool(true))?;
round_trip(
"v",
Value::Struct(vec![Value::I32(1), Value::String("one".into())]),
)?;
round_trip(
"v",
Value::Array {
element: Signature::new("s")?.to_owned(),
values: vec![Value::String("a".into()), Value::String("b".into())],
},
)?;
round_trip("v", Value::array("(iiay)")?)?;
Ok(())
}
#[test]
fn hints() -> Result<()> {
let mut hints = HashMap::new();
hints.insert(String::from("urgency"), Value::U8(1));
hints.insert(String::from("category"), Value::String("device".into()));
hints.insert(
String::from("image-data"),
Value::Struct(vec![
Value::I32(1),
Value::I32(1),
Value::I32(4),
Value::Bool(true),
Value::I32(8),
Value::I32(4),
Value::Array {
element: Signature::new("y")?.to_owned(),
values: vec![Value::U8(1), Value::U8(2), Value::U8(3), Value::U8(4)],
},
]),
);
round_trip("a{sv}", hints)?;
Ok(())
}
#[test]
fn recursive_menu_layout() -> Result<()> {
fn item(id: i32, children: Vec<Value>) -> Value {
let mut properties = Vec::new();
properties.push((
Value::String("label".into()),
Value::Variant(Box::new(Value::String(format!("Item {id}")))),
));
Value::Struct(vec![
Value::I32(id),
Value::Dict {
key: Signature::new("s").unwrap().to_owned(),
value: Signature::new("v").unwrap().to_owned(),
entries: properties,
},
Value::Array {
element: Signature::new("v").unwrap().to_owned(),
values: children,
},
])
}
let layout = item(
0,
vec![
Value::Variant(Box::new(item(1, Vec::new()))),
Value::Variant(Box::new(item(
2,
vec![Value::Variant(Box::new(item(3, Vec::new())))],
))),
],
);
let value = (1u32, layout);
round_trip("(u(ia{sv}av))", value.clone())?;
let signature = Signature::new("u(ia{sv}av)")?;
let mut buf = BodyBuf::new();
buf.extend_signature(signature)?;
value.0.encode(&mut buf.raw());
value.1.encode_value(&mut buf.raw());
let mut body = buf.as_body();
assert_eq!(u32::decode(&mut body)?, 1);
let actual = Value::decode_as(&mut body, Signature::new("(ia{sv}av)")?)?;
assert_eq!(actual, value.1);
assert!(body.is_empty());
Ok(())
}
#[test]
fn get_layout_reply() -> Result<()> {
use tokio_dbus::ty;
use crate::Arguments;
fn item(id: i32, children: Vec<Value>) -> (i32, HashMap<String, Value>, Vec<Value>) {
let mut properties = HashMap::new();
properties.insert(String::from("label"), Value::String(format!("Item {id}")));
(id, properties, children)
}
fn nested(id: i32, children: Vec<Value>) -> Value {
let (id, properties, children) = item(id, children);
Value::Struct(vec![
Value::I32(id),
Value::Dict {
key: Signature::STRING.to_owned(),
value: Signature::VARIANT.to_owned(),
entries: properties
.into_iter()
.map(|(k, v)| (Value::String(k), Value::Variant(Box::new(v))))
.collect(),
},
Value::Array {
element: Signature::VARIANT.to_owned(),
values: children,
},
])
}
type Layout = (
i32,
ty::Array<ty::Dict<ty::Str, ty::Variant>>,
ty::Array<ty::Variant>,
);
let root = item(
0,
vec![
nested(1, Vec::new()),
nested(2, vec![nested(3, Vec::new())]),
],
);
let mut arguments = Arguments::new(Signature::new("u(ia{sv}av)")?)?;
arguments.store(7u32);
arguments.store(&root);
let mut body = arguments.body_for_test();
assert_eq!(body.load::<u32>()?, 7);
let (id, mut properties, mut children) = body.load_struct::<Layout>()?;
assert_eq!(id, 0);
assert_eq!(
properties.load_entry()?,
Some(("label", tokio_dbus::Variant::String("Item 0")))
);
assert_eq!(properties.load_entry()?, None);
while !children.is_empty() {
let signature = children.skip_variant()?.expect("Missing child").to_owned();
assert_eq!(signature, "(ia{sv}av)");
}
assert!(body.is_empty(), "Body was not fully consumed");
Ok(())
}
#[test]
fn bare_values_in_variant_positions() -> Result<()> {
let wrapped = Value::Array {
element: Signature::VARIANT.to_owned(),
values: vec![Value::Variant(Box::new(Value::U32(1)))],
};
let bare = Value::Array {
element: Signature::VARIANT.to_owned(),
values: vec![Value::U32(1)],
};
let mut left = BodyBuf::new();
left.extend_signature(Signature::new("av")?)?;
wrapped.encode_value(&mut left.raw());
let mut right = BodyBuf::new();
right.extend_signature(Signature::new("av")?)?;
bare.encode_value(&mut right.raw());
assert_eq!(left.get(), right.get());
let mut body = left.as_body();
assert_eq!(Value::decode_as(&mut body, Signature::new("av")?)?, wrapped);
Ok(())
}