zbus_lockstep/lib.rs
1//! # zbus-lockstep
2//!
3//! Is a collection of helpers for retrieving `DBus` type signatures from XML descriptions.
4//! Useful for comparing these with your types' signatures to ensure that they are compatible.
5//!
6//! It offers functions that retrieve the signature of a method's argument type, of a method's
7//! return type, pf a signal's body type or of a property's type from `DBus` XML.
8//!
9//! These functions require that you provide the file path to the XML file, the interface name,
10//! and the interface member wherein the signature resides.
11//!
12//! Corresponding to each of these functions, macros are provided which do not
13//! require you to exactly point out where the signature is found. These will just search
14//! by interface member name.
15//!
16//! The macros assume that the file path to the XML files is either:
17//!
18//! - `xml` or `XML`, the default path for `DBus` XML files - or is set by the
19//! - `LOCKSTEP_XML_PATH`, the env variable that overrides the default.
20#![doc(html_root_url = "https://docs.rs/zbus-lockstep/0.4.4")]
21#![allow(clippy::missing_errors_doc)]
22
23mod error;
24mod macros;
25
26use std::io::Read;
27
28pub use error::LockstepError;
29pub use macros::resolve_xml_path;
30pub use zbus_xml::{
31 self,
32 ArgDirection::{In, Out},
33 Node,
34};
35use zvariant::Signature;
36use LockstepError::{ArgumentNotFound, InterfaceNotFound, MemberNotFound, PropertyNotFound};
37
38type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
39
40#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
41pub enum MsgType {
42 Method,
43 Signal,
44 Property,
45}
46
47/// Retrieve a signal's body type signature from `DBus` XML.
48///
49/// If you provide an argument name, then the signature of that argument is returned.
50/// If you do not provide an argument name, then the signature of all arguments is returned.
51///
52/// # Examples
53///
54/// ```rust
55/// # use std::fs::File;
56/// # use std::io::{Seek, SeekFrom, Write};
57/// # use tempfile::tempfile;
58/// use zvariant::{Signature, Type, OwnedObjectPath};
59/// use zbus_lockstep::get_signal_body_type;
60///
61/// let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
62/// <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
63/// <interface name="org.freedesktop.bolt1.Manager">
64/// <signal name="DeviceAdded">
65/// <arg name="device" type="o"/>
66/// </signal>
67/// </interface>
68/// </node>
69/// "#;
70///
71/// let mut xml_file: File = tempfile().unwrap();
72/// xml_file.write_all(xml.as_bytes()).unwrap();
73/// xml_file.seek(SeekFrom::Start(0)).unwrap();
74///
75/// #[derive(Debug, PartialEq, Type)]
76/// struct DeviceEvent {
77/// device: OwnedObjectPath,
78/// }
79///
80/// let interface_name = "org.freedesktop.bolt1.Manager";
81/// let member_name = "DeviceAdded";
82///
83/// let signature = get_signal_body_type(xml_file, interface_name, member_name, None).unwrap();
84///
85/// // Single `DBus` type codes, here 'o', are returned as a single character.
86/// // Also, signal body types (often) omit the struct or tuple type parentheses.
87///
88/// assert_eq!(&signature, &DeviceEvent::signature());
89/// ```
90pub fn get_signal_body_type<'a>(
91 mut xml: impl Read,
92 interface_name: &str,
93 member_name: &str,
94 arg: Option<&str>,
95) -> Result<Signature<'a>> {
96 let node = Node::from_reader(&mut xml)?;
97
98 let interfaces = node.interfaces();
99 let interface = interfaces
100 .iter()
101 .find(|iface| iface.name() == interface_name)
102 .ok_or(InterfaceNotFound(interface_name.to_owned()))?;
103
104 let signals = interface.signals();
105 let signal = signals
106 .iter()
107 .find(|signal| signal.name() == member_name)
108 .ok_or(MemberNotFound(member_name.to_owned()))?;
109
110 let signature = {
111 if let Some(arg_name) = arg {
112 let args = signal.args();
113 let arg = args
114 .iter()
115 .find(|arg| arg.name() == Some(arg_name))
116 .ok_or(ArgumentNotFound(arg_name.to_owned()))?;
117 arg.ty().to_string()
118 } else {
119 signal
120 .args()
121 .iter()
122 .map(|arg| arg.ty().to_string())
123 .collect::<String>()
124 }
125 };
126 Ok(Signature::from_string_unchecked(signature))
127}
128
129/// Retrieve the signature of a property's type from XML.
130///
131/// # Examples
132///
133/// ```rust
134/// use std::fs::File;
135/// use std::io::{Seek, SeekFrom, Write};
136/// use tempfile::tempfile;
137/// use zvariant::Type;
138/// use zbus_lockstep::get_property_type;
139///
140/// #[derive(Debug, PartialEq, Type)]
141/// struct InUse(bool);
142///
143/// let xml = String::from(r#"
144/// <node>
145/// <interface name="org.freedesktop.GeoClue2.Manager">
146/// <property type="b" name="InUse" access="read"/>
147/// </interface>
148/// </node>
149/// "#);
150///
151/// let mut xml_file: File = tempfile().unwrap();
152/// xml_file.write_all(xml.as_bytes()).unwrap();
153/// xml_file.seek(SeekFrom::Start(0)).unwrap();
154///
155/// let interface_name = "org.freedesktop.GeoClue2.Manager";
156/// let property_name = "InUse";
157///
158/// let signature = get_property_type(xml_file, interface_name, property_name).unwrap();
159/// assert_eq!(signature, InUse::signature());
160/// ```
161pub fn get_property_type<'a>(
162 mut xml: impl Read,
163 interface_name: &str,
164 property_name: &str,
165) -> Result<Signature<'a>> {
166 let node = Node::from_reader(&mut xml)?;
167
168 let interfaces = node.interfaces();
169 let interface = interfaces
170 .iter()
171 .find(|iface| iface.name() == interface_name)
172 .ok_or(InterfaceNotFound(interface_name.to_string()))?;
173
174 let properties = interface.properties();
175 let property = properties
176 .iter()
177 .find(|property| property.name() == property_name)
178 .ok_or(PropertyNotFound(property_name.to_owned()))?;
179
180 let signature = property.ty().to_string();
181 Ok(Signature::from_string_unchecked(signature))
182}
183
184/// Retrieve the signature of a method's return type from XML.
185///
186/// If you provide an argument name, then the signature of that argument is returned.
187/// If you do not provide an argument name, then the signature of all arguments is returned.
188///
189///
190/// # Examples
191///
192/// ```rust
193/// use std::fs::File;
194/// use std::io::{Seek, SeekFrom, Write};
195/// use tempfile::tempfile;
196/// use zvariant::Type;
197/// use zbus_lockstep::get_method_return_type;
198///
199/// #[derive(Debug, PartialEq, Type)]
200/// #[repr(u32)]
201/// enum Role {
202/// Invalid,
203/// TitleBar,
204/// MenuBar,
205/// ScrollBar,
206/// }
207///
208/// let xml = String::from(r#"
209/// <node>
210/// <interface name="org.a11y.atspi.Accessible">
211/// <method name="GetRole">
212/// <arg name="role" type="u" direction="out"/>
213/// </method>
214/// </interface>
215/// </node>
216/// "#);
217///
218/// let mut xml_file: File = tempfile().unwrap();
219/// xml_file.write_all(xml.as_bytes()).unwrap();
220/// xml_file.seek(SeekFrom::Start(0)).unwrap();
221///
222/// let interface_name = "org.a11y.atspi.Accessible";
223/// let member_name = "GetRole";
224///
225/// let signature = get_method_return_type(xml_file, interface_name, member_name, None).unwrap();
226/// assert_eq!(signature, Role::signature());
227/// ```
228pub fn get_method_return_type<'a>(
229 mut xml: impl Read,
230 interface_name: &str,
231 member_name: &str,
232 arg_name: Option<&str>,
233) -> Result<Signature<'a>> {
234 let node = Node::from_reader(&mut xml)?;
235
236 let interfaces = node.interfaces();
237 let interface = interfaces
238 .iter()
239 .find(|iface| iface.name() == interface_name)
240 .ok_or(InterfaceNotFound(interface_name.to_string()))?;
241
242 let methods = interface.methods();
243 let method = methods
244 .iter()
245 .find(|method| method.name() == member_name)
246 .ok_or(MemberNotFound(member_name.to_string()))?;
247
248 let args = method.args();
249
250 let signature = {
251 if arg_name.is_some() {
252 args.iter()
253 .find(|arg| arg.name() == arg_name)
254 .ok_or(ArgumentNotFound(
255 arg_name.expect("arg_name guarded by 'is_some'").to_string(),
256 ))?
257 .ty()
258 .to_string()
259 } else {
260 args.iter()
261 .filter(|arg| arg.direction() == Some(Out))
262 .map(|arg| arg.ty().to_string())
263 .collect::<String>()
264 }
265 };
266
267 Ok(Signature::from_string_unchecked(signature))
268}
269
270/// Retrieve the signature of a method's argument type from XML.
271///
272/// Useful when one or more arguments, used to call a method, outline a useful type.
273///
274/// If you provide an argument name, then the signature of that argument is returned.
275/// If you do not provide an argument name, then the signature of all arguments to the call is
276/// returned.
277///
278/// # Examples
279///
280/// ```rust
281/// use std::fs::File;
282/// use std::collections::HashMap;
283/// use std::io::{Seek, SeekFrom, Write};
284/// use tempfile::tempfile;
285/// use zvariant::{Type, Value};
286/// use zbus_lockstep::get_method_args_type;
287///
288/// let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
289/// <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
290/// <interface name="org.freedesktop.Notifications">
291/// <method name="Notify">
292/// <arg type="s" name="app_name" direction="in"/>
293/// <arg type="u" name="replaces_id" direction="in"/>
294/// <arg type="s" name="app_icon" direction="in"/>
295/// <arg type="s" name="summary" direction="in"/>
296/// <arg type="s" name="body" direction="in"/>
297/// <arg type="as" name="actions" direction="in"/>
298/// <arg type="a{sv}" name="hints" direction="in"/>
299/// <arg type="i" name="expire_timeout" direction="in"/>
300/// <arg type="u" name="id" direction="out"/>
301/// </method>
302/// </interface>
303/// </node>
304/// "#;
305///
306/// #[derive(Debug, PartialEq, Type)]
307/// struct Notification<'a> {
308/// app_name: String,
309/// replaces_id: u32,
310/// app_icon: String,
311/// summary: String,
312/// body: String,
313/// actions: Vec<String>,
314/// hints: HashMap<String, Value<'a>>,
315/// expire_timeout: i32,
316/// }
317///
318/// let mut xml_file = tempfile().unwrap();
319/// xml_file.write_all(xml.as_bytes()).unwrap();
320/// xml_file.seek(SeekFrom::Start(0)).unwrap();
321///
322/// let interface_name = "org.freedesktop.Notifications";
323/// let member_name = "Notify";
324///
325/// let signature = get_method_args_type(xml_file, interface_name, member_name, None).unwrap();
326/// assert_eq!(&signature, &Notification::signature());
327/// ```
328pub fn get_method_args_type<'a>(
329 mut xml: impl Read,
330 interface_name: &str,
331 member_name: &str,
332 arg_name: Option<&str>,
333) -> Result<Signature<'a>> {
334 let node = Node::from_reader(&mut xml)?;
335
336 let interfaces = node.interfaces();
337 let interface = interfaces
338 .iter()
339 .find(|iface| iface.name() == interface_name)
340 .ok_or(InterfaceNotFound(interface_name.to_owned()))?;
341
342 let methods = interface.methods();
343 let method = methods
344 .iter()
345 .find(|method| method.name() == member_name)
346 .ok_or(member_name.to_owned())?;
347
348 let args = method.args();
349
350 let signature = if arg_name.is_some() {
351 args.iter()
352 .find(|arg| arg.name() == arg_name)
353 .ok_or(ArgumentNotFound(
354 arg_name.expect("arg_name guarded by is_some").to_string(),
355 ))?
356 .ty()
357 .to_string()
358 } else {
359 args.iter()
360 .filter(|arg| arg.direction() == Some(In))
361 .map(|arg| arg.ty().to_string())
362 .collect::<String>()
363 };
364
365 Ok(Signature::from_string_unchecked(signature))
366}
367
368#[cfg(test)]
369mod test {
370 use std::io::{Seek, SeekFrom, Write};
371
372 use tempfile::tempfile;
373 use zvariant::{OwnedObjectPath, Type};
374
375 use crate::get_signal_body_type;
376
377 #[test]
378 fn test_get_signature_of_cache_add_accessible() {
379 #[derive(Debug, PartialEq, Type)]
380 struct Accessible {
381 name: String,
382 path: OwnedObjectPath,
383 }
384
385 #[derive(Debug, PartialEq, Type)]
386 struct CacheItem {
387 obj: Accessible,
388 application: Accessible,
389 parent: Accessible,
390 index_in_parent: i32,
391 child_count: i32,
392 interfaces: Vec<String>,
393 name: String,
394 role: u32,
395 description: String,
396 state_set: Vec<u32>,
397 }
398
399 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
400 <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
401 <interface name="org.a11y.atspi.Cache">
402 <signal name="AddAccessible">
403 <arg name="nodeAdded" type="((so)(so)(so)iiassusau)"/>
404 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/>
405 </signal>
406 </interface>
407 </node>
408 "#;
409
410 let mut xml_file = tempfile().unwrap();
411 xml_file.write_all(xml.as_bytes()).unwrap();
412 xml_file.seek(SeekFrom::Start(0)).unwrap();
413
414 let interface_name = "org.a11y.atspi.Cache";
415 let member_name = "AddAccessible";
416
417 let signature = get_signal_body_type(xml_file, interface_name, member_name, None).unwrap();
418 assert_eq!(signature, CacheItem::signature());
419 }
420}