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.5.1")]
21#![allow(clippy::missing_errors_doc)]
22
23mod error;
24mod macros;
25
26use std::{io::Read, str::FromStr};
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/// #[zvariant(signature = "o")]
77/// struct DeviceEvent {
78/// device: OwnedObjectPath,
79/// }
80///
81/// let interface_name = "org.freedesktop.bolt1.Manager";
82/// let member_name = "DeviceAdded";
83///
84/// let signature = get_signal_body_type(xml_file, interface_name, member_name, None).unwrap();
85///
86/// assert_eq!(&signature, DeviceEvent::SIGNATURE);
87/// ```
88pub fn get_signal_body_type(
89 mut xml: impl Read,
90 interface_name: &str,
91 member_name: &str,
92 arg: Option<&str>,
93) -> Result<Signature> {
94 let node = Node::from_reader(&mut xml)?;
95
96 let interfaces = node.interfaces();
97 let interface = interfaces
98 .iter()
99 .find(|iface| iface.name() == interface_name)
100 .ok_or(InterfaceNotFound(interface_name.to_owned()))?;
101
102 let signals = interface.signals();
103 let signal = signals
104 .iter()
105 .find(|signal| signal.name() == member_name)
106 .ok_or(MemberNotFound(member_name.to_owned()))?;
107
108 let signature = {
109 if let Some(arg_name) = arg {
110 let args = signal.args();
111 let arg = args
112 .iter()
113 .find(|arg| arg.name() == Some(arg_name))
114 .ok_or(ArgumentNotFound(arg_name.to_owned()))?;
115 arg.ty().to_string()
116 } else {
117 signal
118 .args()
119 .iter()
120 .map(|arg| arg.ty().to_string())
121 .collect::<String>()
122 }
123 };
124 Ok(Signature::from_str(&signature).map_err(|_| "Invalid signature")?)
125}
126
127/// Retrieve the signature of a property's type from XML.
128///
129/// # Examples
130///
131/// ```rust
132/// use std::fs::File;
133/// use std::io::{Seek, SeekFrom, Write};
134/// use tempfile::tempfile;
135/// use zvariant::Type;
136/// use zbus_lockstep::get_property_type;
137///
138/// #[derive(Debug, PartialEq, Type)]
139/// struct InUse(bool);
140///
141/// let xml = String::from(r#"
142/// <node>
143/// <interface name="org.freedesktop.GeoClue2.Manager">
144/// <property type="b" name="InUse" access="read"/>
145/// </interface>
146/// </node>
147/// "#);
148///
149/// let mut xml_file: File = tempfile().unwrap();
150/// xml_file.write_all(xml.as_bytes()).unwrap();
151/// xml_file.seek(SeekFrom::Start(0)).unwrap();
152///
153/// let interface_name = "org.freedesktop.GeoClue2.Manager";
154/// let property_name = "InUse";
155///
156/// let signature = get_property_type(xml_file, interface_name, property_name).unwrap();
157/// assert_eq!(signature, *InUse::SIGNATURE);
158/// ```
159pub fn get_property_type(
160 mut xml: impl Read,
161 interface_name: &str,
162 property_name: &str,
163) -> Result<Signature> {
164 let node = Node::from_reader(&mut xml)?;
165
166 let interfaces = node.interfaces();
167 let interface = interfaces
168 .iter()
169 .find(|iface| iface.name() == interface_name)
170 .ok_or(InterfaceNotFound(interface_name.to_string()))?;
171
172 let properties = interface.properties();
173 let property = properties
174 .iter()
175 .find(|property| property.name() == property_name)
176 .ok_or(PropertyNotFound(property_name.to_owned()))?;
177
178 let signature = property.ty().to_string();
179 Ok(Signature::from_str(&signature).map_err(|_| "Invalid signature")?)
180}
181
182/// Retrieve the signature of a method's return type from XML.
183///
184/// If you provide an argument name, then the signature of that argument is returned.
185/// If you do not provide an argument name, then the signature of all arguments is returned.
186///
187///
188/// # Examples
189///
190/// ```rust
191/// use std::fs::File;
192/// use std::io::{Seek, SeekFrom, Write};
193/// use tempfile::tempfile;
194/// use zvariant::Type;
195/// use zbus_lockstep::get_method_return_type;
196///
197/// #[derive(Debug, PartialEq, Type)]
198/// #[repr(u32)]
199/// enum Role {
200/// Invalid,
201/// TitleBar,
202/// MenuBar,
203/// ScrollBar,
204/// }
205///
206/// let xml = String::from(r#"
207/// <node>
208/// <interface name="org.a11y.atspi.Accessible">
209/// <method name="GetRole">
210/// <arg name="role" type="u" direction="out"/>
211/// </method>
212/// </interface>
213/// </node>
214/// "#);
215///
216/// let mut xml_file: File = tempfile().unwrap();
217/// xml_file.write_all(xml.as_bytes()).unwrap();
218/// xml_file.seek(SeekFrom::Start(0)).unwrap();
219///
220/// let interface_name = "org.a11y.atspi.Accessible";
221/// let member_name = "GetRole";
222///
223/// let signature = get_method_return_type(xml_file, interface_name, member_name, None).unwrap();
224/// assert_eq!(signature, *Role::SIGNATURE);
225/// ```
226pub fn get_method_return_type(
227 mut xml: impl Read,
228 interface_name: &str,
229 member_name: &str,
230 arg_name: Option<&str>,
231) -> Result<Signature> {
232 let node = Node::from_reader(&mut xml)?;
233
234 let interfaces = node.interfaces();
235 let interface = interfaces
236 .iter()
237 .find(|iface| iface.name() == interface_name)
238 .ok_or(InterfaceNotFound(interface_name.to_string()))?;
239
240 let methods = interface.methods();
241 let method = methods
242 .iter()
243 .find(|method| method.name() == member_name)
244 .ok_or(MemberNotFound(member_name.to_string()))?;
245
246 let args = method.args();
247
248 let signature = {
249 if arg_name.is_some() {
250 args.iter()
251 .find(|arg| arg.name() == arg_name)
252 .ok_or(ArgumentNotFound(
253 arg_name.expect("arg_name guarded by 'is_some'").to_string(),
254 ))?
255 .ty()
256 .to_string()
257 } else {
258 args.iter()
259 .filter(|arg| arg.direction() == Some(Out))
260 .map(|arg| arg.ty().to_string())
261 .collect::<String>()
262 }
263 };
264
265 Ok(Signature::from_str(&signature).map_err(|_| "Invalid signature")?)
266}
267
268/// Retrieve the signature of a method's argument type from XML.
269///
270/// Useful when one or more arguments, used to call a method, outline a useful type.
271///
272/// If you provide an argument name, then the signature of that argument is returned.
273/// If you do not provide an argument name, then the signature of all arguments to the call is
274/// returned.
275///
276/// # Examples
277///
278/// ```rust
279/// use std::fs::File;
280/// use std::collections::HashMap;
281/// use std::io::{Seek, SeekFrom, Write};
282/// use tempfile::tempfile;
283/// use zvariant::{Type, Value};
284/// use zbus_lockstep::get_method_args_type;
285///
286/// let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
287/// <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
288/// <interface name="org.freedesktop.Notifications">
289/// <method name="Notify">
290/// <arg type="s" name="app_name" direction="in"/>
291/// <arg type="u" name="replaces_id" direction="in"/>
292/// <arg type="s" name="app_icon" direction="in"/>
293/// <arg type="s" name="summary" direction="in"/>
294/// <arg type="s" name="body" direction="in"/>
295/// <arg type="as" name="actions" direction="in"/>
296/// <arg type="a{sv}" name="hints" direction="in"/>
297/// <arg type="i" name="expire_timeout" direction="in"/>
298/// <arg type="u" name="id" direction="out"/>
299/// </method>
300/// </interface>
301/// </node>
302/// "#;
303///
304/// #[derive(Debug, PartialEq, Type)]
305/// struct Notification<'a> {
306/// app_name: String,
307/// replaces_id: u32,
308/// app_icon: String,
309/// summary: String,
310/// body: String,
311/// actions: Vec<String>,
312/// hints: HashMap<String, Value<'a>>,
313/// expire_timeout: i32,
314/// }
315///
316/// let mut xml_file = tempfile().unwrap();
317/// xml_file.write_all(xml.as_bytes()).unwrap();
318/// xml_file.seek(SeekFrom::Start(0)).unwrap();
319///
320/// let interface_name = "org.freedesktop.Notifications";
321/// let member_name = "Notify";
322///
323/// let signature = get_method_args_type(xml_file, interface_name, member_name, None).unwrap();
324/// assert_eq!(&signature, Notification::SIGNATURE);
325/// ```
326pub fn get_method_args_type(
327 mut xml: impl Read,
328 interface_name: &str,
329 member_name: &str,
330 arg_name: Option<&str>,
331) -> Result<Signature> {
332 let node = Node::from_reader(&mut xml)?;
333
334 let interfaces = node.interfaces();
335 let interface = interfaces
336 .iter()
337 .find(|iface| iface.name() == interface_name)
338 .ok_or(InterfaceNotFound(interface_name.to_owned()))?;
339
340 let methods = interface.methods();
341 let method = methods
342 .iter()
343 .find(|method| method.name() == member_name)
344 .ok_or(member_name.to_owned())?;
345
346 let args = method.args();
347
348 let signature = if arg_name.is_some() {
349 args.iter()
350 .find(|arg| arg.name() == arg_name)
351 .ok_or(ArgumentNotFound(
352 arg_name.expect("arg_name guarded by is_some").to_string(),
353 ))?
354 .ty()
355 .to_string()
356 } else {
357 args.iter()
358 .filter(|arg| arg.direction() == Some(In))
359 .map(|arg| arg.ty().to_string())
360 .collect::<String>()
361 };
362
363 Ok(Signature::from_str(&signature).map_err(|_| "Invalid signature")?)
364}
365
366#[cfg(test)]
367mod test {
368 use std::io::{Seek, SeekFrom, Write};
369
370 use tempfile::tempfile;
371 use zvariant::{OwnedObjectPath, Type};
372
373 use crate::get_signal_body_type;
374
375 #[test]
376 fn test_get_signature_of_cache_add_accessible() {
377 #[derive(Debug, PartialEq, Type)]
378 struct Accessible {
379 name: String,
380 path: OwnedObjectPath,
381 }
382
383 #[derive(Debug, PartialEq, Type)]
384 struct CacheItem {
385 obj: Accessible,
386 application: Accessible,
387 parent: Accessible,
388 index_in_parent: i32,
389 child_count: i32,
390 interfaces: Vec<String>,
391 name: String,
392 role: u32,
393 description: String,
394 state_set: Vec<u32>,
395 }
396
397 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
398 <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
399 <interface name="org.a11y.atspi.Cache">
400 <signal name="AddAccessible">
401 <arg name="nodeAdded" type="((so)(so)(so)iiassusau)"/>
402 <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/>
403 </signal>
404 </interface>
405 </node>
406 "#;
407
408 let mut xml_file = tempfile().unwrap();
409 xml_file.write_all(xml.as_bytes()).unwrap();
410 xml_file.seek(SeekFrom::Start(0)).unwrap();
411
412 let interface_name = "org.a11y.atspi.Cache";
413 let member_name = "AddAccessible";
414
415 let signature = get_signal_body_type(xml_file, interface_name, member_name, None).unwrap();
416 assert_eq!(signature, *CacheItem::SIGNATURE);
417 }
418}