#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(feature = "std")]
use std::string::String;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(feature = "std")]
use std::fmt::{Display, Formatter, Result};
#[cfg(not(feature = "std"))]
use core::fmt::{Display, Formatter, Result};
#[cfg(feature = "std")]
use std::ops::Deref;
#[cfg(not(feature = "std"))]
use core::ops::Deref;
pub trait Strip {
type Output;
fn strip(&self) -> Self::Output;
}
macro_rules! non {
(string, $name: ident<$param: ident>, $func: expr, $guarantee: expr, $guarantee2: expr) => {
#[doc = "A string that is known to "]
#[doc = $guarantee2]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct $name<$param = String>($param);
impl<$param> $name<$param>
where
$param: AsRef<str>
{
#[doc = "Creates a new `" ]
#[doc = stringify!($name) ]
#[doc = "` string if the string is "]
#[doc = $guarantee]
#[inline]
pub fn new(value: $param) -> Option<Self> {
if $func(&value) {
Some($name(value))
} else {
None
}
}
#[doc = "Creates a new `" ]
#[doc = stringify!($name) ]
#[doc = "` without checking the value."]
#[doc = "The value must be "]
#[doc = $guarantee]
#[inline]
pub unsafe fn new_unchecked(value: $param) -> Self {
Self(value)
}
#[inline]
pub fn into_inner(self) -> $param {
self.0
}
#[inline]
pub fn inner(&self) -> &$param {
&self.0
}
}
impl<$param> Deref for $name<$param> {
type Target = $param;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<$param> From<$name<$param>> for String
where
$param: Into<String>
{
fn from(value: $name<$param>) -> Self {
value.0.into()
}
}
impl<$param> AsRef<str> for $name<$param>
where
$param: AsRef<str>
{
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl<$param> Display for $name<$param>
where
$param: Display
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.fmt(f)
}
}
impl<$param, Z> Strip for $name<$param>
where
$param: NotString + AsRef<str> + Deref<Target = Z>,
Z: AsRef<str> + Clone
{
type Output = $name<Z>;
fn strip(&self) -> Self::Output{
unsafe { $name::<Z>::new_unchecked((*self.0).clone()) }
}
}
};
(string, $name: ident<$param: ident, const $param2: ident: $param3: ident>, $func: expr, $guarantee: expr, $guarantee2: expr) => {
#[doc = "A string that is known to "]
#[doc = $guarantee2]
pub struct $name<const $param2: $param3, $param = String>($param);
impl<$param, const $param2: $param3> $name<$param2, $param>
where
$param: AsRef<str>
{
#[doc = "Creates a new `" ]
#[doc = stringify!($name) ]
#[doc = "` string if the string is "]
#[doc = $guarantee]
#[inline]
pub fn new(value: $param) -> Option<Self> {
if $func(&value) {
Some($name(value))
} else {
None
}
}
#[doc = "Creates a new `" ]
#[doc = stringify!($name) ]
#[doc = "` without checking the value."]
#[doc = "The value must be "]
#[doc = $guarantee]
#[inline]
pub unsafe fn new_unchecked(value: $param) -> Self {
Self(value)
}
#[inline]
pub fn into_inner(self) -> $param {
self.0
}
#[inline]
pub fn inner(&self) -> &$param {
&self.0
}
}
impl<$param, const $param2: $param3> Deref for $name<$param2, $param> {
type Target = $param;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<$param, const $param2: $param3> From<$name<$param2, $param>> for String
where
$param: Into<String>
{
fn from(value: $name<$param2, $param>) -> Self {
value.0.into()
}
}
impl<$param, const $param2: $param3> AsRef<str> for $name<$param2, $param>
where
$param: AsRef<str>
{
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl<$param, const $param2: $param3> Display for $name<$param2, $param>
where
$param: Display
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.fmt(f)
}
}
impl<$param, Z, const $param2: $param3> Strip for $name<$param2, $param>
where
$param: NotString + AsRef<str> + Deref<Target = Z>,
Z: AsRef<str> + Clone
{
type Output = $name<$param2, Z>;
fn strip(&self) -> Self::Output{
unsafe { $name::<$param2, Z>::new_unchecked((*self.0).clone()) }
}
}
};
}
mod sealed {
pub trait NotString {}
impl<T> NotString for T where T: AsRef<str> + Clone {}
}
use sealed::NotString;
non!(string, NonEmpty<T>,|x: &T| !x.as_ref().is_empty(),"not empty", "not be empty");
non!(string, NonBlank<T>,|x: &T| !x.as_ref().chars().all(|y| y.is_whitespace()),"not only whitespace", "not be only whitespace");
non!(string, ASCII<T>,|x: &T| x.as_ref().is_ascii(),"ascii encoded", "be ascii encoded");
non!(string, ExactLength<T, const N: usize>, |x: &T| x.as_ref().chars().count() == N, "exactly N characters long", "have exactly N characters");
non!(string, LowerCase<T>, |x: &T| { let s = x.as_ref(); s.to_lowercase() == s }, "all lowercase", "be all lowercase");
non!(string, UpperCase<T>, |x: &T| { let s = x.as_ref(); s.to_uppercase() == s }, "all uppercase", "be all uppercase");
non!(string, Trimmed<T>, |x: &T| x.as_ref() == x.as_ref().trim(), "trimmed (no leading or trailing whitespace)", "be trimmed");
non!(string, Alphanumeric<T>, |x: &T| x.as_ref().chars().all(|c| c.is_alphanumeric()), "only alphanumeric characters", "contain only alphanumeric characters");
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_non_empty_new_some() {
let s = String::from("helo world");
let non_empty = NonEmpty::new(s);
assert!(non_empty.is_some());
}
#[test]
fn test_non_empty_new_none() {
let s = String::new();
let empty = NonEmpty::new(s);
assert!(empty.is_none());
}
#[test]
fn test_non_empty_new_unchecked() {
let s = String::from("helo world");
let non_empty = unsafe { NonEmpty::new_unchecked(s.clone()) };
assert_eq!(*non_empty, s);
}
#[test]
fn test_ascii_new_some() {
let s = String::from("helo world");
let ascii = ASCII::new(s);
assert!(ascii.is_some());
}
#[test]
fn test_ascii_new_none() {
let s = String::from("cześć");
let ascii = ASCII::new(s);
assert!(ascii.is_none());
}
#[test]
fn test_ascii_new_unchecked() {
let s = String::from("helo world");
let ascii = unsafe { ASCII::new_unchecked(s.clone()) };
assert_eq!(*ascii, s);
}
#[test]
fn test_non_blank_new_some() {
let s = String::from("helo world");
let nonblank = NonBlank::new(s);
assert!(nonblank.is_some());
}
#[test]
fn test_non_blank_new_none() {
let s = String::from(" \n");
let nonblank = NonBlank::new(s);
assert!(nonblank.is_none());
}
#[test]
fn test_non_blank_new_unchecked() {
let s = String::from("helo world");
let nonblank = unsafe { NonBlank::new_unchecked(s.clone()) };
assert_eq!(*nonblank, s);
}
#[test]
fn test_exact_length_new_some() {
let s = String::from("hi");
let exact_length = ExactLength::<2>::new(s);
assert!(exact_length.is_some());
}
#[test]
fn test_exact_length_new_none() {
let s = String::from("helo world");
let exact_length = ExactLength::<2>::new(s);
assert!(exact_length.is_none());
}
#[test]
fn test_exact_length_new_unchecked() {
let s = String::from("hi!");
let exact_length = unsafe { ExactLength::<3>::new_unchecked(s.clone()) };
assert_eq!(*exact_length, s);
}
#[test]
fn test_combinations() {
let s = String::from("helo world");
let nonblank = NonBlank::new(s.clone());
assert!(nonblank.is_some());
let ascii = ASCII::new(nonblank.unwrap());
assert!(ascii.is_some());
let ascii = ascii.unwrap();
assert_eq!(ascii.as_ref(), s);
}
#[test]
fn test_strip() {
let s = String::from("helo world");
let nonblank = NonBlank::new(s.clone());
assert!(nonblank.is_some());
let ascii = ASCII::new(nonblank.unwrap());
assert!(ascii.is_some());
let ascii = ascii.unwrap();
unsafe { assert_eq!(ascii.strip(), ASCII::new_unchecked(s)); }
}
}