atspi_common/
object_ref.rs

1use crate::AtspiError;
2use serde::{Deserialize, Serialize};
3use std::hash::{Hash, Hasher};
4use zbus_lockstep_macros::validate;
5use zbus_names::{BusName, UniqueName};
6use zvariant::{ObjectPath, Structure, Type};
7
8const NULL_PATH_STR: &str = "/org/a11y/atspi/null";
9const NULL_OBJECT_PATH: &ObjectPath<'static> =
10	&ObjectPath::from_static_str_unchecked(NULL_PATH_STR);
11
12#[cfg(test)]
13pub(crate) const TEST_OBJECT_BUS_NAME: &str = ":0.0";
14#[cfg(test)]
15pub(crate) const TEST_OBJECT_PATH_STR: &str = "/org/a11y/atspi/test/default";
16#[cfg(test)]
17pub(crate) const TEST_DEFAULT_OBJECT_REF: ObjectRef<'static> =
18	ObjectRef::from_static_str_unchecked(TEST_OBJECT_BUS_NAME, TEST_OBJECT_PATH_STR);
19
20/// A unique identifier for an object in the accessibility tree.
21///
22/// A ubiquitous type used to refer to an object in the accessibility tree.
23///
24/// In AT-SPI2, objects in the applications' UI object tree are uniquely identified
25/// using an application's bus name and object path. "(so)"
26///
27/// Emitted by `RemoveAccessible` and `Available`
28#[validate(signal: "Available")]
29#[derive(Clone, Debug, Eq, Type)]
30#[zvariant(signature = "(so)")]
31pub enum ObjectRef<'o> {
32	Null,
33	Owned { name: UniqueName<'static>, path: ObjectPath<'static> },
34	Borrowed { name: UniqueName<'o>, path: ObjectPath<'o> },
35}
36
37impl<'o> ObjectRef<'o> {
38	/// Create a new `ObjectRef::Borrowed` from a `UniqueName` and `ObjectPath`.
39	#[must_use]
40	pub fn new(name: UniqueName<'o>, path: ObjectPath<'o>) -> Self {
41		Self::new_borrowed(name, path)
42	}
43
44	/// Create a new, owned `ObjectRef`.
45	///
46	/// # Example
47	/// ```rust
48	/// use zbus::names::UniqueName;
49	/// use zbus::zvariant::ObjectPath;
50	/// use atspi_common::ObjectRef;
51	///
52	/// let name = UniqueName::from_static_str_unchecked(":1.23");
53	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
54	///
55	/// let object_ref = ObjectRef::new_owned(name, path);
56	/// # assert_eq!(object_ref.name_as_str(), Some(":1.23"));
57	/// # assert_eq!(object_ref.path_as_str(), "/org/a11y/example/path/007");
58	/// ```
59	pub fn new_owned<N, P>(name: N, path: P) -> ObjectRefOwned
60	where
61		N: Into<UniqueName<'static>>,
62		P: Into<ObjectPath<'static>>,
63	{
64		let name: UniqueName<'static> = name.into();
65		let path: ObjectPath<'static> = path.into();
66
67		ObjectRefOwned(ObjectRef::Owned { name, path })
68	}
69
70	/// Create a new, borrowed `ObjectRef`.
71	///
72	/// # Example
73	/// ```rust
74	/// use zbus::names::UniqueName;
75	/// use zbus::zvariant::ObjectPath;
76	/// use atspi_common::ObjectRef;
77	///
78	/// let name = UniqueName::from_static_str_unchecked(":1.23");
79	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
80	///
81	/// let object_ref = ObjectRef::new_borrowed(name, path);
82	/// # assert_eq!(object_ref.name_as_str(), Some(":1.23"));
83	/// # assert_eq!(object_ref.path_as_str(), "/org/a11y/example/path/007");
84	/// ```
85	pub fn new_borrowed<N, P>(name: N, path: P) -> ObjectRef<'o>
86	where
87		N: Into<UniqueName<'o>>,
88		P: Into<ObjectPath<'o>>,
89	{
90		let name: UniqueName<'o> = name.into();
91		let path: ObjectPath<'o> = path.into();
92
93		ObjectRef::Borrowed { name, path }
94	}
95
96	/// Create a new `ObjectRef`, from `BusName` and `ObjectPath`.
97	///
98	/// # Errors
99	/// Will fail if the `sender` is not a `UniqueName`.
100	pub fn try_from_bus_name_and_path(
101		sender: BusName<'o>,
102		path: ObjectPath<'o>,
103	) -> Result<Self, AtspiError> {
104		// Check whether `BusName` matches `UniqueName`
105		if let BusName::Unique(unique_sender) = sender {
106			Ok(ObjectRef::new(unique_sender, path))
107		} else {
108			Err(AtspiError::ParseError("Expected UniqueName"))
109		}
110	}
111
112	/// Create a new `ObjectRef`, unchecked.
113	///
114	/// # Safety
115	/// The caller must ensure that the strings are valid.
116	#[must_use]
117	pub const fn from_static_str_unchecked(name: &'static str, path: &'static str) -> Self {
118		let name = UniqueName::from_static_str_unchecked(name);
119		let path = ObjectPath::from_static_str_unchecked(path);
120
121		ObjectRef::Owned { name, path }
122	}
123
124	/// Returns `true` if the object reference is `Null`, otherwise returns `false`.
125	///
126	/// Toolkits may use the `Null` object reference to indicate that an object is not available or does not exist.
127	/// For example, when calling `Accessible::get_parent` on an object that has no parent,
128	/// it may return a `Null` object reference.
129	#[must_use]
130	pub fn is_null(&self) -> bool {
131		matches!(self, Self::Null)
132	}
133
134	/// Returns the name of the object reference.
135	/// If the object reference is `Null`, it returns `None`.
136	/// If the object reference is `Owned` or `Borrowed`, it returns the name.
137	///
138	/// # Example
139	/// ```rust
140	/// use zbus::names::UniqueName;
141	/// use zbus::zvariant::ObjectPath;
142	/// use atspi_common::ObjectRef;
143	///
144	/// let name = UniqueName::from_static_str_unchecked(":1.23");
145	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
146	/// let object_ref = ObjectRef::new_borrowed(name, path);
147	///
148	/// // Check the name of the object reference
149	/// assert!(object_ref.name().is_some());
150	/// assert_eq!(object_ref.name().unwrap().as_str(), ":1.23");
151	/// ```
152	#[must_use]
153	pub fn name(&self) -> Option<&UniqueName<'o>> {
154		match self {
155			Self::Owned { name: own_name, .. } => Some(own_name),
156			Self::Borrowed { name: borrow_name, .. } => Some(borrow_name),
157			Self::Null => None,
158		}
159	}
160
161	/// Returns the path of the object reference.\
162	///
163	/// # Example
164	/// ```rust
165	/// use zbus::names::UniqueName;
166	/// use zbus::zvariant::ObjectPath;
167	/// use atspi_common::ObjectRef;
168	///
169	/// let name = UniqueName::from_static_str_unchecked(":1.23");
170	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
171	/// let object_ref = ObjectRef::new_borrowed(name, path);
172	///
173	/// // Check the path of the object reference
174	/// assert_eq!(object_ref.path().as_str(), "/org/a11y/example/path/007");
175	/// ```
176	#[must_use]
177	pub fn path(&self) -> &ObjectPath<'o> {
178		match self {
179			Self::Owned { path: own_path, .. } => own_path,
180			Self::Borrowed { path: borrow_path, .. } => borrow_path,
181			Self::Null => NULL_OBJECT_PATH,
182		}
183	}
184
185	/// Converts the `ObjectRef` into an owned instance, consuming `self`.\
186	/// If the object reference is `Null`, it returns `ObjectRef::Null`.\
187	/// If the object reference is `Owned`, it returns the same `ObjectRef::Owned`.\
188	/// If the object reference is `Borrowed`, it converts the name and path to owned versions and returns `ObjectRef::Owned`.
189	///
190	/// # Extending lifetime 'magic' (from 'o -> 'static')
191	///
192	/// `ObjectRef<'_>` leans on the implementation of `UniqueName` and `ObjectPath` to
193	/// convert the inner types to `'static`.
194	/// These types have an `Inner` enum that can contain an `Owned`, `Borrowed`, or `Static` `Str` type.
195	/// The `Str`type is either a `&'static str` (static), `&str` (borrowed), or an `Arc<str>` (owned).
196	///
197	/// # Example
198	/// ```rust
199	/// use zbus::names::UniqueName;
200	/// use zbus::zvariant::ObjectPath;
201	/// use atspi_common::ObjectRef;
202	///
203	/// let name = UniqueName::from_static_str_unchecked(":1.23");
204	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
205	/// let object_ref = ObjectRef::new_borrowed(name, path);
206	///
207	/// // Check whether the object reference can be converted to an owned version
208	/// assert!(!object_ref.is_null());
209	/// let object_ref = object_ref.into_owned();
210	/// assert!(matches!(object_ref, ObjectRef::Owned { .. }));
211	/// ```
212	#[must_use]
213	pub fn into_owned(self) -> ObjectRef<'static> {
214		match self {
215			Self::Null => ObjectRef::Null,
216			Self::Owned { name, path } => ObjectRef::Owned { name, path },
217			Self::Borrowed { name, path } => {
218				ObjectRef::Owned { name: name.to_owned(), path: path.to_owned() }
219			}
220		}
221	}
222
223	/// Returns the name of the object reference as a string slice.
224	#[must_use]
225	pub fn name_as_str(&self) -> Option<&str> {
226		match self {
227			ObjectRef::Null => None,
228			ObjectRef::Owned { name, .. } | ObjectRef::Borrowed { name, .. } => Some(name.as_str()),
229		}
230	}
231
232	/// Returns the path of the object reference as a string slice.
233	#[must_use]
234	pub fn path_as_str(&self) -> &str {
235		match self {
236			ObjectRef::Null => NULL_PATH_STR,
237			ObjectRef::Owned { path, .. } | ObjectRef::Borrowed { path, .. } => path.as_str(),
238		}
239	}
240}
241
242// Event tests lean on the `Default` implementation of `ObjectRef`.
243// This is a workaround for the fact that `ObjectRef::Null` in
244// `#[cfg(test)]` context is inconvenient.
245// Events are guaranteed to have a non-null `ObjectRef` on their `item` field, because we receive signals over
246// regular (non-p2p) DBus. Which means the `Message` `Header` has valid `Sender` and `Path` fields which
247// are used to construct the `ObjectRef` from a `Message`.
248#[cfg(test)]
249impl Default for ObjectRef<'_> {
250	/// Returns a non-Null object reference. (test implementation)
251	fn default() -> Self {
252		TEST_DEFAULT_OBJECT_REF
253	}
254}
255
256#[cfg(not(test))]
257impl Default for ObjectRef<'_> {
258	/// Returns a `Null` object reference.
259	fn default() -> Self {
260		ObjectRef::Null
261	}
262}
263
264/// A wrapper around the static variant of `ObjectRef`.
265/// This is guaranteed to have a `'static` lifetime.
266#[validate(signal: "Available")]
267#[derive(Clone, Debug, Default, Eq, Type)]
268pub struct ObjectRefOwned(pub(crate) ObjectRef<'static>);
269
270impl From<ObjectRef<'_>> for ObjectRefOwned {
271	/// Convert an `ObjectRef<'_>` into an `ObjectRefOwned`.
272	///
273	/// # Extending lifetime 'magic' (from 'o -> 'static')
274	///
275	/// `ObjectRef<'_>` leans on the implementation of `UniqueName` and `ObjectPath` to
276	/// convert the inner types to `'static`.
277	/// These types have an `Inner` enum that can contain an `Owned`, `Borrowed`, or `Static` `Str` type.
278	/// The `Str`type is either a `&'static str` (static), `&str` (borrowed), or an `Arc<str>` (owned).
279	fn from(object_ref: ObjectRef<'_>) -> Self {
280		ObjectRefOwned(object_ref.into_owned())
281	}
282}
283
284impl ObjectRefOwned {
285	/// Create a new `ObjectRefOwned` from an `ObjectRef<'static>`.
286	#[must_use]
287	pub const fn new(object_ref: ObjectRef<'static>) -> Self {
288		Self(object_ref)
289	}
290
291	/// Create a new `ObjectRefOwned` from `&'static str` unchecked.
292	///
293	/// # Safety
294	/// The caller must ensure that the strings are valid.
295	#[must_use]
296	pub const fn from_static_str_unchecked(name: &'static str, path: &'static str) -> Self {
297		let name = UniqueName::from_static_str_unchecked(name);
298		let path = ObjectPath::from_static_str_unchecked(path);
299
300		ObjectRefOwned(ObjectRef::Owned { name, path })
301	}
302
303	/// Returns `true` if the object reference is `Null`, otherwise returns `false`.
304	#[must_use]
305	pub fn is_null(&self) -> bool {
306		matches!(self.0, ObjectRef::Null)
307	}
308
309	/// Returns the inner `ObjectRef`, consuming `self`.
310	#[must_use]
311	pub fn into_inner(self) -> ObjectRef<'static> {
312		self.0
313	}
314
315	/// Returns the name of the object reference.
316	/// If the object reference is `Null`, it returns `None`.
317	/// If the object reference is `Owned` or `Borrowed`, it returns the name.
318	///
319	/// # Example
320	/// ```rust
321	/// use zbus::names::UniqueName;
322	/// use zbus::zvariant::ObjectPath;
323	/// use atspi_common::ObjectRef;
324	///
325	/// let name = UniqueName::from_static_str_unchecked(":1.23");
326	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
327	/// let object_ref = ObjectRef::new_borrowed(name, path);
328	///
329	/// // Check the name of the object reference
330	/// assert!(object_ref.name().is_some());
331	/// assert_eq!(object_ref.name_as_str().unwrap(), ":1.23");
332	/// ```
333	#[must_use]
334	pub fn name(&self) -> Option<&UniqueName<'static>> {
335		match &self.0 {
336			ObjectRef::Owned { name, .. } | ObjectRef::Borrowed { name, .. } => Some(name),
337			ObjectRef::Null => None,
338		}
339	}
340
341	/// Returns the path of the object reference.\
342	/// If the object reference is `Null`, it returns the null-path.
343	///
344	/// # Example
345	/// ```rust
346	/// use zbus::names::UniqueName;
347	/// use zbus::zvariant::ObjectPath;
348	/// use atspi_common::ObjectRef;
349	///
350	/// let name = UniqueName::from_static_str_unchecked(":1.23");
351	/// let path = ObjectPath::from_static_str_unchecked("/org/a11y/example/path/007");
352	/// let object_ref = ObjectRef::new_borrowed(name, path);
353	///
354	/// assert_eq!(object_ref.path_as_str(), "/org/a11y/example/path/007");
355	/// ```
356	#[must_use]
357	pub fn path(&self) -> &ObjectPath<'static> {
358		match &self.0 {
359			ObjectRef::Owned { path, .. } | ObjectRef::Borrowed { path, .. } => path,
360			ObjectRef::Null => NULL_OBJECT_PATH,
361		}
362	}
363
364	/// Returns the name of the object reference as a string slice.
365	#[must_use]
366	pub fn name_as_str(&self) -> Option<&str> {
367		match &self.0 {
368			ObjectRef::Null => None,
369			ObjectRef::Owned { name, .. } | ObjectRef::Borrowed { name, .. } => Some(name.as_str()),
370		}
371	}
372
373	/// Returns the path of the object reference as a string slice.
374	#[must_use]
375	pub fn path_as_str(&self) -> &str {
376		match &self.0 {
377			ObjectRef::Null => NULL_PATH_STR,
378			ObjectRef::Owned { path, .. } | ObjectRef::Borrowed { path, .. } => path.as_str(),
379		}
380	}
381}
382
383impl Serialize for ObjectRef<'_> {
384	/// `ObjectRef`'s wire format is `(&str, ObjectPath)`.
385	/// The `Null` variant, the "Null object", is serialized as `("", ObjectPath("/org/a11y/atspi/null"))`.
386	/// Both `Owned` and `Borrowed` variants are serialized as `(&str, ObjectPath)` with the object's\
387	/// unique name and path.
388	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
389	where
390		S: serde::Serializer,
391	{
392		match &self {
393			ObjectRef::Null => ("", NULL_OBJECT_PATH).serialize(serializer),
394			ObjectRef::Owned { name, path } | ObjectRef::Borrowed { name, path } => {
395				(name.as_str(), path).serialize(serializer)
396			}
397		}
398	}
399}
400
401impl Serialize for ObjectRefOwned {
402	/// `ObjectRefOwned` is serialized as the inner `ObjectRef`.
403	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
404	where
405		S: serde::Serializer,
406	{
407		self.0.serialize(serializer)
408	}
409}
410
411impl<'de: 'o, 'o> Deserialize<'de> for ObjectRef<'o> {
412	/// `ObjectRef`'s wire format is `(&str, ObjectPath)`.
413	/// An empty `&str` with a "/org/a11y/atspi/null" path is considered a `Null` object,
414	/// this is deserialized as `ObjectRef::Null`.\
415	/// Any other valid `(&str, ObjectPath)`  will deserialize into `ObjectRef::Borrowed`.
416	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
417	where
418		D: serde::Deserializer<'de>,
419	{
420		struct ObjectRefVisitor;
421
422		impl<'de> serde::de::Visitor<'de> for ObjectRefVisitor {
423			type Value = ObjectRef<'de>;
424
425			fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
426				formatter.write_str("a tuple of (&str, ObjectPath)")
427			}
428
429			fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
430			where
431				A: serde::de::SeqAccess<'de>,
432			{
433				let name: &str = seq
434					.next_element()?
435					.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
436				let path: ObjectPath<'de> = seq
437					.next_element()?
438					.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
439
440				if path == *NULL_OBJECT_PATH {
441					Ok(ObjectRef::Null)
442				} else {
443					assert!(
444						!name.is_empty(),
445						"A non-null ObjectRef requires a name and a path but got: (\"\", {path})"
446					);
447					Ok(ObjectRef::Borrowed {
448						name: UniqueName::try_from(name).map_err(serde::de::Error::custom)?,
449						path,
450					})
451				}
452			}
453		}
454
455		deserializer.deserialize_tuple(2, ObjectRefVisitor)
456	}
457}
458
459impl<'de> Deserialize<'de> for ObjectRefOwned {
460	/// `ObjectRefOwned` is deserialized as "Owned" variant `ObjectRef<'static>`.
461	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
462	where
463		D: serde::Deserializer<'de>,
464	{
465		let object_ref: ObjectRef<'_> = Deserialize::deserialize(deserializer)?;
466		Ok(object_ref.into())
467	}
468}
469
470impl PartialEq for ObjectRef<'_> {
471	fn eq(&self, other: &Self) -> bool {
472		match (self, other) {
473			// Match order is relevant here. Null == Null, but Null != any other object.
474			(ObjectRef::Null, ObjectRef::Null) => true,
475			(ObjectRef::Null, _) | (_, ObjectRef::Null) => false,
476			_ => self.name() == other.name() && self.path() == other.path(),
477		}
478	}
479}
480
481// `Hash` requires that hashes are equal if values are equal.
482// If a == b, then a.hash() == b.hash() must hold true.
483//
484// Because PartialEq treats Owned and Borrowed variants with identical (name, path) as equal,
485// we must implement Hash manually to ignore the variant discriminant and preserve hash/equality
486// consistency.
487impl Hash for ObjectRef<'_> {
488	fn hash<H: Hasher>(&self, state: &mut H) {
489		match self {
490			ObjectRef::Null => {
491				// Hashing a Null object reference.
492				"Null".hash(state);
493			}
494			ObjectRef::Owned { name, path } | ObjectRef::Borrowed { name, path } => {
495				name.as_str().hash(state);
496				path.as_str().hash(state);
497			}
498		}
499	}
500}
501
502impl Hash for ObjectRefOwned {
503	fn hash<H: Hasher>(&self, state: &mut H) {
504		self.0.hash(state);
505	}
506}
507
508impl PartialEq for ObjectRefOwned {
509	fn eq(&self, other: &Self) -> bool {
510		self.0 == other.0
511	}
512}
513
514impl PartialEq<ObjectRef<'_>> for ObjectRefOwned {
515	fn eq(&self, other: &ObjectRef<'_>) -> bool {
516		self.0 == *other
517	}
518}
519
520impl PartialEq<ObjectRefOwned> for ObjectRef<'_> {
521	fn eq(&self, other: &ObjectRefOwned) -> bool {
522		*self == other.0
523	}
524}
525
526#[cfg(feature = "zbus")]
527impl<'m: 'o, 'o> TryFrom<&'m zbus::message::Header<'_>> for ObjectRef<'o> {
528	type Error = crate::AtspiError;
529
530	// Construct an ObjectRef<'o> by reborrowing from the Header’s data.
531	// 'm: 'o, 'm outlives 'o, so the references returned by this function
532	// are guaranteed to be valid for the lifetime of the header.
533
534	/// Construct an `ObjectRef` from a `zbus::message::Header`.
535	///
536	/// # Header fields
537	///
538	/// `Path` is a mandatory field on method calls and signals,
539	/// `Sender` is an optional field, see:
540	/// [DBus specification - header fields](<https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields>)).,
541	///
542	/// ```quote
543	///  On a message bus, this header field is controlled by the message bus,
544	///  so it is as reliable and trustworthy as the message bus itself.
545	///  Otherwise, (eg. P2P) this header field is controlled by the message sender,
546	///  unless there is out-of-band information that indicates otherwise.
547	/// ```
548	///
549	/// While unlikely, it is possible that `Sender` or `Path` are not set on the header.
550	/// This could happen if the server implementation does not set these fields for any reason.
551	///
552	/// # Errors
553	/// Will return an `AtspiError::ParseError` if the header does not contain a valid path or sender.
554	fn try_from(header: &'m zbus::message::Header) -> Result<Self, Self::Error> {
555		let path = header.path().ok_or(crate::AtspiError::MissingPath)?;
556		let name = header.sender().ok_or(crate::AtspiError::MissingName)?;
557
558		Ok(ObjectRef::Borrowed { name: name.clone(), path: path.clone() })
559	}
560}
561
562#[cfg(feature = "zbus")]
563impl<'m> TryFrom<&'m zbus::message::Header<'_>> for ObjectRefOwned {
564	type Error = crate::AtspiError;
565
566	/// Construct an `ObjectRefOwned` from a `zbus::message::Header`.
567	fn try_from(header: &'m zbus::message::Header) -> Result<Self, Self::Error> {
568		let path = header.path().ok_or(crate::AtspiError::MissingPath)?;
569		let name = header.sender().ok_or(crate::AtspiError::MissingName)?;
570
571		let object_ref =
572			ObjectRef::Owned { name: name.clone().into_owned(), path: path.clone().into_owned() };
573		Ok(ObjectRefOwned(object_ref))
574	}
575}
576
577impl<'v> TryFrom<zvariant::Value<'v>> for ObjectRef<'v> {
578	type Error = zvariant::Error;
579
580	fn try_from(value: zvariant::Value<'v>) -> Result<Self, Self::Error> {
581		// Relies on the generic `Value` to tuple conversion `(UniqueName, ObjectPath)`.
582		let (name, path): (UniqueName, ObjectPath) = value.try_into()?;
583		Ok(ObjectRef::new_borrowed(name, path))
584	}
585}
586
587impl<'v> TryFrom<zvariant::Value<'v>> for ObjectRefOwned {
588	type Error = zvariant::Error;
589
590	fn try_from(value: zvariant::Value<'v>) -> Result<Self, Self::Error> {
591		// Relies on the generic `Value` to tuple conversion `(UniqueName, ObjectPath)`.
592		let (name, path): (UniqueName, ObjectPath) = value.try_into()?;
593		Ok(ObjectRef::new_borrowed(name, path).into())
594	}
595}
596
597impl TryFrom<zvariant::OwnedValue> for ObjectRef<'static> {
598	type Error = zvariant::Error;
599	fn try_from(value: zvariant::OwnedValue) -> Result<Self, Self::Error> {
600		let (name, path): (UniqueName<'static>, ObjectPath<'static>) = value.try_into()?;
601		Ok(ObjectRef::Owned { name, path })
602	}
603}
604
605impl TryFrom<zvariant::OwnedValue> for ObjectRefOwned {
606	type Error = zvariant::Error;
607	fn try_from(value: zvariant::OwnedValue) -> Result<Self, Self::Error> {
608		let (name, path): (UniqueName<'static>, ObjectPath<'static>) = value.try_into()?;
609		let obj = ObjectRef::Owned { name, path };
610		Ok(ObjectRefOwned(obj))
611	}
612}
613
614impl<'reference: 'structure, 'object: 'structure, 'structure> From<&'reference ObjectRef<'object>>
615	for zvariant::Structure<'structure>
616{
617	fn from(obj: &'reference ObjectRef<'object>) -> Self {
618		match obj {
619			ObjectRef::Null => ("", NULL_OBJECT_PATH).into(),
620			ObjectRef::Borrowed { name, path } => Structure::from((name.clone(), path)),
621			ObjectRef::Owned { name, path } => Structure::from((name.as_str(), path.as_ref())),
622		}
623	}
624}
625
626impl<'o> From<ObjectRef<'o>> for zvariant::Structure<'o> {
627	fn from(obj: ObjectRef<'o>) -> Self {
628		match obj {
629			ObjectRef::Null => Structure::from(("", NULL_OBJECT_PATH)),
630			ObjectRef::Borrowed { name, path } | ObjectRef::Owned { name, path } => {
631				Structure::from((name, path))
632			}
633		}
634	}
635}
636
637impl From<ObjectRefOwned> for zvariant::Structure<'_> {
638	fn from(obj: ObjectRefOwned) -> Self {
639		let object_ref = obj.into_inner();
640		object_ref.into()
641	}
642}
643
644#[cfg(test)]
645mod tests {
646	use std::hash::{DefaultHasher, Hash, Hasher};
647
648	use super::ObjectRef;
649	use crate::object_ref::{NULL_OBJECT_PATH, NULL_PATH_STR};
650	use zbus::zvariant;
651	use zbus::{names::UniqueName, zvariant::ObjectPath};
652	use zvariant::{serialized::Context, to_bytes, OwnedValue, Value, LE};
653
654	const TEST_OBJECT_PATH: &str = "/org/a11y/atspi/path/007";
655
656	#[test]
657	fn owned_object_ref_creation() {
658		let name = UniqueName::from_static_str_unchecked(":1.23");
659		let path = ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH);
660
661		let object_ref = ObjectRef::new_owned(name, path);
662
663		assert_eq!(object_ref.name_as_str(), Some(":1.23"));
664		assert_eq!(object_ref.path_as_str(), TEST_OBJECT_PATH);
665	}
666
667	#[test]
668	fn borrowed_object_ref_creation() {
669		let object_ref = ObjectRef::new_borrowed(
670			UniqueName::from_static_str(":1.23").unwrap(),
671			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
672		);
673		assert_eq!(object_ref.name_as_str(), Some(":1.23"));
674		assert_eq!(object_ref.path_as_str(), TEST_OBJECT_PATH);
675	}
676
677	#[test]
678	fn null_object_ref() {
679		let null_object_ref: ObjectRef = ObjectRef::Null;
680		assert!(null_object_ref.is_null());
681		assert!(null_object_ref.name().is_none());
682		assert_eq!(null_object_ref.path(), NULL_OBJECT_PATH);
683	}
684
685	#[test]
686	fn object_ref_into_owned() {
687		let borrowed_object_ref = ObjectRef::new_borrowed(
688			UniqueName::from_static_str(":1.23").unwrap(),
689			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
690		);
691		let owned_object_ref = borrowed_object_ref.into_owned();
692		assert!(matches!(owned_object_ref, ObjectRef::Owned { .. }));
693		assert_eq!(owned_object_ref.name_as_str(), Some(":1.23"));
694		assert_eq!(owned_object_ref.path_as_str(), TEST_OBJECT_PATH);
695	}
696
697	#[test]
698	fn object_ref_into_name_and_path() {
699		let object_ref = ObjectRef::new_borrowed(
700			UniqueName::from_static_str(":1.23").unwrap(),
701			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
702		);
703		let name = object_ref.name().unwrap();
704		let path = object_ref.path();
705		assert_eq!(name.as_str(), ":1.23");
706		assert_eq!(path.as_str(), TEST_OBJECT_PATH);
707	}
708
709	#[test]
710	fn serialization_null_object_ref() {
711		let null_object_ref: ObjectRef = ObjectRef::Null;
712		assert!(null_object_ref.is_null());
713
714		let ctxt = Context::new_dbus(LE, 0);
715		let encoded = to_bytes(ctxt, &null_object_ref).unwrap();
716
717		let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
718
719		assert!(obj.is_null());
720		assert!(obj.name().is_none());
721		assert_eq!(obj.path(), NULL_OBJECT_PATH);
722	}
723
724	#[test]
725	fn serialization_owned_object_ref() {
726		let name = UniqueName::from_static_str_unchecked(":1.23");
727		let path = ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH);
728
729		let object_ref = ObjectRef::new_owned(name, path);
730
731		let ctxt = Context::new_dbus(LE, 0);
732		let encoded = to_bytes(ctxt, &object_ref).unwrap();
733
734		let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
735
736		// Deserialization alwayys results in a borrowed object reference.
737		// On the wire the distinction between owned and borrowed is not preserved.
738		// As borrowed is the cheaper option, we always deserialize to that.
739		assert!(matches!(obj, ObjectRef::Borrowed { .. }));
740		assert_eq!(obj.name().unwrap().as_str(), ":1.23");
741		assert_eq!(obj.path_as_str(), TEST_OBJECT_PATH);
742	}
743
744	#[test]
745	fn serialization_borrowed_object_ref() {
746		let object_ref = ObjectRef::new_borrowed(
747			UniqueName::from_static_str(":1.23").unwrap(),
748			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
749		);
750
751		let ctxt = Context::new_dbus(LE, 0);
752		let encoded = to_bytes(ctxt, &object_ref).unwrap();
753
754		let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
755		assert!(matches!(obj, ObjectRef::Borrowed { .. }));
756
757		assert_eq!(obj.name().unwrap().as_str(), ":1.23");
758		assert_eq!(obj.path_as_str(), TEST_OBJECT_PATH);
759	}
760
761	#[test]
762	fn object_ref_equality() {
763		let object_ref1 = ObjectRef::new_borrowed(
764			UniqueName::from_static_str(":1.23").unwrap(),
765			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
766		);
767		let object_ref2 = ObjectRef::new_borrowed(
768			UniqueName::from_static_str(":1.23").unwrap(),
769			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
770		);
771		assert_eq!(object_ref1, object_ref2);
772
773		let object_ref3 = ObjectRef::new_borrowed(
774			UniqueName::from_static_str(":1.24").unwrap(),
775			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
776		);
777		assert_ne!(object_ref1, object_ref3);
778
779		let object_ref4 = ObjectRef::new_owned(
780			UniqueName::from_static_str_unchecked(":1.23"),
781			ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
782		);
783		assert_eq!(object_ref1, object_ref4);
784
785		let null_object_ref: ObjectRef = ObjectRef::Null;
786		assert_ne!(object_ref1, null_object_ref);
787		assert_ne!(null_object_ref, object_ref1);
788
789		let null_object_ref2: ObjectRef = ObjectRef::Null;
790		assert_eq!(null_object_ref, null_object_ref2);
791	}
792
793	#[test]
794	fn try_from_value_for_objectref() {
795		let name = UniqueName::from_static_str_unchecked(":0.0");
796		let path = ObjectPath::from_static_str_unchecked("/org/a11y/atspi/testpath");
797
798		let objref = ObjectRef::new_borrowed(name, path);
799		let value: Value = objref.into();
800
801		let objref_2: ObjectRef = value.try_into().unwrap();
802
803		assert_eq!(objref_2.name().unwrap().as_str(), ":0.0");
804		assert_eq!(objref_2.path_as_str(), "/org/a11y/atspi/testpath");
805	}
806
807	#[test]
808	fn try_from_owned_value_for_objectref() {
809		let name = UniqueName::from_static_str_unchecked(":0.0");
810		let path = ObjectPath::from_static_str_unchecked("/org/a11y/atspi/testpath");
811
812		let objref = ObjectRef::new_borrowed(name, path);
813
814		let value: Value = objref.into();
815		let value: OwnedValue = value.try_into().unwrap();
816		let objref_2: ObjectRef = value.try_into().unwrap();
817
818		assert_eq!(objref_2.name_as_str(), Some(":0.0"));
819		assert_eq!(objref_2.path_as_str(), "/org/a11y/atspi/testpath");
820	}
821
822	// Must fail test:
823
824	#[test]
825	fn must_fail_test_try_from_invalid_value_for_object_ref() {
826		let value = zvariant::Value::from((42, true));
827		let obj: Result<ObjectRef, _> = value.try_into();
828		assert!(obj.is_err());
829	}
830
831	#[test]
832	fn hash_and_object_coherence() {
833		let name = UniqueName::from_static_str_unchecked(":1.23");
834		let path = ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH);
835
836		let object_ref1 = ObjectRef::new_borrowed(&name, &path);
837		let object_ref2 = ObjectRef::new_borrowed(name, path);
838
839		let mut hasher1 = DefaultHasher::new();
840		let mut hasher2 = DefaultHasher::new();
841		assert_eq!(object_ref1, object_ref2);
842		object_ref1.hash(&mut hasher1);
843		object_ref2.hash(&mut hasher2);
844		assert_eq!(hasher1.finish(), hasher2.finish());
845	}
846
847	#[test]
848	#[should_panic(expected = "assertion failed: matches!(obj, ObjectRef::Borrowed { .. })")]
849	fn valid_name_null_path_object_ref() {
850		let object_ref = ObjectRef::from_static_str_unchecked("1.23", NULL_PATH_STR);
851
852		let ctxt = Context::new_dbus(LE, 0);
853		let encoded = to_bytes(ctxt, &object_ref).unwrap();
854
855		let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
856		assert!(matches!(obj, ObjectRef::Borrowed { .. }));
857	}
858
859	// Check that the Deserialize implementation correctly panics
860	#[test]
861	#[should_panic(
862		expected = r#"A non-null ObjectRef requires a name and a path but got: ("", /org/a11y/atspi/path/007)"#
863	)]
864	fn empty_name_valid_path_object_ref() {
865		let object_ref = ObjectRef::from_static_str_unchecked("", TEST_OBJECT_PATH);
866
867		let ctxt = Context::new_dbus(LE, 0);
868		let encoded = to_bytes(ctxt, &object_ref).unwrap();
869
870		let (_obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
871	}
872}