Skip to content

Commit 0c6e231

Browse files
committed
automatically use introspection to determine the right values to pass
1 parent 640983b commit 0c6e231

7 files changed

Lines changed: 435 additions & 29 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ edition = "2021"
99
dbus = "0.9.7"
1010
nu-plugin = "0.89.0"
1111
nu-protocol = { version = "0.89.0", features = ["plugin"] }
12+
serde = { version = "1.0.196", features = ["derive"] }
13+
serde-xml-rs = "0.6.0"

src/client.rs

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@ use dbus::{channel::{Channel, BusType}, Message};
22
use nu_plugin::LabeledError;
33
use nu_protocol::{Spanned, Value};
44

5-
use crate::{config::{DbusClientConfig, DbusBusChoice}, dbus_type::DbusType, convert::to_message_item};
5+
use crate::{config::{DbusClientConfig, DbusBusChoice}, dbus_type::DbusType, convert::to_message_item, introspection::Node};
66

77
/// Executes D-Bus actions on a connection, handling nushell types
88
pub struct DbusClient {
99
config: DbusClientConfig,
1010
conn: Channel,
1111
}
1212

13+
// Convenience macros for error handling
14+
macro_rules! validate_with {
15+
($type:ty, $spanned:expr) => (<$type>::new(&$spanned.item).map_err(|msg| {
16+
LabeledError {
17+
label: msg,
18+
msg: "this argument is incorrect".into(),
19+
span: Some($spanned.span),
20+
}
21+
}))
22+
}
23+
1324
impl DbusClient {
1425
pub fn new(config: DbusClientConfig) -> Result<DbusClient, LabeledError> {
1526
// Try to connect to the correct D-Bus destination, as specified in the config
@@ -35,6 +46,71 @@ impl DbusClient {
3546
})
3647
}
3748

