Nushell plugin for interacting with D-Bus
1use dbus::{Message, arg::{ArgType, RefArg, messageitem::{MessageItemArray, MessageItem, MessageItemDict}}, Signature};
2use nu_plugin::LabeledError;
3use nu_protocol::{Value, Span, Record};
4use std::str::FromStr;
5
6use crate::dbus_type::DbusType;
7
8/// Get the arguments of a message as nushell Values
9pub fn from_message(message: &Message, span: Span) -> Result<Vec<Value>, String> {
10 let mut out = vec![];
11 for refarg in message.iter_init() {
12 out.push(from_refarg(&refarg, span)?);
13 }
14 Ok(out)
15}
16
17pub fn from_refarg(refarg: &dyn RefArg, span: Span) -> Result<Value, String> {
18 Ok(match refarg.arg_type() {
19 ArgType::Array => {
20 if refarg.signature().starts_with("a{") {
21 // This is a dictionary
22 let mut record = Record::new();
23 let mut iter = refarg.as_iter().unwrap();
24 while let Some(key) = iter.next() {
25 if let Some(val) = iter.next() {
26 if let Some(key_str) = key.as_str() {
27 record.insert(key_str, from_refarg(val, span)?);
28 }
29 }
30 }
31 Value::record(record, span)
32 } else if &*refarg.signature() == "ay" {
33 // Byte array - better to return as binary
34 let bytes = dbus::arg::cast::<Vec<u8>>(&refarg.box_clone()).unwrap().to_owned();
35 Value::binary(bytes, span)
36 } else {
37 // It's an array
38 Value::list(
39 refarg.as_iter().unwrap().map(|v| from_refarg(v, span)).flatten().collect(),
40 span)
41 }
42 },
43 ArgType::Variant => {
44 let inner = refarg.as_iter().unwrap().nth(0).unwrap();
45 return from_refarg(inner, span);
46 },
47 ArgType::Boolean =>
48 Value::bool(refarg.as_i64().unwrap() != 0, span),
49
50 // Strings
51 ArgType::String | ArgType::ObjectPath | ArgType::Signature =>
52 Value::string(refarg.as_str().unwrap(), span),
53 // Ints
54 ArgType::Byte | ArgType::Int16 | ArgType::UInt16 | ArgType::Int32 |
55 ArgType::UInt32 | ArgType::Int64 | ArgType::UnixFd =>
56 Value::int(refarg.as_i64().unwrap(), span),
57
58 // Nushell doesn't support u64, so present it as a string
59 ArgType::UInt64 => Value::string(refarg.as_u64().unwrap().to_string(), span),
60
61 // Floats
62 ArgType::Double =>
63 Value::float(refarg.as_f64().unwrap(), span),
64
65 ArgType::Struct =>
66 Value::list(
67 refarg.as_iter().unwrap().map(|v| from_refarg(v, span)).flatten().collect(),
68 span),
69
70 ArgType::DictEntry =>
71 return Err("Encountered dictionary entry outside of dictionary".into()),
72 ArgType::Invalid =>
73 return Err("Encountered invalid D-Bus value".into()),
74 })
75}
76
77pub fn to_message_item(value: &Value, expected_type: Option<&DbusType>)
78 -> Result<MessageItem, LabeledError>
79{
80 // Report errors from conversion. Error must support Display
81 macro_rules! try_convert {
82 ($result_expr:expr) => ($result_expr.map_err(|err| LabeledError {
83 label: format!("Failed to convert value to the D-Bus `{:?}` type",
84 expected_type.unwrap()),
85 msg: err.to_string(),
86 span: Some(value.span()),
87 })?)
88 }
89
90 // Try to match values to expected types
91 match (value, expected_type) {
92 // Boolean
93 (Value::Bool { val, .. }, Some(DbusType::Boolean)) =>
94 Ok(MessageItem::Bool(*val)),
95
96 // Strings and specialized strings
97 (Value::String { val, .. }, Some(DbusType::String)) =>
98 Ok(MessageItem::Str(val.to_owned())),
99 (Value::String { val, .. }, Some(DbusType::ObjectPath)) =>
100 Ok(MessageItem::ObjectPath(try_convert!(dbus::strings::Path::new(val)))),
101 (Value::String { val, .. }, Some(DbusType::Signature)) =>
102 Ok(MessageItem::Signature(try_convert!(dbus::strings::Signature::new(val)))),
103
104 // Signed ints
105 (Value::Int { val, .. }, Some(DbusType::Int64)) =>
106 Ok(MessageItem::Int64(*val)),
107 (Value::Int { val, .. }, Some(DbusType::Int32)) =>
108 Ok(MessageItem::Int32(try_convert!(i32::try_from(*val)))),
109 (Value::Int { val, .. }, Some(DbusType::Int16)) =>
110 Ok(MessageItem::Int16(try_convert!(i16::try_from(*val)))),
111
112 // Unsigned ints
113 (Value::Int { val, .. }, Some(DbusType::UInt64)) =>
114 Ok(MessageItem::UInt64(try_convert!(u64::try_from(*val)))),
115 (Value::Int { val, .. }, Some(DbusType::UInt32)) =>
116 Ok(MessageItem::UInt32(try_convert!(u32::try_from(*val)))),
117 (Value::Int { val, .. }, Some(DbusType::UInt16)) =>
118 Ok(MessageItem::UInt16(try_convert!(u16::try_from(*val)))),
119 (Value::Int { val, .. }, Some(DbusType::Byte)) =>
120 Ok(MessageItem::Byte(try_convert!(u8::try_from(*val)))),
121
122 // Ints from string
123 (Value::String { val, .. }, Some(DbusType::Int64)) =>
124 Ok(MessageItem::Int64(try_convert!(i64::from_str(&val[..])))),
125 (Value::String { val, .. }, Some(DbusType::Int32)) =>
126 Ok(MessageItem::Int32(try_convert!(i32::from_str(&val[..])))),
127 (Value::String { val, .. }, Some(DbusType::Int16)) =>
128 Ok(MessageItem::Int16(try_convert!(i16::from_str(&val[..])))),
129 (Value::String { val, .. }, Some(DbusType::UInt64)) =>
130 Ok(MessageItem::UInt64(try_convert!(u64::from_str(&val[..])))),
131 (Value::String { val, .. }, Some(DbusType::UInt32)) =>
132 Ok(MessageItem::UInt32(try_convert!(u32::from_str(&val[..])))),
133 (Value::String { val, .. }, Some(DbusType::UInt16)) =>
134 Ok(MessageItem::UInt16(try_convert!(u16::from_str(&val[..])))),
135 (Value::String { val, .. }, Some(DbusType::Byte)) =>
136 Ok(MessageItem::Byte(try_convert!(u8::from_str(&val[..])))),
137
138 // Float
139 (Value::Float { val, .. }, Some(DbusType::Double)) =>
140 Ok(MessageItem::Double(*val)),
141 (Value::String { val, .. }, Some(DbusType::Double)) =>
142 Ok(MessageItem::Double(try_convert!(f64::from_str(&val[..])))),
143
144 // Binary
145 (Value::Binary { val, .. }, Some(r#type @ DbusType::Array(content_type)))
146 if matches!(**content_type, DbusType::Byte) =>
147 {
148 // FIXME: this is likely pretty inefficient for a bunch of bytes
149 let sig = Signature::from(r#type.stringify());
150 let items = val.iter().cloned().map(MessageItem::Byte).collect::<Vec<_>>();
151 Ok(MessageItem::Array(MessageItemArray::new(items, sig).unwrap()))
152 },
153
154 // List/array
155 (Value::List { vals, .. }, Some(r#type @ DbusType::Array(content_type))) => {
156 let sig = Signature::from(r#type.stringify());
157 let items = vals.iter()
158 .map(|content| to_message_item(content, Some(content_type)))
159 .collect::<Result<Vec<MessageItem>, _>>()?;
160 Ok(MessageItem::Array(MessageItemArray::new(items, sig).unwrap()))
161 },
162
163 // Struct
164 (Value::List { vals, .. }, Some(DbusType::Struct(types))) => {
165 if vals.len() != types.len() {
166 return Err(LabeledError {
167 label: format!("expected struct with {} element(s) ({:?})", types.len(), types),
168 msg: format!("this list has {} element(s) instead", vals.len()),
169 span: Some(value.span())
170 });
171 }
172 let items = vals.iter().zip(types)
173 .map(|(content, r#type)| to_message_item(content, Some(r#type)))
174 .collect::<Result<Vec<MessageItem>, _>>()?;
175 Ok(MessageItem::Struct(items))
176 },
177
178 // Record/dict
179 (Value::Record { val, .. }, Some(DbusType::Array(content_type)))
180 if matches!(**content_type, DbusType::DictEntry(_, _)) =>
181 {
182 if let DbusType::DictEntry(ref key_type, ref val_type) = **content_type {
183 let key_sig = Signature::from(key_type.stringify());
184 let val_sig = Signature::from(val_type.stringify());
185 let pairs = val.iter()
186 .map(|(key, val)| {
187 let key_as_value = Value::string(key, value.span());
188 let key_message_item = to_message_item(&key_as_value, Some(key_type))?;
189 let val_message_item = to_message_item(val, Some(val_type))?;
190 Ok((key_message_item, val_message_item))
191 })
192 .collect::<Result<Vec<_>, LabeledError>>()?;
193 Ok(MessageItem::Dict(MessageItemDict::new(pairs, key_sig, val_sig).unwrap()))
194 } else {
195 unreachable!()
196 }
197 },
198
199 // Variant - use automatic type
200 (other_value, Some(DbusType::Variant)) =>
201 Ok(MessageItem::Variant(Box::new(to_message_item(other_value, None)?))),
202
203 // Value not compatible with expected type
204 (other_value, Some(expectation)) =>
205 Err(LabeledError {
206 label: format!("`{}` can not be converted to the D-Bus `{:?}` type",
207 other_value.get_type(), expectation),
208 msg: format!("expected a `{:?}` here", expectation),
209 span: Some(other_value.span()),
210 }),
211
212 // Automatic types (with no type expectation)
213 (Value::String { .. }, None) =>
214 to_message_item(value, Some(&DbusType::String)),
215 (Value::Int { .. }, None) =>
216 to_message_item(value, Some(&DbusType::Int64)),
217 (Value::Float { .. }, None) =>
218 to_message_item(value, Some(&DbusType::Double)),
219 (Value::Bool { .. }, None) =>
220 to_message_item(value, Some(&DbusType::Boolean)),
221 (Value::List { .. }, None) =>
222 to_message_item(value, Some(&DbusType::Array(DbusType::Variant.into()))),
223 (Value::Record { .. }, None) =>
224 to_message_item(value, Some(&DbusType::Array(
225 DbusType::DictEntry(
226 DbusType::String.into(),
227 DbusType::Variant.into()
228 ).into()))),
229
230 // No expected type, but can't handle this type
231 _ =>
232 Err(LabeledError {
233 label: format!("can not use values of type `{}` in D-Bus calls", value.get_type()),
234 msg: "use a supported type here instead".into(),
235 span: Some(value.span()),
236 })
237 }
238}