atspi_common/
object_match.rs

1use std::{borrow::Borrow, collections::HashMap, marker::PhantomData};
2
3use serde::{Deserialize, Serialize};
4use zvariant::{Signature, Type};
5
6use crate::{Interface, InterfaceSet, Role, State, StateSet};
7
8/// Defines how an object-tree is to be traversed.
9/// Used in `CollectionProxy`.
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Type)]
11#[repr(u32)]
12pub enum TreeTraversalType {
13	/// When traversing the tree, restrict to children of the current object.
14	RestrictChildren,
15
16	/// When traversing the tree, restrict to siblings of the current object.
17	RestrictSibling,
18
19	/// Traverse the tree in order of appearance.
20	#[default]
21	Inorder,
22}
23
24/// Definition of match rules for accessible objects.
25/// Rule(s) against which we can match an  object or a collection thereof.
26///
27/// # Examples
28/// ```rust
29/// # use zbus::MatchRule;
30/// let builder = MatchRule::builder();
31/// ```
32///
33#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
34pub struct ObjectMatchRule {
35	pub states: StateSet,
36	pub states_mt: MatchType,
37	pub attr: HashMap<String, String>,
38	pub attr_mt: MatchType,
39	pub roles: Vec<Role>,
40	pub roles_mt: MatchType,
41	pub ifaces: InterfaceSet,
42	pub ifaces_mt: MatchType,
43	pub invert: bool,
44	// Private phantom, gets compiled away.
45	// Here to ensure the builder is the only route to obtain a `MatchRule`
46	#[serde(skip)]
47	_marker: std::marker::PhantomData<()>,
48}
49
50/// !!! WARNING !!! :
51///
52/// State and Role are defined as u32 in Accessible.xml but as i32 in Collection.xml
53///
54/// The signature on `StateSet` is defined inconsistently in the XMLs:
55///
56/// - `Accessible.xml`: `GetState type="au"`
57/// - `Collection.xml`: `GetMatches` argument `name="rule" type="(aiia{ss}iaiiasib)"`
58///
59/// The latter starts with ai, which is a signature for an array of signed ints i32.
60///
61/// <https://gitlab.gnome.org/federico/at-spi2-core/-/commit/4885efedeef71e0df8210622771a0b1bb10e194d>
62impl Type for ObjectMatchRule {
63	const SIGNATURE: &'static Signature = &Signature::static_structure(&[
64		<Vec<i32>>::SIGNATURE,
65		&Signature::I32,
66		<HashMap<&str, &str>>::SIGNATURE,
67		&Signature::I32,
68		<Vec<i32>>::SIGNATURE,
69		&Signature::I32,
70		<Vec<&str>>::SIGNATURE,
71		&Signature::I32,
72		&Signature::Bool,
73	]);
74}
75
76impl ObjectMatchRule {
77	/// Create a new `MatchRuleBuilder`
78	#[must_use]
79	pub fn builder() -> ObjectMatchRuleBuilder {
80		ObjectMatchRuleBuilder::default()
81	}
82}
83
84/// The 'builder' type for `MatchRule`.  
85/// Use its methods to set match criteria.
86#[derive(Debug, Clone, Default)]
87pub struct ObjectMatchRuleBuilder {
88	states: StateSet,
89	states_mt: MatchType,
90	attr: HashMap<String, String>,
91	attr_mt: MatchType,
92	roles: Vec<Role>,
93	roles_mt: MatchType,
94	ifaces: InterfaceSet,
95	ifaces_mt: MatchType,
96	invert: bool,
97}
98
99impl ObjectMatchRuleBuilder {
100	/// Insert a `StateSet` to the builder
101	#[must_use]
102	pub fn states<I>(mut self, states: I, mt: MatchType) -> Self
103	where
104		I: IntoIterator,
105		I::Item: Borrow<State>,
106	{
107		self.states = states.into_iter().map(|state| *state.borrow()).collect();
108		self.states_mt = mt;
109		self
110	}
111
112	/// Insert a map of attributes
113	#[must_use]
114	pub fn attributes(mut self, attributes: HashMap<String, String>, mt: MatchType) -> Self {
115		self.attr = attributes;
116		self.attr_mt = mt;
117		self
118	}
119
120	/// Insert a slice of `Role`s
121	#[must_use]
122	pub fn roles(mut self, roles: &[Role], mt: MatchType) -> Self {
123		self.roles = roles.into();
124		self.roles_mt = mt;
125		self
126	}
127
128	/// Insert an `InterfaceSet` from a collection of `Interface`s
129	#[must_use]
130	pub fn interfaces<I>(mut self, interfaces: I, mt: MatchType) -> Self
131	where
132		I: IntoIterator,
133		I::Item: Borrow<Interface>,
134	{
135		self.ifaces = interfaces.into_iter().map(|iface| *iface.borrow()).collect();
136		self.ifaces_mt = mt;
137		self
138	}
139
140	/// Sets the inversion of the `MatchRule`, defaults to `false`, no inversion.
141	#[must_use]
142	pub fn invert(mut self, invert: bool) -> Self {
143		self.invert = invert;
144		self
145	}
146
147	/// Builds the [`ObjectMatchRule`]
148	///
149	/// [`ObjectMatchRule``]: crate::object_match::ObjectMatchRule
150	#[must_use]
151	pub fn build(self) -> ObjectMatchRule {
152		ObjectMatchRule {
153			states: self.states,
154			states_mt: self.states_mt,
155			attr: self.attr,
156			attr_mt: self.attr_mt,
157			roles: self.roles,
158			roles_mt: self.roles_mt,
159			ifaces: self.ifaces,
160			ifaces_mt: self.ifaces_mt,
161			invert: self.invert,
162			_marker: PhantomData,
163		}
164	}
165}
166
167/// Enumeration used by [`ObjectMatchRule`] to specify how to interpret [`ObjectRef`] objects.
168///
169/// [`ObjectRef`]: crate::object_ref::ObjectRef
170#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type, Default)]
171#[repr(i32)]
172pub enum MatchType {
173	/// Invalidates match criterion.
174	Invalid,
175
176	#[default]
177	/// All of the criteria must be met.
178	All,
179
180	/// Any of the criteria must criteria must be met.
181	Any,
182
183	/// None of the criteria must be met.
184	NA,
185
186	/// Same as [`Self::All`] if the criterion item is non-empty - All of the criteria must be met.
187	/// For empty criteria this rule requires the returned value to also have empty set.
188	Empty,
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
192#[repr(u32)]
193/// Enumeration used by interface `CollectionProxy` to specify the way [`ObjectRef`]
194/// objects should be sorted.
195///
196/// # Note
197///
198/// Only `Canonical` and `ReverseCanonical` are currently implemented in the known implementation of `Collection`.
199///
200/// see: [issue 140. "Are all the AtspiCollectionSortOrder types really a thing?"](https://gitlab.gnome.org/GNOME/at-spi2-core/-/issues/140)
201///
202/// [`ObjectRef`]: crate::object_ref::ObjectRef
203pub enum SortOrder {
204	/// Invalid sort order
205	///
206	/// Unimplemented
207	Invalid,
208
209	/// Canonical sort order
210	Canonical,
211
212	/// Flow sort order
213	///
214	/// Unimplemented
215	Flow,
216
217	/// Tab sort order
218	///
219	/// Unimplemented
220	Tab,
221
222	/// Reverse canonical sort order
223	ReverseCanonical,
224
225	/// Reverse flow sort order
226	///
227	/// Unimplemented
228	ReverseFlow,
229
230	/// Reverse tab sort order
231	///
232	/// Unimplemented
233	ReverseTab,
234}
235
236#[cfg(test)]
237mod tests {
238	use super::*;
239	use crate::{SortOrder, State};
240	use std::str::FromStr;
241	use zbus_lockstep::method_args_signature;
242
243	#[test]
244	fn validate_match_rule_signature() {
245		let signature = method_args_signature!(member: "GetMatchesTo", interface: "org.a11y.atspi.Collection", argument: "rule");
246		assert_eq!(*<ObjectMatchRule as Type>::SIGNATURE, signature);
247	}
248
249	#[test]
250	fn validate_match_type_signature() {
251		let rule_signature = method_args_signature!(member: "GetMatchesTo", interface: "org.a11y.atspi.Collection", argument: "rule");
252		// The match type signature is the fourth element in the signature
253		let match_type_signature_str = rule_signature.to_string();
254		let match_type_signature = Signature::from_str(&match_type_signature_str.as_str()[3..4])
255			.expect("Valid signature pattern");
256		assert_eq!(*<MatchType as Type>::SIGNATURE, match_type_signature);
257	}
258
259	#[test]
260	fn validate_tree_traversal_type_signature() {
261		let signature = method_args_signature!(member: "GetMatchesTo", interface: "org.a11y.atspi.Collection", argument: "tree");
262		assert_eq!(*<TreeTraversalType as Type>::SIGNATURE, signature);
263	}
264
265	#[test]
266	fn validate_sort_order_signature() {
267		let signature = method_args_signature!(member: "GetMatches", interface: "org.a11y.atspi.Collection", argument: "sortby");
268		assert_eq!(*<SortOrder as Type>::SIGNATURE, signature);
269	}
270
271	#[test]
272	fn create_empty_object_match_rule() {
273		let rule = ObjectMatchRule::builder().build();
274
275		assert_eq!(rule.states, StateSet::default());
276		assert_eq!(rule.attr, HashMap::new());
277		assert_eq!(rule.roles, Vec::new());
278		assert_eq!(rule.ifaces, InterfaceSet::default());
279		assert!(!rule.invert);
280	}
281
282	#[test]
283	fn create_object_match_rule() {
284		let rule = ObjectMatchRule::builder()
285			.states(StateSet::new(State::Active), MatchType::All)
286			.attributes(
287				[("name".to_string(), "value".to_string())].iter().cloned().collect(),
288				MatchType::Any,
289			)
290			.roles(&[Role::Alert], MatchType::All)
291			.interfaces([Interface::Action], MatchType::Any)
292			.invert(true)
293			.build();
294
295		assert_eq!(rule.states, StateSet::new(State::Active));
296		assert_eq!(
297			rule.attr,
298			[("name".to_string(), "value".to_string())].iter().cloned().collect()
299		);
300		assert_eq!(rule.roles, vec![Role::Alert]);
301		assert_eq!(rule.ifaces, InterfaceSet::new(Interface::Action));
302		assert!(rule.invert);
303	}
304}