49+
fn error(&self, err: impl std::fmt::Display, msg: impl std::fmt::Display) -> LabeledError {
50+
LabeledError {
51+
label: err.to_string(),
52+
msg: msg.to_string(),
53+
span: Some(self.config.span)
54+
}
55+
}
56+
57+
/// Introspect a D-Bus object
58+
pub fn introspect(
59+
&self,
60+
dest: &Spanned<String>,
61+
object: &Spanned<String>,
62+
) -> Result<Node, LabeledError> {
63+
let context = "while introspecting a D-Bus method";
64+
let valid_dest = validate_with!(dbus::strings::BusName, dest)?;
65+
let valid_object = validate_with!(dbus::strings::Path, object)?;
66+
67+
// Create the introspection method call
68+
let message = Message::new_method_call(
69+
valid_dest,
70+
valid_object,
71+
"org.freedesktop.DBus.Introspectable",
72+
"Introspect"
73+
).map_err(|err| self.error(err, context))?;
74+
75+
// Send and get the response
76+
let resp = self.conn.send_with_reply_and_block(message, self.config.timeout.item)
77+
.map_err(|err| self.error(err, context))?;
78+
79+
// Parse it to a Node
80+
let xml: &str = resp.get1()
81+
.ok_or_else(|| self.error("Introspect method returned the wrong type", context))?;
82+
83+
Node::from_xml(xml).map_err(|err| self.error(err, context))
84+
}
85+
86+
/// Try to use introspection to get the signature of a method
87+
fn get_method_signature_by_introspection(
88+
&self,
89+
dest: &Spanned<String>,
90+
object: &Spanned<String>,
91+
interface: &Spanned<String>,
92+
method: &Spanned<String>,
93+
) -> Result<Vec<DbusType>, LabeledError> {
94+
let node = self.introspect(dest, object)?;
95+
96+
if let Some(sig) = node.get_method_args_signature(&interface.item, &method.item) {
97+
DbusType::parse_all(&sig).map_err(|err| LabeledError {
98+
label: format!("while getting interface {:?} method {:?} signature: {}",
99+
interface.item,
100+
method.item,
101+
err),
102+
msg: "try running with --no-introspect or --signature".into(),
103+
span: Some(self.config.span),
104+
})
105+
} else {
106+
Err(LabeledError {
107+
label: format!("Method {:?} not found on {:?}", method.item, interface.item),
108+
msg: "check that this method/interface is correct".into(),
109+
span: Some(method.span),
110+
})
111+
}
112+
}
113+
38114
/// Call a D-Bus method and wait for the response
39115
pub fn call(
40116
&self,
@@ -45,41 +121,42 @@ impl DbusClient {
45121
signature: Option<&Spanned<String>>,
46122
args: &[Value],
47123
) -> Result<Vec<Value>, LabeledError> {
48-
macro_rules! error {
49-
($label:expr) => (LabeledError {
50-
label: $label,
51-
msg: "while calling a D-Bus method".into(),
52-
span: Some(self.config.span)
53-
})
54-
}
124+
let context = "while calling a D-Bus method";
55125

56126
// Validate inputs before sending to the dbus lib so we don't panic
57-
macro_rules! validate_with {
58-
($type:ty, $spanned:expr) => (<$type>::new(&$spanned.item).map_err(|msg| {
59-
LabeledError {
60-
label: msg,
61-
msg: "this argument is incorrect".into(),
62-
span: Some($spanned.span),
63-
}
64-
}))
65-
}
66127
let valid_dest = validate_with!(dbus::strings::BusName, dest)?;
67128
let valid_object = validate_with!(dbus::strings::Path, object)?;
68129
let valid_interface = validate_with!(dbus::strings::Interface, interface)?;
69130
let valid_method = validate_with!(dbus::strings::Member, method)?;
70131

71132
// Parse the signature
72-
let valid_signature = signature.map(|s| DbusType::parse_all(&s.item).map_err(|err| {
133+
let mut valid_signature = signature.map(|s| DbusType::parse_all(&s.item).map_err(|err| {
73134
LabeledError {
74135
label: err,
75136
msg: "in signature specified here".into(),
76137
span: Some(s.span),
77138
}
78139
})).transpose()?;
79140

141+
// If not provided, try introspection (unless disabled)
142+
if valid_signature.is_none() && self.config.introspect {
143+
match self.get_method_signature_by_introspection(dest, object, interface, method) {
144+
Ok(sig) => {
145+
valid_signature = Some(sig);
146+
},
147+
Err(err) => {
148+
eprintln!("Warning: D-Bus introspection failed on {:?}. \
149+
Use `--no-introspect` or pass `--signature` to silence this warning. \
150+
Cause: {}",
151+
object.item,
152+
err.label);
153+
}
154+
}
155+
}
156+
80157
if let Some(sig) = &valid_signature {
81158
if sig.len() != args.len() {
82-
error!(format!("expected {} arguments, got {}", sig.len(), args.len()));
159+
self.error(format!("expected {} arguments, got {}", sig.len(), args.len()), context);
83160
}
84161
}
85162

@@ -89,7 +166,7 @@ impl DbusClient {
89166
valid_object,
90167
valid_interface,
91168
valid_method,
92-
).map_err(|err| error!(err))?;
169+
).map_err(|err| self.error(err, context))?;
93170

94171
// Convert the args to message items
95172
let sigs_iter = valid_signature.iter().flatten().map(Some).chain(std::iter::repeat(None));
@@ -99,8 +176,8 @@ impl DbusClient {
99176

100177
// Send it on the channel and get the response
101178
let resp = self.conn.send_with_reply_and_block(message, self.config.timeout.item)
102-
.map_err(|err| error!(err.to_string()))?;
179+
.map_err(|err| self.error(err, context))?;
103180

104-
crate::convert::from_message(&resp).map_err(|err| error!(err))
181+
crate::convert::from_message(&resp).map_err(|err| self.error(err, context))
105182
}
106183
}

src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ use nu_protocol::{Spanned, Span};
77
#[derive(Debug, Clone)]
88
pub struct DbusClientConfig {
99
pub span: Span,
10+
/// Which bus should we connect to?
1011
pub bus_choice: Spanned<DbusBusChoice>,
12+
/// How long to wait for a method call to return
1113
pub timeout: Spanned<Duration>,
14+
/// Enable introspection if signature unknown (default true)
15+
pub introspect: bool,
1216
}
1317

1418
/// Where to connect to the D-Bus server
@@ -35,6 +39,7 @@ impl TryFrom<&EvaluatedCall> for DbusClientConfig {
3539
span: call.head,
3640
bus_choice: Spanned { item: DbusBusChoice::default(), span: call.head },
3741
timeout: Spanned { item: Duration::from_secs(2), span: call.head },
42+
introspect: true,
3843
};
3944

4045
// Handle recognized config args
@@ -75,6 +80,11 @@ impl TryFrom<&EvaluatedCall> for DbusClientConfig {
7580
config.timeout = Spanned { item, span: value.span() };
7681
}
7782
},
83+
"no-introspect" => {
84+
config.introspect = !value.as_ref()
85+
.and_then(|v| v.as_bool().ok())
86+
.unwrap_or(false);
87+
},
7888
_ => ()
7989
}
8090
}

src/convert.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ pub fn to_message_item(value: &Value, expected_type: Option<&DbusType>)
142142
Ok(MessageItem::Double(try_convert!(f64::from_str(&val[..])))),
143143

144144
// List/array
145-
(Value::List { vals, .. }, Some(DbusType::Array(content_type))) => {
146-
let content_sig = Signature::from(content_type.stringify());
145+
(Value::List { vals, .. }, Some(r#type @ DbusType::Array(content_type))) => {
146+
let sig = Signature::from(r#type.stringify());
147147
let items = vals.iter()
148148
.map(|content| to_message_item(content, Some(content_type)))
149149
.collect::<Result<Vec<MessageItem>, _>>()?;
150-
Ok(MessageItem::Array(MessageItemArray::new(items, content_sig).unwrap()))
150+
Ok(MessageItem::Array(MessageItemArray::new(items, sig).unwrap()))
151151
},
152152

153153
// Struct

0 commit comments

Comments
 (0)