zbus_names/
member_name.rs

1use crate::{utils::impl_try_from, Error, Result};
2use serde::{de, Deserialize, Serialize};
3use static_assertions::assert_impl_all;
4use std::{
5    borrow::{Borrow, Cow},
6    convert::TryFrom,
7    fmt::{self, Display, Formatter},
8    ops::Deref,
9    sync::Arc,
10};
11use zvariant::{NoneValue, OwnedValue, Str, Type, Value};
12
13/// String that identifies an [member (method or signal) name][in] on the bus.
14///
15/// # Examples
16///
17/// ```
18/// use core::convert::TryFrom;
19/// use zbus_names::MemberName;
20///
21/// // Valid member names.
22/// let name = MemberName::try_from("Member_for_you").unwrap();
23/// assert_eq!(name, "Member_for_you");
24/// let name = MemberName::try_from("CamelCase101").unwrap();
25/// assert_eq!(name, "CamelCase101");
26/// let name = MemberName::try_from("a_very_loooooooooooooooooo_ooooooo_0000o0ngName").unwrap();
27/// assert_eq!(name, "a_very_loooooooooooooooooo_ooooooo_0000o0ngName");
28///
29/// // Invalid member names
30/// MemberName::try_from("").unwrap_err();
31/// MemberName::try_from(".").unwrap_err();
32/// MemberName::try_from("1startWith_a_Digit").unwrap_err();
33/// MemberName::try_from("contains.dots_in_the_name").unwrap_err();
34/// MemberName::try_from("contains-dashes-in_the_name").unwrap_err();
35/// ```
36///
37/// [in]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-member
38#[derive(
39    Clone, Debug, Hash, PartialEq, Eq, Serialize, Type, Value, PartialOrd, Ord, OwnedValue,
40)]
41pub struct MemberName<'name>(Str<'name>);
42
43assert_impl_all!(MemberName<'_>: Send, Sync, Unpin);
44
45impl<'name> MemberName<'name> {
46    /// A borrowed clone (never allocates, unlike clone).
47    pub fn as_ref(&self) -> MemberName<'_> {
48        MemberName(self.0.as_ref())
49    }
50
51    /// The member name as string.
52    pub fn as_str(&self) -> &str {
53        self.0.as_str()
54    }
55
56    /// Create a new `MemberName` from the given string.
57    ///
58    /// Since the passed string is not checked for correctness, prefer using the
59    /// `TryFrom<&str>` implementation.
60    pub fn from_str_unchecked(name: &'name str) -> Self {
61        Self(Str::from(name))
62    }
63
64    /// Same as `try_from`, except it takes a `&'static str`.
65    pub fn from_static_str(name: &'static str) -> Result<Self> {
66        ensure_correct_member_name(name)?;
67        Ok(Self(Str::from_static(name)))
68    }
69
70    /// Same as `from_str_unchecked`, except it takes a `&'static str`.
71    pub const fn from_static_str_unchecked(name: &'static str) -> Self {
72        Self(Str::from_static(name))
73    }
74
75    /// Same as `from_str_unchecked`, except it takes an owned `String`.
76    ///
77    /// Since the passed string is not checked for correctness, prefer using the
78    /// `TryFrom<String>` implementation.
79    pub fn from_string_unchecked(name: String) -> Self {
80        Self(Str::from(name))
81    }
82
83    /// Creates an owned clone of `self`.
84    pub fn to_owned(&self) -> MemberName<'static> {
85        MemberName(self.0.to_owned())
86    }
87
88    /// Creates an owned clone of `self`.
89    pub fn into_owned(self) -> MemberName<'static> {
90        MemberName(self.0.into_owned())
91    }
92}
93
94impl Deref for MemberName<'_> {
95    type Target = str;
96
97    fn deref(&self) -> &Self::Target {
98        self.as_str()
99    }
100}
101
102impl Borrow<str> for MemberName<'_> {
103    fn borrow(&self) -> &str {
104        self.as_str()
105    }
106}
107
108impl Display for MemberName<'_> {
109    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
110        Display::fmt(&self.as_str(), f)
111    }
112}
113
114impl PartialEq<str> for MemberName<'_> {
115    fn eq(&self, other: &str) -> bool {
116        self.as_str() == other
117    }
118}
119
120impl PartialEq<&str> for MemberName<'_> {
121    fn eq(&self, other: &&str) -> bool {
122        self.as_str() == *other
123    }
124}
125
126impl PartialEq<OwnedMemberName> for MemberName<'_> {
127    fn eq(&self, other: &OwnedMemberName) -> bool {
128        *self == other.0
129    }
130}
131
132impl<'de: 'name, 'name> Deserialize<'de> for MemberName<'name> {
133    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
134    where
135        D: serde::Deserializer<'de>,
136    {
137        let name = <Cow<'name, str>>::deserialize(deserializer)?;
138
139        Self::try_from(name).map_err(|e| de::Error::custom(e.to_string()))
140    }
141}
142
143impl<'name> From<MemberName<'name>> for Str<'name> {
144    fn from(value: MemberName<'name>) -> Self {
145        value.0
146    }
147}
148
149impl_try_from! {
150    ty: MemberName<'s>,
151    owned_ty: OwnedMemberName,
152    validate_fn: ensure_correct_member_name,
153    try_from: [&'s str, String, Arc<str>, Cow<'s, str>, Str<'s>],
154}
155
156fn ensure_correct_member_name(name: &str) -> Result<()> {
157    // Rules
158    //
159    // * Only ASCII alphanumeric or `_`.
160    // * Must not begin with a digit.
161    // * Must contain at least 1 character.
162    // * <= 255 characters.
163    if name.is_empty() {
164        return Err(Error::InvalidMemberName(format!(
165            "`{}` is {} characters long, which is smaller than minimum allowed (1)",
166            name,
167            name.len(),
168        )));
169    } else if name.len() > 255 {
170        return Err(Error::InvalidMemberName(format!(
171            "`{}` is {} characters long, which is longer than maximum allowed (255)",
172            name,
173            name.len(),
174        )));
175    }
176
177    // SAFETY: We established above that there is at least 1 character so unwrap is fine.
178    if name.chars().next().unwrap().is_ascii_digit() {
179        return Err(Error::InvalidMemberName(String::from(
180            "must not start with a digit",
181        )));
182    }
183
184    for c in name.chars() {
185        if !c.is_ascii_alphanumeric() && c != '_' {
186            return Err(Error::InvalidMemberName(format!(
187                "`{c}` character not allowed"
188            )));
189        }
190    }
191
192    Ok(())
193}
194
195/// This never succeeds but is provided so it's easier to pass `Option::None` values for API
196/// requiring `Option<TryInto<impl BusName>>`, since type inference won't work here.
197impl TryFrom<()> for MemberName<'_> {
198    type Error = Error;
199
200    fn try_from(_value: ()) -> Result<Self> {
201        unreachable!("Conversion from `()` is not meant to actually work");
202    }
203}
204
205impl<'name> From<&MemberName<'name>> for MemberName<'name> {
206    fn from(name: &MemberName<'name>) -> Self {
207        name.clone()
208    }
209}
210
211impl<'name> NoneValue for MemberName<'name> {
212    type NoneType = &'name str;
213
214    fn null_value() -> Self::NoneType {
215        <&str>::default()
216    }
217}
218
219/// Owned sibling of [`MemberName`].
220#[derive(
221    Clone, Debug, Hash, PartialEq, Eq, Serialize, Type, Value, PartialOrd, Ord, OwnedValue,
222)]
223pub struct OwnedMemberName(#[serde(borrow)] MemberName<'static>);
224
225assert_impl_all!(OwnedMemberName: Send, Sync, Unpin);
226
227impl OwnedMemberName {
228    /// Convert to the inner `MemberName`, consuming `self`.
229    pub fn into_inner(self) -> MemberName<'static> {
230        self.0
231    }
232
233    /// Get a reference to the inner `MemberName`.
234    pub fn inner(&self) -> &MemberName<'static> {
235        &self.0
236    }
237}
238
239impl Deref for OwnedMemberName {
240    type Target = MemberName<'static>;
241
242    fn deref(&self) -> &Self::Target {
243        &self.0
244    }
245}
246
247impl Borrow<str> for OwnedMemberName {
248    fn borrow(&self) -> &str {
249        self.0.as_str()
250    }
251}
252
253impl From<OwnedMemberName> for MemberName<'static> {
254    fn from(o: OwnedMemberName) -> Self {
255        o.into_inner()
256    }
257}
258
259impl<'unowned, 'owned: 'unowned> From<&'owned OwnedMemberName> for MemberName<'unowned> {
260    fn from(name: &'owned OwnedMemberName) -> Self {
261        MemberName::from_str_unchecked(name.as_str())
262    }
263}
264
265impl From<MemberName<'_>> for OwnedMemberName {
266    fn from(name: MemberName<'_>) -> Self {
267        OwnedMemberName(name.into_owned())
268    }
269}
270
271impl From<OwnedMemberName> for Str<'static> {
272    fn from(value: OwnedMemberName) -> Self {
273        value.into_inner().0
274    }
275}
276
277impl<'de> Deserialize<'de> for OwnedMemberName {
278    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
279    where
280        D: de::Deserializer<'de>,
281    {
282        String::deserialize(deserializer)
283            .and_then(|n| MemberName::try_from(n).map_err(|e| de::Error::custom(e.to_string())))
284            .map(Self)
285    }
286}
287
288impl PartialEq<&str> for OwnedMemberName {
289    fn eq(&self, other: &&str) -> bool {
290        self.as_str() == *other
291    }
292}
293
294impl PartialEq<MemberName<'_>> for OwnedMemberName {
295    fn eq(&self, other: &MemberName<'_>) -> bool {
296        self.0 == *other
297    }
298}
299
300impl Display for OwnedMemberName {
301    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
302        MemberName::from(self).fmt(f)
303    }
304}
305
306impl NoneValue for OwnedMemberName {
307    type NoneType = <MemberName<'static> as NoneValue>::NoneType;
308
309    fn null_value() -> Self::NoneType {
310        MemberName::null_value()
311    }
312}