dbus_strings/
lib.rs

1#![warn(missing_docs)]
2
3//! A small crate which has a Rust native implementation of different kinds of D-Bus string types.
4
5
6use std::borrow::Cow;
7use std::borrow::Borrow;
8use std::fmt;
9use std::error::Error;
10use std::ops::Deref;
11use std::convert::TryFrom;
12
13mod validity;
14
15/// The supplied string was not a valid string of the desired type.
16#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
17pub struct InvalidStringError(&'static str);
18
19impl fmt::Display for InvalidStringError {
20    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21        write!(f, "String is not a valid {}", self.0)
22    }
23}
24
25impl Error for InvalidStringError {}
26
27/// A D-Bus string-like type - a basic (non-container) type with variable length.
28///
29/// It wraps a str, which means that it is unsized.
30pub trait StringLike: ToOwned {
31    /// The name of the type
32    const NAME: &'static str;
33
34    /// Creates a new borrowed string
35    fn new(s: &str) -> Result<&Self, InvalidStringError> {
36        Self::is_valid(s)?;
37        Ok(Self::new_unchecked(s))
38    }
39
40    /// Creates a new owned string
41    fn new_owned<S: Into<String>>(s: S) -> Result<<Self as ToOwned>::Owned, InvalidStringError> {
42        let s = s.into();
43        Self::is_valid(&s)?;
44        Ok(Self::new_unchecked_owned(s))
45    }
46
47    /// Creates a new borrowed string without actually checking that it is valid.
48    ///
49    /// Sending this over D-Bus if actually invalid, could result in e g immediate disconnection
50    /// from the server.
51    fn new_unchecked(_: &str) -> &Self;
52
53    /// Creates a new owned string without actually checking that it is valid.
54    ///
55    /// Sending this over D-Bus if actually invalid, could result in e g immediate disconnection
56    /// from the server.
57    fn new_unchecked_owned(_: String) -> <Self as ToOwned>::Owned;
58
59    /// Checks whether or not a string is valid.
60    fn is_valid(_: &str)  -> Result<(), InvalidStringError>;
61}
62
63macro_rules! string_wrapper_base {
64    ($(#[$comment:meta])* $t: ident, $towned: ident) => {
65        $(#[$comment])*
66        ///
67        /// Like str and CStr, this struct is unsized, which means that the way to access
68        /// it is through a reference.
69        #[repr(transparent)]
70        #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
71        pub struct $t(str);
72
73        impl Deref for $t {
74            type Target = str;
75            fn deref(&self) -> &str { &self.0 }
76        }
77
78        impl fmt::Display for $t {
79            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) }
80        }
81
82        impl AsRef<str> for $t {
83            fn as_ref(&self) -> &str { &self.0 }
84        }
85
86        impl ToOwned for $t {
87            type Owned = $towned;
88            fn to_owned(&self) -> $towned { $towned(self.0.into()) }
89        }
90
91        impl<'a> TryFrom<&'a str> for &'a $t {
92            type Error = InvalidStringError;
93            fn try_from(s: &'a str) -> Result<&'a $t, Self::Error> { $t::new(s) }
94        }
95
96        $(#[$comment])*
97        ///
98        /// Like String and CString, this struct is an owned buffer. It Derefs to its unsized
99        /// variant.
100        #[repr(transparent)]
101        #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
102        pub struct $towned(String);
103
104        impl $towned {
105            /// Creates a new string.
106            pub fn new<S: Into<String>>(s: S) -> Result<Self, InvalidStringError> {
107                $t::new_owned(s)
108            }
109
110            /// Unwraps the inner String.
111            pub fn into_inner(self) -> String { self.0 }
112        }
113
114        impl Deref for $towned {
115            type Target = $t;
116            fn deref(&self) -> &$t { $t::new_unchecked(&self.0) }
117        }
118
119        impl Borrow<$t> for $towned {
120            fn borrow(&self) -> &$t { &self }
121        }
122
123        impl fmt::Display for $towned {
124            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) }
125        }
126
127        impl TryFrom<String> for $towned {
128            type Error = InvalidStringError;
129            fn try_from(s: String) -> Result<$towned, Self::Error> { $towned::new(s) }
130        }
131
132        impl<'a> From<$towned> for Cow<'a, $t> {
133            fn from(s: $towned) -> Cow<'a,  $t> { Cow::Owned(s) }
134        }
135
136        impl<'a> From<&'a $t> for Cow<'a, $t> {
137            fn from(s: &'a $t) -> Cow<'a, $t> { Cow::Borrowed(s) }
138        }
139
140        impl<'a> From<&'a $towned> for Cow<'a, $t> {
141            fn from(s: &'a $towned) -> Cow<'a, $t> { Cow::Borrowed(&s) }
142        }
143    }
144}
145
146macro_rules! string_wrapper {
147    ($(#[$comment:meta])* $t: ident, $towned: ident, $validate: ident) => {
148        string_wrapper_base!($(#[$comment])* $t, $towned);
149
150        impl StringLike for $t {
151            const NAME: &'static str = stringify!($t);
152            fn new_unchecked(s: &str) -> &Self {
153                // Unfortunately we have to go unsafe here - there is no safe way to wrap an unsized
154                // type into a newtype.
155                // We know it's sound because of repr(transparent)
156                unsafe { std::mem::transmute(s) }
157            }
158            fn new_unchecked_owned(s: String) -> $towned {
159                $towned(s)
160            }
161            fn is_valid(s: &str) -> Result<(), InvalidStringError> {
162                validity::$validate(s.as_bytes()).map_err(|_| InvalidStringError(Self::NAME))
163            }
164        }
165
166        impl<'a> From<&'a $t> for &'a DBusStr {
167            fn from(s: &'a $t) -> &'a DBusStr { DBusStr::new_unchecked(&*s) }
168        }
169
170        impl<'a> TryFrom<&'a DBusStr> for &'a $t {
171            type Error = InvalidStringError;
172            fn try_from(s: &'a DBusStr) -> Result<&'a $t, Self::Error> {
173                $t::new(&*s)
174            }
175        }
176
177        impl AsRef<DBusStr> for $t {
178            fn as_ref(&self) -> &DBusStr { DBusStr::new_unchecked(self) }
179        }
180
181        impl $t {
182            /// Type conversion to DBusStr.
183            pub fn as_dbus_str(&self) -> &DBusStr { DBusStr::new_unchecked(self) }
184        }
185
186        impl From<$towned> for DBusString {
187            fn from(s: $towned) -> DBusString { DBusStr::new_unchecked_owned(s.into_inner()) }
188        }
189
190        impl TryFrom<DBusString> for $towned {
191            type Error = InvalidStringError;
192            fn try_from(s: DBusString) -> Result<$towned, Self::Error> {
193                $t::new_owned(s.into_inner())
194            }
195        }
196    }
197}
198
199string_wrapper_base!(
200    /// A D-Bus string must be valid UTF-8 and contain no interior nul bytes.
201    DBusStr, DBusString
202);
203
204impl StringLike for DBusStr {
205    const NAME: &'static str = "DBusStr";
206    fn new_unchecked(s: &str) -> &Self {
207        // Unfortunately we have to go unsafe here - there is no safe way to wrap an unsized
208        // type into a newtype.
209        // We know it's sound because of repr(transparent)
210        unsafe { std::mem::transmute(s) }
211    }
212    fn new_unchecked_owned(s: String) -> DBusString {
213        DBusString(s)
214    }
215    fn is_valid(s: &str) -> Result<(), InvalidStringError> {
216        validity::is_valid_string(s).map_err(|_| InvalidStringError(Self::NAME))
217    }
218}
219
220string_wrapper!(
221    /// A D-Bus interface name is usually something like "org.freedesktop.DBus"
222    ///
223    /// For exact rules see the D-Bus specification.
224    InterfaceName, InterfaceNameBuf, is_valid_interface_name
225);
226
227string_wrapper!(
228    /// A D-Bus member name is usually something like "Hello", a single identifier without special
229    /// characters.
230    ///
231    /// For exact rules see the D-Bus specification.
232    MemberName, MemberNameBuf, is_valid_member_name
233);
234
235string_wrapper!(
236    /// A D-Bus error name is usually something like "org.freedesktop.DBus.Error.Failed"
237    ///
238    /// For exact rules see the D-Bus specification.
239    ErrorName, ErrorNameBuf, is_valid_error_name
240);
241
242string_wrapper!(
243    /// A D-Bus bus name is either something like "com.example.MyService" or ":1.54"
244    ///
245    /// For exact rules see the D-Bus specification.
246    BusName, BusNameBuf, is_valid_bus_name
247);
248
249impl<'a> From<&'a SignatureSingle> for &'a SignatureMulti {
250    fn from(s: &'a SignatureSingle) -> &'a SignatureMulti { SignatureMulti::new_unchecked(&s.0) }
251}
252
253impl From<SignatureSingleBuf> for SignatureMultiBuf {
254    fn from(s: SignatureSingleBuf) -> SignatureMultiBuf { SignatureMulti::new_unchecked_owned(s.0) }
255}
256
257
258impl SignatureMulti {
259    /// Splits this signature into the first and remaining parts.
260    ///
261    /// Returns none if the signature is empty.
262    pub fn single(&self) -> Option<(&SignatureSingle, &SignatureMulti)> {
263        validity::sig_single(self.as_bytes(), 0, 0).map(|x|
264            (SignatureSingle::new_unchecked(&self[0..x]), SignatureMulti::new_unchecked(&self[x..]))
265        )
266    }
267}
268
269string_wrapper!(
270    /// A D-Bus type signature of a single type, e g "b" or "a{sv}" but not "ii"
271    ///
272    /// For exact rules see the D-Bus specification.
273    SignatureSingle, SignatureSingleBuf, is_valid_signature_single
274);
275
276string_wrapper!(
277    /// A D-Bus type signature of zero or more types, e g "ii" or "sa{sv}"
278    ///
279    /// For exact rules see the D-Bus specification.
280    SignatureMulti, SignatureMultiBuf, is_valid_signature_multi
281);
282
283string_wrapper!(
284    /// A D-Bus object path is usually something like "/org/freedesktop/DBus".
285    ///
286    /// For exact rules see the D-Bus specification.
287    ObjectPath, ObjectPathBuf, is_valid_object_path
288);
289
290#[test]
291fn type_conversions() {
292    use std::borrow::Cow;
293    let x: &ObjectPath = ObjectPath::new("/test").unwrap();
294    let y: ObjectPathBuf = ObjectPath::new_owned("/test").unwrap();
295    assert_eq!(x, &*y);
296
297    let x = Cow::from(x);
298    let y = Cow::from(y);
299    assert_eq!(x, y);
300
301    let x: &DBusStr = (&*x).into();
302    let y = DBusString::from(y.into_owned());
303    assert_eq!(x, &*y);
304}
305
306#[test]
307fn errors() {
308    let q = MemberName::new("Hello.world").unwrap_err();
309    assert_eq!(q.to_string(), "String is not a valid MemberName".to_string());
310}
311
312#[test]
313fn sig_split() {
314    let s = SignatureMulti::new("ua{sv}(ss)").unwrap();
315    let (a2, s2) = s.single().unwrap();
316    assert_eq!(&**a2, "u");
317    assert_eq!(&**s2, "a{sv}(ss)");
318
319    let (a3, s3) = s2.single().unwrap();
320    assert_eq!(&**a3, "a{sv}");
321    assert_eq!(&**s3, "(ss)");
322
323    let (a4, s4) = s3.single().unwrap();
324    assert_eq!(&**a4, "(ss)");
325    assert_eq!(&**s4, "");
326
327    assert!(s4.single().is_none());
328}