#![warn(missing_docs)]
use std::borrow::Cow;
use std::borrow::Borrow;
use std::fmt;
use std::error::Error;
use std::ops::Deref;
use std::convert::TryFrom;
mod validity;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct InvalidStringError(&'static str);
impl fmt::Display for InvalidStringError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "String is not a valid {}", self.0)
}
}
impl Error for InvalidStringError {}
pub trait StringLike: ToOwned {
const NAME: &'static str;
fn new(s: &str) -> Result<&Self, InvalidStringError> {
Self::is_valid(s)?;
Ok(Self::new_unchecked(s))
}
fn new_owned<S: Into<String>>(s: S) -> Result<<Self as ToOwned>::Owned, InvalidStringError> {
let s = s.into();
Self::is_valid(&s)?;
Ok(Self::new_unchecked_owned(s))
}
fn new_unchecked(_: &str) -> &Self;
fn new_unchecked_owned(_: String) -> <Self as ToOwned>::Owned;
fn is_valid(_: &str) -> Result<(), InvalidStringError>;
}
macro_rules! string_wrapper_base {
($(#[$comment:meta])* $t: ident, $towned: ident) => {
$(#[$comment])*
#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct $t(str);
impl Deref for $t {
type Target = str;
fn deref(&self) -> &str { &self.0 }
}
impl fmt::Display for $t {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) }
}
impl AsRef<str> for $t {
fn as_ref(&self) -> &str { &self.0 }
}
impl ToOwned for $t {
type Owned = $towned;
fn to_owned(&self) -> $towned { $towned(self.0.into()) }
}
impl<'a> TryFrom<&'a str> for &'a $t {
type Error = InvalidStringError;
fn try_from(s: &'a str) -> Result<&'a $t, Self::Error> { $t::new(s) }
}
$(#[$comment])*
#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
pub struct $towned(String);
impl $towned {
pub fn new<S: Into<String>>(s: S) -> Result<Self, InvalidStringError> {
$t::new_owned(s)
}
pub fn into_inner(self) -> String { self.0 }
}
impl Deref for $towned {
type Target = $t;
fn deref(&self) -> &$t { $t::new_unchecked(&self.0) }
}
impl Borrow<$t> for $towned {
fn borrow(&self) -> &$t { &self }
}
impl fmt::Display for $towned {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) }
}
impl TryFrom<String> for $towned {
type Error = InvalidStringError;
fn try_from(s: String) -> Result<$towned, Self::Error> { $towned::new(s) }
}
impl<'a> From<$towned> for Cow<'a, $t> {
fn from(s: $towned) -> Cow<'a, $t> { Cow::Owned(s) }
}
impl<'a> From<&'a $t> for Cow<'a, $t> {
fn from(s: &'a $t) -> Cow<'a, $t> { Cow::Borrowed(s) }
}
impl<'a> From<&'a $towned> for Cow<'a, $t> {
fn from(s: &'a $towned) -> Cow<'a, $t> { Cow::Borrowed(&s) }
}
}
}
macro_rules! string_wrapper {
($(#[$comment:meta])* $t: ident, $towned: ident, $validate: ident) => {
string_wrapper_base!($(#[$comment])* $t, $towned);
impl StringLike for $t {
const NAME: &'static str = stringify!($t);
fn new_unchecked(s: &str) -> &Self {
unsafe { std::mem::transmute(s) }
}
fn new_unchecked_owned(s: String) -> $towned {
$towned(s)
}
fn is_valid(s: &str) -> Result<(), InvalidStringError> {
validity::$validate(s.as_bytes()).map_err(|_| InvalidStringError(Self::NAME))
}
}
impl<'a> From<&'a $t> for &'a DBusStr {
fn from(s: &'a $t) -> &'a DBusStr { DBusStr::new_unchecked(&*s) }
}
impl<'a> TryFrom<&'a DBusStr> for &'a $t {
type Error = InvalidStringError;
fn try_from(s: &'a DBusStr) -> Result<&'a $t, Self::Error> {
$t::new(&*s)
}
}
impl AsRef<DBusStr> for $t {
fn as_ref(&self) -> &DBusStr { DBusStr::new_unchecked(self) }
}
impl $t {
pub fn as_dbus_str(&self) -> &DBusStr { DBusStr::new_unchecked(self) }
}
impl From<$towned> for DBusString {
fn from(s: $towned) -> DBusString { DBusStr::new_unchecked_owned(s.into_inner()) }
}
impl TryFrom<DBusString> for $towned {
type Error = InvalidStringError;
fn try_from(s: DBusString) -> Result<$towned, Self::Error> {
$t::new_owned(s.into_inner())
}
}
}
}
string_wrapper_base!(
DBusStr, DBusString
);
impl StringLike for DBusStr {
const NAME: &'static str = "DBusStr";
fn new_unchecked(s: &str) -> &Self {
unsafe { std::mem::transmute(s) }
}
fn new_unchecked_owned(s: String) -> DBusString {
DBusString(s)
}
fn is_valid(s: &str) -> Result<(), InvalidStringError> {
validity::is_valid_string(s).map_err(|_| InvalidStringError(Self::NAME))
}
}
string_wrapper!(
InterfaceName, InterfaceNameBuf, is_valid_interface_name
);
string_wrapper!(
MemberName, MemberNameBuf, is_valid_member_name
);
string_wrapper!(
ErrorName, ErrorNameBuf, is_valid_error_name
);
string_wrapper!(
BusName, BusNameBuf, is_valid_bus_name
);
impl<'a> From<&'a SignatureSingle> for &'a SignatureMulti {
fn from(s: &'a SignatureSingle) -> &'a SignatureMulti { SignatureMulti::new_unchecked(&s.0) }
}
impl From<SignatureSingleBuf> for SignatureMultiBuf {
fn from(s: SignatureSingleBuf) -> SignatureMultiBuf { SignatureMulti::new_unchecked_owned(s.0) }
}
impl SignatureMulti {
pub fn single(&self) -> Option<(&SignatureSingle, &SignatureMulti)> {
validity::sig_single(self.as_bytes(), 0, 0).map(|x|
(SignatureSingle::new_unchecked(&self[0..x]), SignatureMulti::new_unchecked(&self[x..]))
)
}
}
string_wrapper!(
SignatureSingle, SignatureSingleBuf, is_valid_signature_single
);
string_wrapper!(
SignatureMulti, SignatureMultiBuf, is_valid_signature_multi
);
string_wrapper!(
ObjectPath, ObjectPathBuf, is_valid_object_path
);
#[test]
fn type_conversions() {
use std::borrow::Cow;
let x: &ObjectPath = ObjectPath::new("/test").unwrap();
let y: ObjectPathBuf = ObjectPath::new_owned("/test").unwrap();
assert_eq!(x, &*y);
let x = Cow::from(x);
let y = Cow::from(y);
assert_eq!(x, y);
let x: &DBusStr = (&*x).into();
let y = DBusString::from(y.into_owned());
assert_eq!(x, &*y);
}
#[test]
fn errors() {
let q = MemberName::new("Hello.world").unwrap_err();
assert_eq!(q.to_string(), "String is not a valid MemberName".to_string());
}
#[test]
fn sig_split() {
let s = SignatureMulti::new("ua{sv}(ss)").unwrap();
let (a2, s2) = s.single().unwrap();
assert_eq!(&**a2, "u");
assert_eq!(&**s2, "a{sv}(ss)");
let (a3, s3) = s2.single().unwrap();
assert_eq!(&**a3, "a{sv}");
assert_eq!(&**s3, "(ss)");
let (a4, s4) = s3.single().unwrap();
assert_eq!(&**a4, "(ss)");
assert_eq!(&**s4, "");
assert!(s4.single().is_none());
}