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}