use crate::AtspiError;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use zbus_lockstep_macros::validate;
use zbus_names::{BusName, UniqueName};
use zvariant::{ObjectPath, Structure, Type};
const NULL_PATH_STR: &str = "/org/a11y/atspi/null";
const NULL_OBJECT_PATH: &ObjectPath<'static> =
&ObjectPath::from_static_str_unchecked(NULL_PATH_STR);
#[cfg(test)]
pub(crate) const TEST_OBJECT_BUS_NAME: &str = ":0.0";
#[cfg(test)]
pub(crate) const TEST_OBJECT_PATH_STR: &str = "/org/a11y/atspi/test/default";
#[cfg(test)]
pub(crate) const TEST_DEFAULT_OBJECT_REF: ObjectRef<'static> =
ObjectRef::from_static_str_unchecked(TEST_OBJECT_BUS_NAME, TEST_OBJECT_PATH_STR);
#[validate(signal: "Available")]
#[derive(Clone, Debug, Eq, Type)]
#[zvariant(signature = "(so)")]
pub enum ObjectRef<'o> {
Null,
Owned { name: UniqueName<'static>, path: ObjectPath<'static> },
Borrowed { name: UniqueName<'o>, path: ObjectPath<'o> },
}
impl<'o> ObjectRef<'o> {
#[must_use]
pub fn new(name: UniqueName<'o>, path: ObjectPath<'o>) -> Self {
Self::new_borrowed(name, path)
}
pub fn new_owned<N, P>(name: N, path: P) -> ObjectRefOwned
where
N: Into<UniqueName<'static>>,
P: Into<ObjectPath<'static>>,
{
let name: UniqueName<'static> = name.into();
let path: ObjectPath<'static> = path.into();
ObjectRefOwned(ObjectRef::Owned { name, path })
}
pub fn new_borrowed<N, P>(name: N, path: P) -> ObjectRef<'o>
where
N: Into<UniqueName<'o>>,
P: Into<ObjectPath<'o>>,
{
let name: UniqueName<'o> = name.into();
let path: ObjectPath<'o> = path.into();
ObjectRef::Borrowed { name, path }
}
pub fn try_from_bus_name_and_path(
sender: BusName<'o>,
path: ObjectPath<'o>,
) -> Result<Self, AtspiError> {
if let BusName::Unique(unique_sender) = sender {
Ok(ObjectRef::new(unique_sender, path))
} else {
Err(AtspiError::ParseError("Expected UniqueName"))
}
}
#[must_use]
pub const fn from_static_str_unchecked(name: &'static str, path: &'static str) -> Self {
let name = UniqueName::from_static_str_unchecked(name);
let path = ObjectPath::from_static_str_unchecked(path);
ObjectRef::Owned { name, path }
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub fn name(&self) -> Option<&UniqueName<'o>> {
match self {
Self::Owned { name: own_name, .. } => Some(own_name),
Self::Borrowed { name: borrow_name, .. } => Some(borrow_name),
Self::Null => None,
}
}
#[must_use]
pub fn path(&self) -> &ObjectPath<'o> {
match self {
Self::Owned { path: own_path, .. } => own_path,
Self::Borrowed { path: borrow_path, .. } => borrow_path,
Self::Null => NULL_OBJECT_PATH,
}
}
#[must_use]
pub fn into_owned(self) -> ObjectRef<'static> {
match self {
Self::Null => ObjectRef::Null,
Self::Owned { name, path } => ObjectRef::Owned { name, path },
Self::Borrowed { name, path } => {
ObjectRef::Owned { name: name.to_owned(), path: path.to_owned() }
}
}
}
#[must_use]
pub fn name_as_str(&self) -> Option<&str> {
match self {
ObjectRef::Null => None,
ObjectRef::Owned { name, .. } | ObjectRef::Borrowed { name, .. } => Some(name.as_str()),
}
}
#[must_use]
pub fn path_as_str(&self) -> &str {
match self {
ObjectRef::Null => NULL_PATH_STR,
ObjectRef::Owned { path, .. } | ObjectRef::Borrowed { path, .. } => path.as_str(),
}
}
}
#[cfg(test)]
impl Default for ObjectRef<'_> {
fn default() -> Self {
TEST_DEFAULT_OBJECT_REF
}
}
#[cfg(not(test))]
impl Default for ObjectRef<'_> {
fn default() -> Self {
ObjectRef::Null
}
}
#[validate(signal: "Available")]
#[derive(Clone, Debug, Default, Eq, Type)]
pub struct ObjectRefOwned(pub(crate) ObjectRef<'static>);
impl From<ObjectRef<'_>> for ObjectRefOwned {
fn from(object_ref: ObjectRef<'_>) -> Self {
ObjectRefOwned(object_ref.into_owned())
}
}
impl ObjectRefOwned {
#[must_use]
pub const fn new(object_ref: ObjectRef<'static>) -> Self {
Self(object_ref)
}
#[must_use]
pub const fn from_static_str_unchecked(name: &'static str, path: &'static str) -> Self {
let name = UniqueName::from_static_str_unchecked(name);
let path = ObjectPath::from_static_str_unchecked(path);
ObjectRefOwned(ObjectRef::Owned { name, path })
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self.0, ObjectRef::Null)
}
#[must_use]
pub fn into_inner(self) -> ObjectRef<'static> {
self.0
}
#[must_use]
pub fn name(&self) -> Option<&UniqueName<'static>> {
match &self.0 {
ObjectRef::Owned { name, .. } | ObjectRef::Borrowed { name, .. } => Some(name),
ObjectRef::Null => None,
}
}
#[must_use]
pub fn path(&self) -> &ObjectPath<'static> {
match &self.0 {
ObjectRef::Owned { path, .. } | ObjectRef::Borrowed { path, .. } => path,
ObjectRef::Null => NULL_OBJECT_PATH,
}
}
#[must_use]
pub fn name_as_str(&self) -> Option<&str> {
match &self.0 {
ObjectRef::Null => None,
ObjectRef::Owned { name, .. } | ObjectRef::Borrowed { name, .. } => Some(name.as_str()),
}
}
#[must_use]
pub fn path_as_str(&self) -> &str {
match &self.0 {
ObjectRef::Null => NULL_PATH_STR,
ObjectRef::Owned { path, .. } | ObjectRef::Borrowed { path, .. } => path.as_str(),
}
}
}
impl Serialize for ObjectRef<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match &self {
ObjectRef::Null => ("", NULL_OBJECT_PATH).serialize(serializer),
ObjectRef::Owned { name, path } | ObjectRef::Borrowed { name, path } => {
(name.as_str(), path).serialize(serializer)
}
}
}
}
impl Serialize for ObjectRefOwned {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de: 'o, 'o> Deserialize<'de> for ObjectRef<'o> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ObjectRefVisitor;
impl<'de> serde::de::Visitor<'de> for ObjectRefVisitor {
type Value = ObjectRef<'de>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a tuple of (&str, ObjectPath)")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let name: &str = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let path: ObjectPath<'de> = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
if path == *NULL_OBJECT_PATH {
Ok(ObjectRef::Null)
} else {
assert!(
!name.is_empty(),
"A non-null ObjectRef requires a name and a path but got: (\"\", {path})"
);
Ok(ObjectRef::Borrowed {
name: UniqueName::try_from(name).map_err(serde::de::Error::custom)?,
path,
})
}
}
}
deserializer.deserialize_tuple(2, ObjectRefVisitor)
}
}
impl<'de> Deserialize<'de> for ObjectRefOwned {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let object_ref: ObjectRef<'_> = Deserialize::deserialize(deserializer)?;
Ok(object_ref.into())
}
}
impl PartialEq for ObjectRef<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ObjectRef::Null, ObjectRef::Null) => true,
(ObjectRef::Null, _) | (_, ObjectRef::Null) => false,
_ => self.name() == other.name() && self.path() == other.path(),
}
}
}
impl Hash for ObjectRef<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
ObjectRef::Null => {
"Null".hash(state);
}
ObjectRef::Owned { name, path } | ObjectRef::Borrowed { name, path } => {
name.as_str().hash(state);
path.as_str().hash(state);
}
}
}
}
impl Hash for ObjectRefOwned {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl PartialEq for ObjectRefOwned {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl PartialEq<ObjectRef<'_>> for ObjectRefOwned {
fn eq(&self, other: &ObjectRef<'_>) -> bool {
self.0 == *other
}
}
impl PartialEq<ObjectRefOwned> for ObjectRef<'_> {
fn eq(&self, other: &ObjectRefOwned) -> bool {
*self == other.0
}
}
#[cfg(feature = "zbus")]
impl<'m: 'o, 'o> TryFrom<&'m zbus::message::Header<'_>> for ObjectRef<'o> {
type Error = crate::AtspiError;
fn try_from(header: &'m zbus::message::Header) -> Result<Self, Self::Error> {
let path = header.path().ok_or(crate::AtspiError::MissingPath)?;
let name = header.sender().ok_or(crate::AtspiError::MissingName)?;
Ok(ObjectRef::Borrowed { name: name.clone(), path: path.clone() })
}
}
#[cfg(feature = "zbus")]
impl<'m> TryFrom<&'m zbus::message::Header<'_>> for ObjectRefOwned {
type Error = crate::AtspiError;
fn try_from(header: &'m zbus::message::Header) -> Result<Self, Self::Error> {
let path = header.path().ok_or(crate::AtspiError::MissingPath)?;
let name = header.sender().ok_or(crate::AtspiError::MissingName)?;
let object_ref =
ObjectRef::Owned { name: name.clone().into_owned(), path: path.clone().into_owned() };
Ok(ObjectRefOwned(object_ref))
}
}
impl<'v> TryFrom<zvariant::Value<'v>> for ObjectRef<'v> {
type Error = zvariant::Error;
fn try_from(value: zvariant::Value<'v>) -> Result<Self, Self::Error> {
let (name, path): (UniqueName, ObjectPath) = value.try_into()?;
Ok(ObjectRef::new_borrowed(name, path))
}
}
impl<'v> TryFrom<zvariant::Value<'v>> for ObjectRefOwned {
type Error = zvariant::Error;
fn try_from(value: zvariant::Value<'v>) -> Result<Self, Self::Error> {
let (name, path): (UniqueName, ObjectPath) = value.try_into()?;
Ok(ObjectRef::new_borrowed(name, path).into())
}
}
impl TryFrom<zvariant::OwnedValue> for ObjectRef<'static> {
type Error = zvariant::Error;
fn try_from(value: zvariant::OwnedValue) -> Result<Self, Self::Error> {
let (name, path): (UniqueName<'static>, ObjectPath<'static>) = value.try_into()?;
Ok(ObjectRef::Owned { name, path })
}
}
impl TryFrom<zvariant::OwnedValue> for ObjectRefOwned {
type Error = zvariant::Error;
fn try_from(value: zvariant::OwnedValue) -> Result<Self, Self::Error> {
let (name, path): (UniqueName<'static>, ObjectPath<'static>) = value.try_into()?;
let obj = ObjectRef::Owned { name, path };
Ok(ObjectRefOwned(obj))
}
}
impl<'reference: 'structure, 'object: 'structure, 'structure> From<&'reference ObjectRef<'object>>
for zvariant::Structure<'structure>
{
fn from(obj: &'reference ObjectRef<'object>) -> Self {
match obj {
ObjectRef::Null => ("", NULL_OBJECT_PATH).into(),
ObjectRef::Borrowed { name, path } => Structure::from((name.clone(), path)),
ObjectRef::Owned { name, path } => Structure::from((name.as_str(), path.as_ref())),
}
}
}
impl<'o> From<ObjectRef<'o>> for zvariant::Structure<'o> {
fn from(obj: ObjectRef<'o>) -> Self {
match obj {
ObjectRef::Null => Structure::from(("", NULL_OBJECT_PATH)),
ObjectRef::Borrowed { name, path } | ObjectRef::Owned { name, path } => {
Structure::from((name, path))
}
}
}
}
impl From<ObjectRefOwned> for zvariant::Structure<'_> {
fn from(obj: ObjectRefOwned) -> Self {
let object_ref = obj.into_inner();
object_ref.into()
}
}
#[cfg(test)]
mod tests {
use std::hash::{DefaultHasher, Hash, Hasher};
use super::ObjectRef;
use crate::object_ref::{NULL_OBJECT_PATH, NULL_PATH_STR};
use zbus::zvariant;
use zbus::{names::UniqueName, zvariant::ObjectPath};
use zvariant::{serialized::Context, to_bytes, OwnedValue, Value, LE};
const TEST_OBJECT_PATH: &str = "/org/a11y/atspi/path/007";
#[test]
fn owned_object_ref_creation() {
let name = UniqueName::from_static_str_unchecked(":1.23");
let path = ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH);
let object_ref = ObjectRef::new_owned(name, path);
assert_eq!(object_ref.name_as_str(), Some(":1.23"));
assert_eq!(object_ref.path_as_str(), TEST_OBJECT_PATH);
}
#[test]
fn borrowed_object_ref_creation() {
let object_ref = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.23").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
assert_eq!(object_ref.name_as_str(), Some(":1.23"));
assert_eq!(object_ref.path_as_str(), TEST_OBJECT_PATH);
}
#[test]
fn null_object_ref() {
let null_object_ref: ObjectRef = ObjectRef::Null;
assert!(null_object_ref.is_null());
assert!(null_object_ref.name().is_none());
assert_eq!(null_object_ref.path(), NULL_OBJECT_PATH);
}
#[test]
fn object_ref_into_owned() {
let borrowed_object_ref = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.23").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
let owned_object_ref = borrowed_object_ref.into_owned();
assert!(matches!(owned_object_ref, ObjectRef::Owned { .. }));
assert_eq!(owned_object_ref.name_as_str(), Some(":1.23"));
assert_eq!(owned_object_ref.path_as_str(), TEST_OBJECT_PATH);
}
#[test]
fn object_ref_into_name_and_path() {
let object_ref = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.23").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
let name = object_ref.name().unwrap();
let path = object_ref.path();
assert_eq!(name.as_str(), ":1.23");
assert_eq!(path.as_str(), TEST_OBJECT_PATH);
}
#[test]
fn serialization_null_object_ref() {
let null_object_ref: ObjectRef = ObjectRef::Null;
assert!(null_object_ref.is_null());
let ctxt = Context::new_dbus(LE, 0);
let encoded = to_bytes(ctxt, &null_object_ref).unwrap();
let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
assert!(obj.is_null());
assert!(obj.name().is_none());
assert_eq!(obj.path(), NULL_OBJECT_PATH);
}
#[test]
fn serialization_owned_object_ref() {
let name = UniqueName::from_static_str_unchecked(":1.23");
let path = ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH);
let object_ref = ObjectRef::new_owned(name, path);
let ctxt = Context::new_dbus(LE, 0);
let encoded = to_bytes(ctxt, &object_ref).unwrap();
let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
assert!(matches!(obj, ObjectRef::Borrowed { .. }));
assert_eq!(obj.name().unwrap().as_str(), ":1.23");
assert_eq!(obj.path_as_str(), TEST_OBJECT_PATH);
}
#[test]
fn serialization_borrowed_object_ref() {
let object_ref = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.23").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
let ctxt = Context::new_dbus(LE, 0);
let encoded = to_bytes(ctxt, &object_ref).unwrap();
let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
assert!(matches!(obj, ObjectRef::Borrowed { .. }));
assert_eq!(obj.name().unwrap().as_str(), ":1.23");
assert_eq!(obj.path_as_str(), TEST_OBJECT_PATH);
}
#[test]
fn object_ref_equality() {
let object_ref1 = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.23").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
let object_ref2 = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.23").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
assert_eq!(object_ref1, object_ref2);
let object_ref3 = ObjectRef::new_borrowed(
UniqueName::from_static_str(":1.24").unwrap(),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
assert_ne!(object_ref1, object_ref3);
let object_ref4 = ObjectRef::new_owned(
UniqueName::from_static_str_unchecked(":1.23"),
ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH),
);
assert_eq!(object_ref1, object_ref4);
let null_object_ref: ObjectRef = ObjectRef::Null;
assert_ne!(object_ref1, null_object_ref);
assert_ne!(null_object_ref, object_ref1);
let null_object_ref2: ObjectRef = ObjectRef::Null;
assert_eq!(null_object_ref, null_object_ref2);
}
#[test]
fn try_from_value_for_objectref() {
let name = UniqueName::from_static_str_unchecked(":0.0");
let path = ObjectPath::from_static_str_unchecked("/org/a11y/atspi/testpath");
let objref = ObjectRef::new_borrowed(name, path);
let value: Value = objref.into();
let objref_2: ObjectRef = value.try_into().unwrap();
assert_eq!(objref_2.name().unwrap().as_str(), ":0.0");
assert_eq!(objref_2.path_as_str(), "/org/a11y/atspi/testpath");
}
#[test]
fn try_from_owned_value_for_objectref() {
let name = UniqueName::from_static_str_unchecked(":0.0");
let path = ObjectPath::from_static_str_unchecked("/org/a11y/atspi/testpath");
let objref = ObjectRef::new_borrowed(name, path);
let value: Value = objref.into();
let value: OwnedValue = value.try_into().unwrap();
let objref_2: ObjectRef = value.try_into().unwrap();
assert_eq!(objref_2.name_as_str(), Some(":0.0"));
assert_eq!(objref_2.path_as_str(), "/org/a11y/atspi/testpath");
}
#[test]
fn must_fail_test_try_from_invalid_value_for_object_ref() {
let value = zvariant::Value::from((42, true));
let obj: Result<ObjectRef, _> = value.try_into();
assert!(obj.is_err());
}
#[test]
fn hash_and_object_coherence() {
let name = UniqueName::from_static_str_unchecked(":1.23");
let path = ObjectPath::from_static_str_unchecked(TEST_OBJECT_PATH);
let object_ref1 = ObjectRef::new_borrowed(&name, &path);
let object_ref2 = ObjectRef::new_borrowed(name, path);
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
assert_eq!(object_ref1, object_ref2);
object_ref1.hash(&mut hasher1);
object_ref2.hash(&mut hasher2);
assert_eq!(hasher1.finish(), hasher2.finish());
}
#[test]
#[should_panic(expected = "assertion failed: matches!(obj, ObjectRef::Borrowed { .. })")]
fn valid_name_null_path_object_ref() {
let object_ref = ObjectRef::from_static_str_unchecked("1.23", NULL_PATH_STR);
let ctxt = Context::new_dbus(LE, 0);
let encoded = to_bytes(ctxt, &object_ref).unwrap();
let (obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
assert!(matches!(obj, ObjectRef::Borrowed { .. }));
}
#[test]
#[should_panic(
expected = r#"A non-null ObjectRef requires a name and a path but got: ("", /org/a11y/atspi/path/007)"#
)]
fn empty_name_valid_path_object_ref() {
let object_ref = ObjectRef::from_static_str_unchecked("", TEST_OBJECT_PATH);
let ctxt = Context::new_dbus(LE, 0);
let encoded = to_bytes(ctxt, &object_ref).unwrap();
let (_obj, _) = encoded.deserialize::<ObjectRef>().unwrap();
}
}