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.2")]
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 let Some(known_arg_name) = arg_name {
250            args.iter()
251                .find(|arg| arg.name() == Some(known_arg_name))
252                .ok_or(ArgumentNotFound(known_arg_name.to_string()))?
253                .ty()
254                .to_string()
255        } else {
256            args.iter()
257                .filter(|arg| arg.direction() == Some(Out))
258                .map(|arg| arg.ty().to_string())
259                .collect::<String>()
260        }
261    };
262
263    Ok(Signature::from_str(&signature).map_err(|_| "Invalid signature")?)
264}
265
266/// Retrieve the signature of a method's argument type from XML.
267///
268/// Useful when one or more arguments, used to call a method, outline a useful type.
269///
270/// If you provide an argument name, then the signature of that argument is returned.
271/// If you do not provide an argument name, then the signature of all arguments to the call is
272/// returned.
273///
274/// # Examples
275///
276/// ```rust
277/// use std::fs::File;
278/// use std::collections::HashMap;
279/// use std::io::{Seek, SeekFrom, Write};
280/// use tempfile::tempfile;
281/// use zvariant::{Type, Value};
282/// use zbus_lockstep::get_method_args_type;
283///
284/// let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
285/// <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
286///  <interface name="org.freedesktop.Notifications">
287///    <method name="Notify">
288///      <arg type="s" name="app_name" direction="in"/>
289///      <arg type="u" name="replaces_id" direction="in"/>
290///      <arg type="s" name="app_icon" direction="in"/>
291///      <arg type="s" name="summary" direction="in"/>
292///      <arg type="s" name="body" direction="in"/>
293///      <arg type="as" name="actions" direction="in"/>
294///      <arg type="a{sv}" name="hints" direction="in"/>
295///      <arg type="i" name="expire_timeout" direction="in"/>
296///      <arg type="u" name="id" direction="out"/>
297///    </method>
298///  </interface>
299/// </node>
300/// "#;
301///
302/// #[derive(Debug, PartialEq, Type)]
303/// struct Notification<'a> {
304///    app_name: String,
305///    replaces_id: u32,
306///    app_icon: String,
307///    summary: String,
308///    body: String,
309///    actions: Vec<String>,
310///    hints: HashMap<String, Value<'a>>,
311///    expire_timeout: i32,
312/// }
313///
314/// let mut xml_file = tempfile().unwrap();
315/// xml_file.write_all(xml.as_bytes()).unwrap();
316/// xml_file.seek(SeekFrom::Start(0)).unwrap();
317///
318/// let interface_name = "org.freedesktop.Notifications";
319/// let member_name = "Notify";
320///
321/// let signature = get_method_args_type(xml_file, interface_name, member_name, None).unwrap();
322/// assert_eq!(&signature, Notification::SIGNATURE);
323/// ```
324pub fn get_method_args_type(
325    mut xml: impl Read,
326    interface_name: &str,
327    member_name: &str,
328    arg_name: Option<&str>,
329) -> Result<Signature> {
330    let node = Node::from_reader(&mut xml)?;
331
332    let interfaces = node.interfaces();
333    let interface = interfaces
334        .iter()
335        .find(|iface| iface.name() == interface_name)
336        .ok_or(InterfaceNotFound(interface_name.to_owned()))?;
337
338    let methods = interface.methods();
339    let method = methods
340        .iter()
341        .find(|method| method.name() == member_name)
342        .ok_or(member_name.to_owned())?;
343
344    let args = method.args();
345
346    let signature = if let Some(known_arg_name) = arg_name {
347        args.iter()
348            .find(|arg| arg.name() == Some(known_arg_name))
349            .ok_or(ArgumentNotFound(known_arg_name.to_string()))?
350            .ty()
351            .to_string()
352    } else {
353        args.iter()
354            .filter(|arg| arg.direction() == Some(In))
355            .map(|arg| arg.ty().to_string())
356            .collect::<String>()
357    };
358
359    Ok(Signature::from_str(&signature).map_err(|_| "Invalid signature")?)
360}
361
362#[cfg(test)]
363mod test {
364    use std::io::{Seek, SeekFrom, Write};
365
366    use tempfile::tempfile;
367    use zvariant::{OwnedObjectPath, Type};
368
369    use crate::get_signal_body_type;
370
371    #[test]
372    fn test_get_signature_of_cache_add_accessible() {
373        #[derive(Debug, PartialEq, Type)]
374        struct Accessible {
375            name: String,
376            path: OwnedObjectPath,
377        }
378
379        #[derive(Debug, PartialEq, Type)]
380        struct CacheItem {
381            obj: Accessible,
382            application: Accessible,
383            parent: Accessible,
384            index_in_parent: i32,
385            child_count: i32,
386            interfaces: Vec<String>,
387            name: String,
388            role: u32,
389            description: String,
390            state_set: Vec<u32>,
391        }
392
393        let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
394            <node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
395                <interface name="org.a11y.atspi.Cache">
396                    <signal name="AddAccessible">
397                        <arg name="nodeAdded" type="((so)(so)(so)iiassusau)"/>
398                        <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiAccessibleCacheItem"/>
399                    </signal>
400                </interface>
401            </node>
402        "#;
403
404        let mut xml_file = tempfile().unwrap();
405        xml_file.write_all(xml.as_bytes()).unwrap();
406        xml_file.seek(SeekFrom::Start(0)).unwrap();
407
408        let interface_name = "org.a11y.atspi.Cache";
409        let member_name = "AddAccessible";
410
411        let signature = get_signal_body_type(xml_file, interface_name, member_name, None).unwrap();
412        assert_eq!(signature, *CacheItem::SIGNATURE);
413    }
414}