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