extern crate dbus;
use std::collections::HashMap;
use dbus::arg::{cast, RefArg, Variant};
#[derive(Debug, PartialEq, EnumKind, is_enum_variant, FromVariants)]
#[enum_kind(ValueKind)]
pub enum Value {
String(String),
I16(i16),
I32(i32),
I64(i64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
F64(f64),
Bool(bool),
Array(Vec<Value>),
Map(HashMap<String, Value>),
#[from_variants(skip)]
Unsupported,
}
macro_rules! cast_variants {
( $data:expr, $fallback:expr, $( $variant:pat => $into:tt ),+ ) => {
let data = $data;
match data.arg_type() {
$(
$variant => { cast_variant!(data, $into) },
)+
_ => $fallback,
}
}
}
macro_rules! cast_variant {
( $data:expr, $into:ty ) => {
cast::<$into>($data).cloned().map(Value::from)
};
( $data:expr, $handler:expr ) => { $handler };
}
impl Value {
pub(crate) fn from_variant(variant: &Variant<Box<RefArg>>) -> Option<Value> {
use dbus::arg::ArgType;
let data = &variant.0;
cast_variants! { data, Some(Value::Unsupported),
ArgType::Boolean => bool,
ArgType::Byte => u8,
ArgType::Double => f64,
ArgType::Int16 => i16,
ArgType::Int32 => i32,
ArgType::Int64 => { data.as_i64().map(Value::from) },
ArgType::String => { data.as_str().map(Value::from) },
ArgType::UInt16 => u16,
ArgType::UInt32 => u32,
ArgType::UInt64 => u64
}
}
pub fn kind(&self) -> ValueKind {
ValueKind::from(self)
}
}
include!("value_conversions.rs");
impl<'a> From<&'a str> for Value {
fn from(string: &'a str) -> Value {
Value::String(String::from(string))
}
}
impl dbus::arg::Arg for Value {
const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Variant;
fn signature() -> dbus::Signature<'static> {
dbus::Signature::from_slice(b"v\0").unwrap()
}
}
impl<'a> dbus::arg::Get<'a> for Value {
fn get(i: &mut dbus::arg::Iter) -> Option<Self> {
use dbus::arg::ArgType;
let arg_type = i.arg_type();
if let ArgType::Invalid = arg_type {
return None;
}
let signature = i.signature();
match arg_type {
ArgType::Array if *signature == *"a{sv}" => {
i.get::<HashMap<String, Value>>().map(Value::from)
}
ArgType::Array => i.get::<Vec<Value>>().map(Value::from),
ArgType::Boolean => i.get::<bool>().map(Value::from),
ArgType::Byte => i.get::<u8>().map(Value::from),
ArgType::Double => i.get::<f64>().map(Value::from),
ArgType::Int16 => i.get::<i16>().map(Value::from),
ArgType::Int32 => i.get::<i32>().map(Value::from),
ArgType::Int64 => i.get::<i64>().map(Value::from),
ArgType::String => i.get::<String>().map(Value::from),
ArgType::UInt16 => i.get::<u16>().map(Value::from),
ArgType::UInt32 => i.get::<u32>().map(Value::from),
ArgType::UInt64 => i.get::<u64>().map(Value::from),
ArgType::Variant => i.recurse(ArgType::Variant).and_then(|mut iter| iter.get()),
ArgType::Invalid => unreachable!("Early return at the top of the method"),
ArgType::DictEntry
| ArgType::UnixFd
| ArgType::Signature
| ArgType::ObjectPath
| ArgType::Struct => Some(Value::Unsupported),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dbus::{BusType, Connection, ConnectionItem, Message};
use dbus::arg::Append;
fn send_values_over_dbus<F>(appender: F) -> Message
where
F: FnOnce(Message) -> Message,
{
let connection = Connection::get_private(BusType::Session)
.expect("Could not open a D-Bus session connection");
connection
.register_object_path("/hello")
.expect("Could not register object path");
let send_message = Message::new_method_call(
&connection.unique_name(),
"/hello",
"com.example.hello",
"Hello",
).expect("Could not create message");
let send_message = appender(send_message);
connection
.send(send_message)
.expect("Could not send message to myself");
for item in connection.iter(200) {
if let ConnectionItem::MethodCall(received_message) = item {
return received_message;
}
}
panic!("Did not find a message on the bus");
}
fn send_value_over_dbus<T: Append>(value: T) -> Message {
send_values_over_dbus(|message| message.append1(value))
}
#[test]
fn it_supports_strings() {
let message = send_value_over_dbus("Hello world!");
let string: Value = message.get1().unwrap();
assert!(string.is_string());
assert_eq!(string.as_str(), Some("Hello world!"));
}
#[test]
fn it_supports_unsigned_integers() {
let message = send_values_over_dbus(|input| input.append3(1u8, 2u16, 3u32).append1(4u64));
let mut values = message.iter_init();
let eight: Value = values.read().unwrap();
let sixteen: Value = values.read().unwrap();
let thirtytwo: Value = values.read().unwrap();
let sixtyfour: Value = values.read().unwrap();
assert_eq!(Value::U8(1), eight);
assert_eq!(Value::U16(2), sixteen);
assert_eq!(Value::U32(3), thirtytwo);
assert_eq!(Value::U64(4), sixtyfour);
}
#[test]
fn it_supports_signed_integers() {
let message = send_values_over_dbus(|input| input.append3(1i16, 2i32, 3i64));
let mut values = message.iter_init();
let sixteen: Value = values.read().unwrap();
let thirtytwo: Value = values.read().unwrap();
let sixtyfour: Value = values.read().unwrap();
assert_eq!(Value::I16(1), sixteen);
assert_eq!(Value::I32(2), thirtytwo);
assert_eq!(Value::I64(3), sixtyfour);
}
#[test]
fn it_supports_floats() {
let message = send_value_over_dbus(42.0f64);
let float: Value = message.get1().unwrap();
assert!(float.is_f64());
assert_eq!(float.as_f64(), Some(42.0));
}
#[test]
fn it_supports_booleans() {
let message = send_value_over_dbus(true);
let boolean: Value = message.get1().unwrap();
assert!(boolean.is_bool());
assert_eq!(boolean.as_bool(), Some(true));
}
#[test]
fn it_supports_arrays_of_variants() {
let input: Vec<Variant<Box<RefArg>>> = vec![
Variant(Box::new(String::from("World"))),
Variant(Box::new(42u8)),
];
let expected = vec![Value::String("World".into()), Value::U8(42)];
let message = send_value_over_dbus(input);
let array: Value = message.get1().unwrap();
assert!(array.is_array());
assert_eq!(array.into_array(), Some(expected));
}
#[test]
fn it_supports_arrays_of_strings() {
let input: Vec<String> = vec!["Hello".into(), "World".into()];
let expected: Vec<Value> = vec!["Hello".into(), "World".into()];
let message = send_value_over_dbus(input);
let array: Value = message.get1().unwrap();
assert!(array.is_array());
assert_eq!(array.into_array(), Some(expected));
}
#[test]
fn it_supports_maps_of_variants() {
let mut input: HashMap<String, Variant<Box<RefArg>>> = HashMap::new();
input.insert(
String::from("receiver"),
Variant(Box::new(String::from("World"))),
);
input.insert(String::from("times"), Variant(Box::new(42u8)));
let mut expected = HashMap::new();
expected.insert(
String::from("receiver"),
Value::String(String::from("World")),
);
expected.insert(String::from("times"), Value::U8(42));
let message = send_value_over_dbus(input);
let hash: Value = message.get1().unwrap();
assert!(hash.is_map());
assert_eq!(hash.into_map(), Some(expected));
}
}