Skip to main content

zbus_xml/
lib.rs

1#![deny(rust_2018_idioms)]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/z-galaxy/zbus/9f7a90d2b594ddc48b7a5f39fda5e00cd56a7dfb/logo.png"
4)]
5#![doc = include_str!("../README.md")]
6#![doc(test(attr(
7    warn(unused),
8    deny(warnings),
9    allow(dead_code),
10    // W/o this, we seem to get some bogus warning about `extern crate zbus`.
11    allow(unused_extern_crates),
12)))]
13
14mod error;
15#[allow(deprecated)]
16pub use error::{DeError, SeError};
17pub use error::{Error, Result, XmlError};
18
19mod xml;
20use xml::escape;
21
22pub mod telepathy;
23
24use serde::{Deserialize, Serialize};
25use std::{
26    fmt,
27    io::{BufWriter, Read, Write},
28    ops::Deref,
29};
30
31use zbus_names::{InterfaceName, MemberName, PropertyName};
32
33/// A warning about document content that was ignored during parsing.
34///
35/// The D-Bus introspection format is sometimes extended with elements from other vocabularies,
36/// most notably the [Telepathy extensions] (`tp:enum`, `tp:struct`, …). The parser skips over
37/// any element it has no use for — or understands but cannot make sense of, e. g. a Telepathy
38/// type definition missing a required attribute — and records a `Warning`, which
39/// [`Node::from_reader_with_warnings`] hands back to the caller.
40///
41/// [Telepathy extensions]: https://telepathy.freedesktop.org/spec/
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Warning {
44    element: String,
45    position: usize,
46    message: String,
47}
48
49impl Warning {
50    /// A warning about a skipped element that is not part of the introspection format.
51    pub(crate) fn unsupported(element: impl Into<String>, position: usize) -> Self {
52        let element = element.into();
53        let message = format!("unsupported element `<{element}>` ignored");
54        Self {
55            element,
56            position,
57            message,
58        }
59    }
60
61    /// A warning about a skipped element that is understood but could not be parsed.
62    pub(crate) fn malformed(
63        element: impl Into<String>,
64        position: usize,
65        reason: impl fmt::Display,
66    ) -> Self {
67        let element = element.into();
68        let message = format!("malformed element `<{element}>` ignored: {reason}");
69        Self {
70            element,
71            position,
72            message,
73        }
74    }
75
76    /// The name of the element that was ignored.
77    pub fn element(&self) -> &str {
78        &self.element
79    }
80
81    /// The byte offset in the document at which the element starts.
82    pub fn position(&self) -> usize {
83        self.position
84    }
85
86    /// A message describing what was ignored, and why.
87    pub fn message(&self) -> &str {
88        &self.message
89    }
90}
91
92impl fmt::Display for Warning {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "{} (at byte offset {})", self.message, self.position)
95    }
96}
97
98/// Annotations are generic key/value pairs of metadata.
99#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
100pub struct Annotation {
101    #[serde(rename = "@name")]
102    name: String,
103    #[serde(rename = "@value")]
104    value: String,
105}
106
107impl Annotation {
108    /// Return the annotation name/key.
109    pub fn name(&self) -> &str {
110        &self.name
111    }
112
113    /// Return the annotation value.
114    pub fn value(&self) -> &str {
115        &self.value
116    }
117
118    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
119        write!(
120            w,
121            "<annotation name=\"{}\" value=\"{}\"/>",
122            escape(&self.name),
123            escape(&self.value)
124        )
125    }
126}
127
128/// A direction of an argument
129#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
130pub enum ArgDirection {
131    #[serde(rename = "in")]
132    In,
133    #[serde(rename = "out")]
134    Out,
135}
136
137impl ArgDirection {
138    fn xml_value(&self) -> &'static str {
139        match self {
140            ArgDirection::In => "in",
141            ArgDirection::Out => "out",
142        }
143    }
144}
145
146/// An argument
147#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
148pub struct Arg {
149    #[serde(rename = "@name")]
150    name: Option<String>,
151    #[serde(rename = "@type")]
152    ty: Signature,
153    #[serde(rename = "@direction")]
154    direction: Option<ArgDirection>,
155    #[serde(rename = "annotation", default)]
156    annotations: Vec<Annotation>,
157    #[serde(skip)]
158    docstring: Option<String>,
159    #[serde(skip)]
160    tp_type: Option<String>,
161}
162
163impl Arg {
164    /// Return the argument name, if any.
165    pub fn name(&self) -> Option<&str> {
166        self.name.as_deref()
167    }
168
169    /// Return the argument type.
170    pub fn ty(&self) -> &Signature {
171        &self.ty
172    }
173
174    /// Return the argument direction, if any.
175    pub fn direction(&self) -> Option<ArgDirection> {
176        self.direction
177    }
178
179    /// Return the associated annotations.
180    pub fn annotations(&self) -> &[Annotation] {
181        &self.annotations
182    }
183
184    /// Return the content of the Telepathy `tp:docstring` extension element, if any.
185    ///
186    /// The content — typically HTML — is returned as it appears in the document, with the
187    /// surrounding whitespace trimmed. Note that docstrings are only captured when parsing;
188    /// the writer does not emit them.
189    pub fn docstring(&self) -> Option<&str> {
190        self.docstring.as_deref()
191    }
192
193    /// Return the named Telepathy type of the argument (its `tp:type` attribute), if any.
194    ///
195    /// The name refers to a [type definition](telepathy::TypeDef) in scope, with one `[]`
196    /// suffix per level of array nesting (e. g. `Playlist[]`).
197    pub fn tp_type(&self) -> Option<&str> {
198        self.tp_type.as_deref()
199    }
200
201    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
202        write!(w, "<arg")?;
203        if let Some(name) = &self.name {
204            write!(w, " name=\"{}\"", escape(name))?;
205        }
206        write!(w, " type=\"{}\"", escape(&self.ty.to_string()))?;
207        if let Some(direction) = self.direction {
208            write!(w, " direction=\"{}\"", direction.xml_value())?;
209        }
210        if self.annotations.is_empty() {
211            return write!(w, "/>");
212        }
213        write!(w, ">")?;
214        for annotation in &self.annotations {
215            annotation.write_xml(w)?;
216        }
217        write!(w, "</arg>")
218    }
219}
220
221/// A method
222#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
223pub struct Method<'a> {
224    #[serde(rename = "@name", borrow)]
225    name: MemberName<'a>,
226    #[serde(rename = "arg", default)]
227    args: Vec<Arg>,
228    #[serde(rename = "annotation", default)]
229    annotations: Vec<Annotation>,
230    #[serde(skip)]
231    docstring: Option<String>,
232}
233
234impl Method<'_> {
235    /// Return the method name.
236    pub fn name(&self) -> MemberName<'_> {
237        self.name.as_ref()
238    }
239
240    /// Return the method arguments.
241    pub fn args(&self) -> &[Arg] {
242        &self.args
243    }
244
245    /// Return the method annotations.
246    pub fn annotations(&self) -> &[Annotation] {
247        &self.annotations
248    }
249
250    /// Return the content of the Telepathy `tp:docstring` extension element, if any.
251    ///
252    /// The content — typically HTML — is returned as it appears in the document, with the
253    /// surrounding whitespace trimmed. Note that docstrings are only captured when parsing;
254    /// the writer does not emit them.
255    pub fn docstring(&self) -> Option<&str> {
256        self.docstring.as_deref()
257    }
258
259    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
260        write!(w, "<method name=\"{}\"", escape(self.name.as_str()))?;
261        if self.args.is_empty() && self.annotations.is_empty() {
262            return write!(w, "/>");
263        }
264        write!(w, ">")?;
265        for arg in &self.args {
266            arg.write_xml(w)?;
267        }
268        for annotation in &self.annotations {
269            annotation.write_xml(w)?;
270        }
271        write!(w, "</method>")
272    }
273}
274
275/// A signal
276#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
277pub struct Signal<'a> {
278    #[serde(rename = "@name", borrow)]
279    name: MemberName<'a>,
280
281    #[serde(rename = "arg", default)]
282    args: Vec<Arg>,
283    #[serde(rename = "annotation", default)]
284    annotations: Vec<Annotation>,
285    #[serde(skip)]
286    docstring: Option<String>,
287}
288
289impl Signal<'_> {
290    /// Return the signal name.
291    pub fn name(&self) -> MemberName<'_> {
292        self.name.as_ref()
293    }
294
295    /// Return the signal arguments.
296    pub fn args(&self) -> &[Arg] {
297        &self.args
298    }
299
300    /// Return the signal annotations.
301    pub fn annotations(&self) -> &[Annotation] {
302        &self.annotations
303    }
304
305    /// Return the content of the Telepathy `tp:docstring` extension element, if any.
306    ///
307    /// The content — typically HTML — is returned as it appears in the document, with the
308    /// surrounding whitespace trimmed. Note that docstrings are only captured when parsing;
309    /// the writer does not emit them.
310    pub fn docstring(&self) -> Option<&str> {
311        self.docstring.as_deref()
312    }
313
314    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
315        write!(w, "<signal name=\"{}\"", escape(self.name.as_str()))?;
316        if self.args.is_empty() && self.annotations.is_empty() {
317            return write!(w, "/>");
318        }
319        write!(w, ">")?;
320        for arg in &self.args {
321            arg.write_xml(w)?;
322        }
323        for annotation in &self.annotations {
324            annotation.write_xml(w)?;
325        }
326        write!(w, "</signal>")
327    }
328}
329
330/// The possible property access types
331#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
332pub enum PropertyAccess {
333    #[serde(rename = "read")]
334    Read,
335    #[serde(rename = "write")]
336    Write,
337    #[serde(rename = "readwrite")]
338    ReadWrite,
339}
340
341impl PropertyAccess {
342    pub fn read(&self) -> bool {
343        matches!(self, PropertyAccess::Read | PropertyAccess::ReadWrite)
344    }
345
346    pub fn write(&self) -> bool {
347        matches!(self, PropertyAccess::Write | PropertyAccess::ReadWrite)
348    }
349
350    fn xml_value(&self) -> &'static str {
351        match self {
352            PropertyAccess::Read => "read",
353            PropertyAccess::Write => "write",
354            PropertyAccess::ReadWrite => "readwrite",
355        }
356    }
357}
358
359/// A property
360#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
361pub struct Property<'a> {
362    #[serde(rename = "@name", borrow)]
363    name: PropertyName<'a>,
364
365    #[serde(rename = "@type")]
366    ty: Signature,
367    #[serde(rename = "@access")]
368    access: PropertyAccess,
369
370    #[serde(rename = "annotation", default)]
371    annotations: Vec<Annotation>,
372    #[serde(skip)]
373    docstring: Option<String>,
374    #[serde(skip)]
375    tp_type: Option<String>,
376}
377
378impl Property<'_> {
379    /// Returns the property name.
380    pub fn name(&self) -> PropertyName<'_> {
381        self.name.as_ref()
382    }
383
384    /// Returns the property type.
385    pub fn ty(&self) -> &Signature {
386        &self.ty
387    }
388
389    /// Returns the property access flags (should be "read", "write" or "readwrite").
390    pub fn access(&self) -> PropertyAccess {
391        self.access
392    }
393
394    /// Return the associated annotations.
395    pub fn annotations(&self) -> &[Annotation] {
396        &self.annotations
397    }
398
399    /// Return the content of the Telepathy `tp:docstring` extension element, if any.
400    ///
401    /// The content — typically HTML — is returned as it appears in the document, with the
402    /// surrounding whitespace trimmed. Note that docstrings are only captured when parsing;
403    /// the writer does not emit them.
404    pub fn docstring(&self) -> Option<&str> {
405        self.docstring.as_deref()
406    }
407
408    /// Return the named Telepathy type of the property (its `tp:type` attribute), if any.
409    ///
410    /// The name refers to a [type definition](telepathy::TypeDef) in scope, with one `[]`
411    /// suffix per level of array nesting (e. g. `Playlist[]`).
412    pub fn tp_type(&self) -> Option<&str> {
413        self.tp_type.as_deref()
414    }
415
416    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
417        write!(
418            w,
419            "<property name=\"{}\" type=\"{}\" access=\"{}\"",
420            escape(self.name.as_str()),
421            escape(&self.ty.to_string()),
422            self.access.xml_value()
423        )?;
424        if self.annotations.is_empty() {
425            return write!(w, "/>");
426        }
427        write!(w, ">")?;
428        for annotation in &self.annotations {
429            annotation.write_xml(w)?;
430        }
431        write!(w, "</property>")
432    }
433}
434
435/// An interface
436#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
437pub struct Interface<'a> {
438    #[serde(rename = "@name", borrow)]
439    name: InterfaceName<'a>,
440
441    #[serde(rename = "method", default)]
442    methods: Vec<Method<'a>>,
443    #[serde(rename = "property", default)]
444    properties: Vec<Property<'a>>,
445    #[serde(rename = "signal", default)]
446    signals: Vec<Signal<'a>>,
447    #[serde(rename = "annotation", default)]
448    annotations: Vec<Annotation>,
449    #[serde(skip)]
450    docstring: Option<String>,
451    #[serde(skip)]
452    telepathy_types: Vec<telepathy::TypeDef>,
453}
454
455impl<'a> Interface<'a> {
456    /// Returns the interface name.
457    pub fn name(&self) -> InterfaceName<'_> {
458        self.name.as_ref()
459    }
460
461    /// Returns the interface methods.
462    pub fn methods(&self) -> &[Method<'a>] {
463        &self.methods
464    }
465
466    /// Returns the interface signals.
467    pub fn signals(&self) -> &[Signal<'a>] {
468        &self.signals
469    }
470
471    /// Returns the interface properties.
472    pub fn properties(&self) -> &[Property<'_>] {
473        &self.properties
474    }
475
476    /// Return the associated annotations.
477    pub fn annotations(&self) -> &[Annotation] {
478        &self.annotations
479    }
480
481    /// Return the content of the Telepathy `tp:docstring` extension element, if any.
482    ///
483    /// The content — typically HTML — is returned as it appears in the document, with the
484    /// surrounding whitespace trimmed. Note that docstrings are only captured when parsing;
485    /// the writer does not emit them.
486    pub fn docstring(&self) -> Option<&str> {
487        self.docstring.as_deref()
488    }
489
490    /// Return the Telepathy type definitions on this interface.
491    pub fn telepathy_types(&self) -> &[telepathy::TypeDef] {
492        &self.telepathy_types
493    }
494
495    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
496        write!(w, "<interface name=\"{}\"", escape(self.name.as_str()))?;
497        if self.methods.is_empty()
498            && self.properties.is_empty()
499            && self.signals.is_empty()
500            && self.annotations.is_empty()
501        {
502            return write!(w, "/>");
503        }
504        write!(w, ">")?;
505        for method in &self.methods {
506            method.write_xml(w)?;
507        }
508        for property in &self.properties {
509            property.write_xml(w)?;
510        }
511        for signal in &self.signals {
512            signal.write_xml(w)?;
513        }
514        for annotation in &self.annotations {
515            annotation.write_xml(w)?;
516        }
517        write!(w, "</interface>")
518    }
519}
520
521/// An introspection tree node (typically the root of the XML document).
522#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
523pub struct Node<'a> {
524    #[serde(rename = "@name")]
525    name: Option<String>,
526
527    #[serde(rename = "interface", default, borrow)]
528    interfaces: Vec<Interface<'a>>,
529    #[serde(rename = "node", default, borrow)]
530    nodes: Vec<Node<'a>>,
531    #[serde(skip)]
532    docstring: Option<String>,
533    #[serde(skip)]
534    telepathy_types: Vec<telepathy::TypeDef>,
535}
536
537impl<'a> Node<'a> {
538    /// Parse the introspection XML document from reader.
539    ///
540    /// Note that `reader` is consumed until end-of-stream before parsing, so this must not be
541    /// used with a reader that stays open past the end of the document (e.g. a socket).
542    pub fn from_reader<R: Read>(reader: R) -> Result<Node<'a>> {
543        Ok(Node::from_reader_with_warnings(reader)?.0)
544    }
545
546    /// Parse the introspection XML document from reader, collecting warnings.
547    ///
548    /// In addition to the parsed node, a [`Warning`] is returned for every element that is not
549    /// part of the [introspection format] (except Telepathy docstrings, which are captured —
550    /// see [`Interface::docstring`]) and was therefore ignored, e. g. the type-definition
551    /// elements of the Telepathy extensions (`tp:enum`, `tp:struct`, …).
552    ///
553    /// Note that `reader` is consumed until end-of-stream before parsing, so this must not be
554    /// used with a reader that stays open past the end of the document (e.g. a socket).
555    ///
556    /// [introspection format]: https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
557    pub fn from_reader_with_warnings<R: Read>(mut reader: R) -> Result<(Node<'a>, Vec<Warning>)> {
558        let mut input = String::new();
559        reader.read_to_string(&mut input)?;
560
561        xml::parse_with_warnings(&input)
562    }
563
564    /// Write the XML document to writer.
565    ///
566    /// Note that data which is only captured when parsing — Telepathy docstrings, type
567    /// definitions and `tp:type` references — is not written. Consequently, a document that
568    /// carried any does not compare equal to its written-and-reparsed self.
569    pub fn to_writer<W: Write>(&self, writer: W) -> Result<()> {
570        let mut writer = BufWriter::new(writer);
571        self.write_xml(&mut writer)?;
572        writer.flush()?;
573
574        Ok(())
575    }
576
577    /// Returns the node name, if any.
578    pub fn name(&self) -> Option<&str> {
579        self.name.as_deref()
580    }
581
582    /// Returns the children nodes.
583    pub fn nodes(&self) -> &[Node<'a>] {
584        &self.nodes
585    }
586
587    /// Returns the interfaces on this node.
588    pub fn interfaces(&self) -> &[Interface<'a>] {
589        &self.interfaces
590    }
591
592    /// Return the content of the Telepathy `tp:docstring` extension element, if any.
593    ///
594    /// The content — typically HTML — is returned as it appears in the document, with the
595    /// surrounding whitespace trimmed. Note that docstrings are only captured when parsing;
596    /// the writer does not emit them.
597    pub fn docstring(&self) -> Option<&str> {
598        self.docstring.as_deref()
599    }
600
601    /// Return the Telepathy type definitions on this node.
602    pub fn telepathy_types(&self) -> &[telepathy::TypeDef] {
603        &self.telepathy_types
604    }
605
606    fn write_xml<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
607        write!(w, "<node")?;
608        if let Some(name) = &self.name {
609            write!(w, " name=\"{}\"", escape(name))?;
610        }
611        if self.interfaces.is_empty() && self.nodes.is_empty() {
612            return write!(w, "/>");
613        }
614        write!(w, ">")?;
615        for interface in &self.interfaces {
616            interface.write_xml(w)?;
617        }
618        for node in &self.nodes {
619            node.write_xml(w)?;
620        }
621        write!(w, "</node>")
622    }
623}
624
625impl<'a> TryFrom<&'a str> for Node<'a> {
626    type Error = Error;
627
628    /// Parse the introspection XML document from `s`.
629    fn try_from(s: &'a str) -> Result<Node<'a>> {
630        xml::parse(s)
631    }
632}
633
634/// A thin wrapper around `zvariant::parsed::Signature`.
635///
636/// This is to allow `Signature` to be deserialized from an owned string, which is what XML
637/// deserializers typically produce.
638#[derive(Debug, Serialize, Clone, PartialEq)]
639pub struct Signature(zvariant::Signature);
640
641impl Signature {
642    /// Return the inner `zvariant::Signature`.
643    pub fn inner(&self) -> &zvariant::Signature {
644        &self.0
645    }
646
647    /// Convert this `Signature` into the inner `zvariant::parsed::Signature`.
648    pub fn into_inner(self) -> zvariant::Signature {
649        self.0
650    }
651}
652
653impl<'de> serde::de::Deserialize<'de> for Signature {
654    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
655    where
656        D: serde::de::Deserializer<'de>,
657    {
658        String::deserialize(deserializer).and_then(|s| {
659            zvariant::Signature::try_from(s.as_bytes())
660                .map_err(serde::de::Error::custom)
661                .map(Signature)
662        })
663    }
664}
665
666impl Deref for Signature {
667    type Target = zvariant::Signature;
668
669    fn deref(&self) -> &Self::Target {
670        self.inner()
671    }
672}
673
674impl PartialEq<str> for Signature {
675    fn eq(&self, other: &str) -> bool {
676        self.0 == other
677    }
678}