diff options
Diffstat (limited to 'src/non_empty_string.rs')
| -rw-r--r-- | src/non_empty_string.rs | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/src/non_empty_string.rs b/src/non_empty_string.rs new file mode 100644 index 0000000..8ad7400 --- /dev/null +++ b/src/non_empty_string.rs @@ -0,0 +1,378 @@ +use { + crate::*, + std::{ + borrow::Borrow, + cmp::PartialEq, + convert::TryFrom, + fmt::{Display, Formatter}, + ops::Deref, + }, +}; + +/// A non-empty [`String`]. +/// [`NonEmptyStr`] is the borrowed version. +#[repr(transparent)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct NonEmptyString(String); + +impl NonEmptyString { + /// Tries to create a [`NonEmptyString`] from the string `s`. + /// Returns `None` if the string `s` is empty. + pub fn new(s: String) -> Option<Self> { + if s.is_empty() { + None + } else { + Some(Self(s)) + } + } + + /// Creates a [`NonEmptyString`] from the string `s` + /// without checking if it is empty. + /// + /// # Safety + /// The caller guarantees the string `s` is not empty. + /// Passing an empty string is undefined behaviour. + /// + /// # Panics + /// Panics in debug configuration only if the string `s` is empty. + pub unsafe fn new_unchecked(s: String) -> Self { + debug_assert!( + !s.is_empty(), + "tried to create a non-empty string from an empty source" + ); + Self(s) + } + + /// Creates a [`NonEmptyString`] from the [`non-empty string slice`](NonEmptyStr) `s`. + pub fn from(s: &NonEmptyStr) -> Self { + unsafe { NonEmptyString::new_unchecked(s.inner().to_owned()) } + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn as_ne_str(&self) -> &NonEmptyStr { + unsafe { NonEmptyStr::new_unchecked(&self.0) } + } + + pub fn inner(&self) -> &String { + &self.0 + } + + pub fn into_inner(self) -> String { + self.0 + } +} + +impl Deref for NonEmptyString { + type Target = NonEmptyStr; + + fn deref(&self) -> &Self::Target { + self.as_ne_str() + } +} + +impl AsRef<NonEmptyStr> for NonEmptyString { + fn as_ref(&self) -> &NonEmptyStr { + self.as_ne_str() + } +} + +impl AsRef<String> for NonEmptyString { + fn as_ref(&self) -> &String { + self.inner() + } +} + +impl AsRef<str> for NonEmptyString { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Borrow<NonEmptyStr> for NonEmptyString { + fn borrow(&self) -> &NonEmptyStr { + self.as_ne_str() + } +} + +// Fallible conversions from string slices and owned strings. +//////////////////////////////////////////////////////////// +impl<'s> TryFrom<&'s str> for NonEmptyString { + type Error = (); + + fn try_from(s: &'s str) -> Result<Self, Self::Error> { + Self::new(s.to_owned()).ok_or(()) + } +} + +impl TryFrom<String> for NonEmptyString { + type Error = (); + + fn try_from(s: String) -> Result<Self, Self::Error> { + Self::new(s).ok_or(()) + } +} +//////////////////////////////////////////////////////////// + +// Infallible conversion from a non-empty string slice. +//////////////////////////////////////////////////////////// +impl<'s> From<&'s NonEmptyStr> for NonEmptyString { + fn from(s: &'s NonEmptyStr) -> Self { + Self::from(s) + } +} +//////////////////////////////////////////////////////////// + +// Conversions into string slices and owned strings. +//////////////////////////////////////////////////////////// +impl<'s> Into<&'s str> for &'s NonEmptyString { + fn into(self) -> &'s str { + self.as_str() + } +} + +impl Into<String> for NonEmptyString { + fn into(self) -> String { + self.into_inner() + } +} +//////////////////////////////////////////////////////////// + +// Comparsions. + +// <NonEmptyString> +//////////////////////////////////////////////////////////// +impl PartialEq<&NonEmptyString> for NonEmptyString { + fn eq(&self, other: &&NonEmptyString) -> bool { + PartialEq::eq(self, *other) + } + + fn ne(&self, other: &&NonEmptyString) -> bool { + PartialEq::ne(self, *other) + } +} + +impl PartialEq<NonEmptyString> for &NonEmptyString { + fn eq(&self, other: &NonEmptyString) -> bool { + PartialEq::eq(*self, other) + } + + fn ne(&self, other: &NonEmptyString) -> bool { + PartialEq::ne(*self, other) + } +} +//////////////////////////////////////////////////////////// + +/// <str> +//////////////////////////////////////////////////////////// +impl PartialEq<str> for NonEmptyString { + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.as_str(), other) + } + + fn ne(&self, other: &str) -> bool { + PartialEq::ne(self.as_str(), other) + } +} + +impl PartialEq<&str> for NonEmptyString { + fn eq(&self, other: &&str) -> bool { + PartialEq::eq(self.as_str(), *other) + } + + fn ne(&self, other: &&str) -> bool { + PartialEq::ne(self.as_str(), *other) + } +} + +impl PartialEq<str> for &NonEmptyString { + fn eq(&self, other: &str) -> bool { + PartialEq::eq(self.as_str(), other) + } + + fn ne(&self, other: &str) -> bool { + PartialEq::ne(self.as_str(), other) + } +} +//////////////////////////////////////////////////////////// + +/// <String> +//////////////////////////////////////////////////////////// +impl PartialEq<String> for NonEmptyString { + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} + +impl PartialEq<&String> for NonEmptyString { + fn eq(&self, other: &&String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &&String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} + +impl PartialEq<String> for &NonEmptyString { + fn eq(&self, other: &String) -> bool { + PartialEq::eq(self.as_str(), other.as_str()) + } + + fn ne(&self, other: &String) -> bool { + PartialEq::ne(self.as_str(), other.as_str()) + } +} +//////////////////////////////////////////////////////////// + +/// <NonEmptyStr> +//////////////////////////////////////////////////////////// +impl PartialEq<NonEmptyStr> for NonEmptyString { + fn eq(&self, other: &NonEmptyStr) -> bool { + PartialEq::eq(self.as_ne_str(), other) + } + + fn ne(&self, other: &NonEmptyStr) -> bool { + PartialEq::ne(self.as_ne_str(), other) + } +} + +impl PartialEq<&NonEmptyStr> for NonEmptyString { + fn eq(&self, other: &&NonEmptyStr) -> bool { + PartialEq::eq(self.as_ne_str(), *other) + } + + fn ne(&self, other: &&NonEmptyStr) -> bool { + PartialEq::ne(self.as_ne_str(), *other) + } +} + +impl PartialEq<NonEmptyStr> for &NonEmptyString { + fn eq(&self, other: &NonEmptyStr) -> bool { + PartialEq::eq(self.as_ne_str(), other) + } + + fn ne(&self, other: &NonEmptyStr) -> bool { + PartialEq::ne(self.as_ne_str(), other) + } +} +//////////////////////////////////////////////////////////// + +impl Display for NonEmptyString { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.inner().fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn cmp(nes: &NonEmptyString, s: &str) { + assert_eq!(nes, s); + //assert_eq!(s, nes); + assert_eq!(&nes[..], s); + assert_eq!(nes.as_str(), s); + assert_eq!(nes.as_ne_str(), s); + assert_eq!(nes.deref(), s); + assert_eq!(nes.inner(), s); + } + + #[test] + fn non_empty_string() { + let foo = "foo"; + let foo_str = foo.to_owned(); + let ne_foo = NonEmptyStr::new(foo).unwrap(); + + // `new()` from non-empty string + let ne_foo_str = NonEmptyString::new(foo_str.clone()).unwrap(); + cmp(&ne_foo_str, foo); + + assert_eq!(ne_foo_str, ne_foo_str); + assert_eq!(ne_foo_str, &ne_foo_str); + assert_eq!(&ne_foo_str, ne_foo_str); + assert_eq!(&ne_foo_str, &ne_foo_str); + assert_eq!(ne_foo_str, *ne_foo_str); + assert_eq!(*ne_foo_str, ne_foo_str); + assert_eq!(*ne_foo_str, *ne_foo_str); + + // `new()` from empty string + let empty = ""; + assert!(NonEmptyString::new(empty.to_owned()).is_none()); + + // `unchecked_new()` from non-empty string + { + let ne_foo_str = unsafe { NonEmptyString::new_unchecked(foo_str.clone()) }; + cmp(&ne_foo_str, foo); + } + + // from `NonEmptyStr`. + { + let ne_foo_str: NonEmptyString = ne_foo.into(); + cmp(&ne_foo_str, foo); + + let ne_foo_str = <NonEmptyString as From<&NonEmptyStr>>::from(&ne_foo); + cmp(&ne_foo_str, foo); + } + + // try from non-empty slice + { + use std::convert::TryInto; + + let ne_foo_str: NonEmptyString = foo.try_into().unwrap(); + cmp(&ne_foo_str, foo); + + let ne_foo_str = <NonEmptyString as TryFrom<&str>>::try_from(foo).unwrap(); + cmp(&ne_foo_str, foo); + } + + // try from empty slice + { + use std::convert::TryInto; + + let ne_foo_str: Result<NonEmptyString, _> = "".try_into(); + assert!(ne_foo_str.is_err()); + + let ne_foo_str = <NonEmptyString as TryFrom<&str>>::try_from(""); + assert!(ne_foo_str.is_err()); + } + + // try from non-empty `String` + { + use std::convert::TryInto; + + let ne_foo_str: NonEmptyString = foo_str.clone().try_into().unwrap(); + cmp(&ne_foo_str, foo); + + let ne_foo_str = + <NonEmptyString as TryFrom<String>>::try_from(foo_str.clone()).unwrap(); + cmp(&ne_foo_str, foo); + } + + // try from empty `String` + { + use std::convert::TryInto; + + let empty_str = "".to_owned(); + + let ne_foo_str: Result<NonEmptyString, _> = empty_str.clone().try_into(); + assert!(ne_foo_str.is_err()); + + let ne_foo_str = <NonEmptyString as TryFrom<String>>::try_from(empty_str); + assert!(ne_foo_str.is_err()); + } + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic(expected = "tried to create a non-empty string from an empty source")] + fn new_unchecked_panic() { + let _ = unsafe { NonEmptyString::new_unchecked("".to_owned()) }; + } +} |