zbus/match_rule/
builder.rs

1use static_assertions::assert_impl_all;
2
3use crate::{
4    match_rule::PathSpec,
5    message::Type,
6    names::{BusName, InterfaceName, MemberName, UniqueName},
7    zvariant::{ObjectPath, Str},
8    Error, MatchRule, Result,
9};
10
11const MAX_ARGS: u8 = 64;
12
13/// Builder for [`MatchRule`].
14///
15/// This is created by [`MatchRule::builder`].
16#[derive(Debug)]
17pub struct Builder<'m>(MatchRule<'m>);
18
19assert_impl_all!(Builder<'_>: Send, Sync, Unpin);
20
21impl<'m> Builder<'m> {
22    /// Build the `MatchRule`.
23    pub fn build(self) -> MatchRule<'m> {
24        self.0
25    }
26
27    /// Set the sender.
28    pub fn sender<B>(mut self, sender: B) -> Result<Self>
29    where
30        B: TryInto<BusName<'m>>,
31        B::Error: Into<Error>,
32    {
33        self.0.sender = Some(sender.try_into().map_err(Into::into)?);
34
35        Ok(self)
36    }
37
38    /// Set the message type.
39    pub fn msg_type(mut self, msg_type: Type) -> Self {
40        self.0.msg_type = Some(msg_type);
41
42        self
43    }
44
45    /// Set the interface.
46    pub fn interface<I>(mut self, interface: I) -> Result<Self>
47    where
48        I: TryInto<InterfaceName<'m>>,
49        I::Error: Into<Error>,
50    {
51        self.0.interface = Some(interface.try_into().map_err(Into::into)?);
52
53        Ok(self)
54    }
55
56    /// Set the member.
57    pub fn member<M>(mut self, member: M) -> Result<Self>
58    where
59        M: TryInto<MemberName<'m>>,
60        M::Error: Into<Error>,
61    {
62        self.0.member = Some(member.try_into().map_err(Into::into)?);
63
64        Ok(self)
65    }
66
67    /// Set the path.
68    ///
69    /// Note: Since both a path and a path namespace are not allowed to appear in a match rule at
70    /// the same time, this overrides any path namespace previously set.
71    pub fn path<P>(mut self, path: P) -> Result<Self>
72    where
73        P: TryInto<ObjectPath<'m>>,
74        P::Error: Into<Error>,
75    {
76        self.0.path_spec = path
77            .try_into()
78            .map(PathSpec::Path)
79            .map(Some)
80            .map_err(Into::into)?;
81
82        Ok(self)
83    }
84
85    /// Set the path namespace.
86    ///
87    /// Note: Since both a path and a path namespace are not allowed to appear in a match rule at
88    /// the same time, this overrides any path previously set.
89    pub fn path_namespace<P>(mut self, path_namespace: P) -> Result<Self>
90    where
91        P: TryInto<ObjectPath<'m>>,
92        P::Error: Into<Error>,
93    {
94        self.0.path_spec = path_namespace
95            .try_into()
96            .map(PathSpec::PathNamespace)
97            .map(Some)
98            .map_err(Into::into)?;
99
100        Ok(self)
101    }
102
103    /// Set the destination.
104    pub fn destination<B>(mut self, destination: B) -> Result<Self>
105    where
106        B: TryInto<UniqueName<'m>>,
107        B::Error: Into<Error>,
108    {
109        self.0.destination = Some(destination.try_into().map_err(Into::into)?);
110
111        Ok(self)
112    }
113
114    /// Append an arguments.
115    ///
116    /// Use this in instead of [`Builder::arg`] if you want to sequentially add args.
117    ///
118    /// # Errors
119    ///
120    /// [`Error::InvalidMatchRule`] on attempt to add the 65th argument.
121    pub fn add_arg<S>(self, arg: S) -> Result<Self>
122    where
123        S: Into<Str<'m>>,
124    {
125        let idx = self.0.args.len() as u8;
126
127        self.arg(idx, arg)
128    }
129
130    /// Add an argument of a specified index.
131    ///
132    /// # Errors
133    ///
134    /// [`Error::InvalidMatchRule`] if `idx` is greater than 64.
135    pub fn arg<S>(mut self, idx: u8, arg: S) -> Result<Self>
136    where
137        S: Into<Str<'m>>,
138    {
139        if idx >= MAX_ARGS {
140            return Err(Error::InvalidMatchRule);
141        }
142        let value = (idx, arg.into());
143        let vec_idx = match self.0.args().binary_search_by(|(i, _)| i.cmp(&idx)) {
144            Ok(i) => {
145                // If the argument is already present, replace it.
146                self.0.args.remove(i);
147
148                i
149            }
150            Err(i) => i,
151        };
152        self.0.args.insert(vec_idx, value);
153
154        Ok(self)
155    }
156
157    /// Append a path argument.
158    ///
159    /// Use this in instead of [`Builder::arg_path`] if you want to sequentially add args.
160    ///
161    /// # Errors
162    ///
163    /// [`Error::InvalidMatchRule`] on attempt to add the 65th path argument.
164    pub fn add_arg_path<P>(self, arg_path: P) -> Result<Self>
165    where
166        P: TryInto<ObjectPath<'m>>,
167        P::Error: Into<Error>,
168    {
169        let idx = self.0.arg_paths.len() as u8;
170
171        self.arg_path(idx, arg_path)
172    }
173
174    /// Add a path argument of a specified index.
175    ///
176    /// # Errors
177    ///
178    /// [`Error::InvalidMatchRule`] if `idx` is greater than 64.
179    pub fn arg_path<P>(mut self, idx: u8, arg_path: P) -> Result<Self>
180    where
181        P: TryInto<ObjectPath<'m>>,
182        P::Error: Into<Error>,
183    {
184        if idx >= MAX_ARGS {
185            return Err(Error::InvalidMatchRule);
186        }
187
188        let value = (idx, arg_path.try_into().map_err(Into::into)?);
189        let vec_idx = match self.0.arg_paths().binary_search_by(|(i, _)| i.cmp(&idx)) {
190            Ok(i) => {
191                // If the argument is already present, replace it.
192                self.0.arg_paths.remove(i);
193
194                i
195            }
196            Err(i) => i,
197        };
198        self.0.arg_paths.insert(vec_idx, value);
199
200        Ok(self)
201    }
202
203    /// Set 0th argument's namespace.
204    ///
205    /// The namespace be a valid bus name or a valid element of a bus name. For more information,
206    /// see [the spec](https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus).
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// # use zbus::MatchRule;
212    /// // Valid namespaces
213    /// MatchRule::builder().arg0ns("org.mpris.MediaPlayer2").unwrap();
214    /// MatchRule::builder().arg0ns("org").unwrap();
215    /// MatchRule::builder().arg0ns(":org").unwrap();
216    /// MatchRule::builder().arg0ns(":1org").unwrap();
217    ///
218    /// // Invalid namespaces
219    /// MatchRule::builder().arg0ns("org.").unwrap_err();
220    /// MatchRule::builder().arg0ns(".org").unwrap_err();
221    /// MatchRule::builder().arg0ns("1org").unwrap_err();
222    /// MatchRule::builder().arg0ns(".").unwrap_err();
223    /// MatchRule::builder().arg0ns("org..freedesktop").unwrap_err();
224    /// ````
225    pub fn arg0ns<S>(mut self, namespace: S) -> Result<Self>
226    where
227        S: Into<Str<'m>>,
228    {
229        let namespace: Str<'m> = namespace.into();
230
231        // Rules: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus
232        // minus the requirement to have more than one element.
233
234        if namespace.is_empty() || namespace.len() > 255 {
235            return Err(Error::InvalidMatchRule);
236        }
237
238        let (is_unique, namespace_str) = match namespace.strip_prefix(':') {
239            Some(s) => (true, s),
240            None => (false, namespace.as_str()),
241        };
242
243        let valid_first_char = |s: &str| match s.chars().next() {
244            None | Some('.') => false,
245            Some('0'..='9') if !is_unique => false,
246            _ => true,
247        };
248
249        if !valid_first_char(namespace_str)
250            || !namespace_str.split('.').all(valid_first_char)
251            || !namespace_str
252                .chars()
253                .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
254        {
255            return Err(Error::InvalidMatchRule);
256        }
257
258        self.0.arg0ns = Some(namespace);
259
260        Ok(self)
261    }
262
263    /// Create a builder for `MatchRule`.
264    pub(crate) fn new() -> Self {
265        Self(MatchRule {
266            msg_type: None,
267            sender: None,
268            interface: None,
269            member: None,
270            path_spec: None,
271            destination: None,
272            args: Vec::with_capacity(MAX_ARGS as usize),
273            arg_paths: Vec::with_capacity(MAX_ARGS as usize),
274            arg0ns: None,
275        })
276    }
277}