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}