use core::{
convert::Infallible,
fmt::{Debug, Display},
hash::{Hash, Hasher},
marker::PhantomData,
mem::transmute,
};
#[cfg(feature = "alloc")]
use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, sync::Arc};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[repr(transparent)]
pub struct VStr<Rule: ValidateString> {
_rule: PhantomData<Rule>,
inner: str,
}
#[allow(non_camel_case_types)]
#[allow(dead_code)]
pub type vstr<Rule> = VStr<Rule>;
pub trait ValidateString: Send + Sync + Unpin {
type Error;
fn validate_str(s: &str) -> Result<(), Self::Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ValidateAll;
impl ValidateString for ValidateAll {
type Error = Infallible;
fn validate_str(_: &str) -> Result<(), Self::Error> {
Ok(())
}
}
impl ValidateString for () {
type Error = ();
fn validate_str(_: &str) -> Result<(), Self::Error> {
Err(())
}
}
impl<Rule: ValidateString> VStr<Rule> {
pub fn try_validate(s: &str) -> Result<&Self, Rule::Error> {
Rule::validate_str(s)?;
Ok(unsafe { transmute(s) })
}
pub fn assume_valid(s: &str) -> &Self {
unsafe { transmute(s) }
}
pub fn check(&self) -> Result<&Self, Rule::Error> {
Rule::validate_str(self.as_ref())?;
Ok(self)
}
pub fn try_change_rules<Rule2: ValidateString>(&self) -> Result<&VStr<Rule2>, Rule2::Error> {
VStr::<Rule2>::try_validate(self.as_ref())
}
pub fn change_rules<Rule2: ValidateString>(&self) -> &VStr<Rule2>
where
Rule: Into<Rule2>,
{
VStr::<Rule2>::assume_valid(&self.inner)
}
pub fn erase_rules(&self) -> &VStr<ValidateAll> {
VStr::<ValidateAll>::assume_valid(&self.inner)
}
pub fn as_str(&self) -> &str {
&self.inner
}
}
impl<Rule: ValidateString> VStr<Later<Rule>> {
pub fn make_strict(&self) -> Result<&VStr<Rule>, Rule::Error> {
self.as_ref().validate()
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Later<R: ValidateString> {
_rule: PhantomData<R>,
}
impl<R: ValidateString> ValidateString for Later<R> {
type Error = Infallible;
fn validate_str(_: &str) -> Result<(), Self::Error> {
Ok(())
}
}
impl<R: ValidateString> From<ValidateAll> for Later<R> {
fn from(_: ValidateAll) -> Self {
Later { _rule: PhantomData }
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for Arc<str> {
fn from(vstr: &VStr<Rule>) -> Self {
Arc::from(&vstr.inner)
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for Arc<VStr<Rule>> {
fn from(vstr: &VStr<Rule>) -> Self {
let arcstr: Arc<str> = Arc::from(&vstr.inner);
let ptr = Arc::into_raw(arcstr) as *const VStr<Rule>;
unsafe { Arc::from_raw(ptr) }
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for Rc<str> {
fn from(vstr: &VStr<Rule>) -> Self {
Rc::from(&vstr.inner)
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for Rc<VStr<Rule>> {
fn from(vstr: &VStr<Rule>) -> Self {
let rcstr: Rc<str> = Rc::from(&vstr.inner);
let ptr = Rc::into_raw(rcstr) as *const VStr<Rule>;
unsafe { Rc::from_raw(ptr) }
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for String {
fn from(vstr: &VStr<Rule>) -> Self {
String::from(&vstr.inner)
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for Box<str> {
fn from(vstr: &VStr<Rule>) -> Self {
Box::from(&vstr.inner)
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> From<&VStr<Rule>> for Box<VStr<Rule>> {
fn from(vstr: &VStr<Rule>) -> Self {
let boxstr: Box<str> = Box::from(&vstr.inner);
let ptr = Box::into_raw(boxstr) as *mut VStr<Rule>;
unsafe { Box::from_raw(ptr) }
}
}
impl<'a, Rule: ValidateString> TryFrom<&'a str> for &'a VStr<Rule> {
type Error = Rule::Error;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
VStr::try_validate(s)
}
}
impl<'a, Rule: ValidateString> From<&'a VStr<Rule>> for &'a str {
fn from(vstr: &'a VStr<Rule>) -> Self {
&vstr.inner
}
}
#[cfg(feature = "alloc")]
impl<Rule: ValidateString> TryFrom<String> for Box<VStr<Rule>> {
type Error = Rule::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Ok(Box::from(s.validate::<Rule>()?))
}
}
impl<Rule: ValidateString> AsRef<str> for VStr<Rule> {
fn as_ref(&self) -> &str {
&self.inner
}
}
#[cfg(feature = "serde")]
impl<Rule: ValidateString> Serialize for VStr<Rule> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.inner.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de: 'a, 'a, Rule: ValidateString> Deserialize<'de> for &'a VStr<Rule>
where
Rule::Error: Display,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = <&str>::deserialize(deserializer)?;
let s = s.validate::<Rule>().map_err(serde::de::Error::custom)?;
Ok(s)
}
}
#[cfg(feature = "serde")]
impl<'de, Rule: ValidateString> Deserialize<'de> for Box<VStr<Rule>>
where
Rule::Error: Display,
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = <String>::deserialize(deserializer)?;
let s = s.validate::<Rule>().map_err(serde::de::Error::custom)?;
Ok(Box::from(s))
}
}
pub trait StrExt<'a> {
fn validate<Rule: ValidateString>(self) -> Result<&'a VStr<Rule>, Rule::Error>;
fn assume_valid<Rule: ValidateString>(self) -> &'a VStr<Rule>;
fn validate_or_panic<Rule: ValidateString>(self) -> &'a VStr<Rule>
where
Rule::Error: Debug;
}
impl<'a> StrExt<'a> for &'a str {
fn validate<Rule: ValidateString>(self) -> Result<&'a VStr<Rule>, Rule::Error> {
VStr::<Rule>::try_validate(self)
}
fn assume_valid<Rule: ValidateString>(self) -> &'a VStr<Rule> {
VStr::<Rule>::assume_valid(self)
}
fn validate_or_panic<Rule: ValidateString>(self) -> &'a VStr<Rule>
where
Rule::Error: Debug,
{
VStr::<Rule>::try_validate(self).unwrap()
}
}
impl<Rule: ValidateString> Debug for VStr<Rule> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Debug::fmt(&self.inner, f)
}
}
impl<Rule: ValidateString> Display for VStr<Rule> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.inner, f)
}
}
impl<Rule1: ValidateString, Rule2: ValidateString> PartialEq<VStr<Rule2>> for VStr<Rule1> {
fn eq(&self, other: &VStr<Rule2>) -> bool {
self.inner == other.inner
}
}
impl<Rule: ValidateString> Eq for VStr<Rule> {}
impl<Rule: ValidateString> PartialEq<str> for VStr<Rule> {
fn eq(&self, other: &str) -> bool {
&self.inner == other
}
}
impl<Rule: ValidateString> PartialEq<VStr<Rule>> for str {
fn eq(&self, other: &VStr<Rule>) -> bool {
self == &other.inner
}
}
impl<Rule1: ValidateString, Rule2: ValidateString> PartialOrd<VStr<Rule2>> for VStr<Rule1> {
fn partial_cmp(&self, other: &VStr<Rule2>) -> Option<core::cmp::Ordering> {
self.inner.partial_cmp(&other.inner)
}
}
impl<Rule: ValidateString> Ord for VStr<Rule> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.inner.cmp(&other.inner)
}
}
impl<Rule: ValidateString> PartialOrd<str> for VStr<Rule> {
fn partial_cmp(&self, other: &str) -> Option<core::cmp::Ordering> {
self.inner.partial_cmp(other)
}
}
impl<Rule: ValidateString> PartialOrd<VStr<Rule>> for str {
fn partial_cmp(&self, other: &VStr<Rule>) -> Option<core::cmp::Ordering> {
self.partial_cmp(&other.inner)
}
}
impl<Rule: ValidateString> Hash for VStr<Rule> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state)
}
}
#[macro_export]
macro_rules! easy_rule {
($name:ident, err = $err:ty, $func:expr) => {
impl $crate::vstr::ValidateString for $name {
type Error = $err;
fn validate_str(s: &str) -> Result<(), Self::Error> {
$func(s)
}
}
};
}
#[macro_export]
macro_rules! fast_rule {
($name:ident, msg = $msg:expr, $func:expr) => {
impl $crate::vstr::ValidateString for $name {
type Error = &'static str;
fn validate_str(s: &str) -> Result<(), Self::Error> {
if $func(s) {
Ok(())
} else {
Err($msg)
}
}
}
};
($name:ty, err = $err:tt, msg = $msg:expr, $func:expr) => {
impl core::fmt::Display for $err {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt($msg, f)
}
}
impl core::fmt::Debug for $err {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt($msg, f)
}
}
#[cfg(feature = "std")]
impl std::error::Error for $err {}
impl $crate::vstr::ValidateString for $name {
type Error = $err;
fn validate_str(s: &str) -> Result<(), Self::Error> {
if $func(s) {
Ok(())
} else {
Err($err {})
}
}
}
};
}
#[cfg(feature = "alloc")]
impl<R: ValidateString> ToOwned for vstr<R> {
type Owned = Box<vstr<R>>;
fn to_owned(&self) -> Self::Owned {
Box::from(self)
}
}
macro_rules! with_cow_feature {
($($item:item)*) => {
$(
#[cfg(feature = "cow")]
$item
)*
}
}
with_cow_feature! {
use core::ops::Deref;
use alloc::borrow::Cow;
pub struct VCow<'a, R: ValidateString> {
_rule: PhantomData<R>,
surely_valid: bool,
cow: Cow<'a, str>,
}
impl<'a, R: ValidateString> VCow<'a, R> {
pub fn new() -> Self {
Self::default()
}
pub fn from_cow_str(s: Cow<'a, str>) -> Self {
Self {
_rule: PhantomData,
surely_valid: false,
cow: s,
}
}
pub fn from_str_slice(s: &'a str) -> Self {
Self::from_cow_str(Cow::Borrowed(s))
}
pub fn from_string(s: String) -> Self {
Self::from_cow_str(Cow::Owned(s))
}
pub fn from_vstr(s: &'a vstr<R>) -> Self {
Self::from_cow_str(Cow::Borrowed(s.as_ref()))
}
pub fn from_vstr_owned(s: <vstr<R> as ToOwned>::Owned) -> Self {
Self::from_cow_str(Cow::Owned(s.as_ref().into()))
}
pub fn from_cow_vstr(s: Cow<'a, vstr<R>>) -> Self {
Self {
_rule: PhantomData,
surely_valid: true,
cow: match s {
Cow::Owned(s) => Cow::Owned(s.to_string()),
Cow::Borrowed(s) => Cow::Borrowed(s.as_ref()),
},
}
}
pub fn new_unchecked(s: Cow<'a, str>) -> Self {
Self {
_rule: PhantomData,
surely_valid: true,
cow: s,
}
}
pub fn try_new(s: Cow<'a, str>) -> Result<Self, R::Error> {
R::validate_str(&s)?;
Ok(Self {
_rule: PhantomData,
surely_valid: true,
cow: s,
})
}
pub fn into_mark_surely_valid(this: Self, surely_valid: bool) -> Self {
Self {
surely_valid,
..this
}
}
pub fn mark_surely_valid_mut(this: &mut Self, surely_valid: bool) {
this.surely_valid = surely_valid;
}
pub fn decide_valid(this: &Self) -> Result<(), R::Error> {
R::validate_str(&this.cow)
}
pub fn into_validate(this: Self) -> Result<Self, R::Error> {
if this.surely_valid {
Ok(this)
} else {
Self::try_new(this.cow)
}
}
pub fn validate_mut(this: &mut Self) -> Result<(), R::Error> {
if this.surely_valid {
Ok(())
} else {
R::validate_str(&this.cow)?;
this.surely_valid = true;
Ok(())
}
}
pub fn is_certain(&self) -> bool {
self.surely_valid
}
pub fn into_cow(self) -> Cow<'a, str> {
self.cow
}
pub fn as_cow(&self) -> &Cow<'a, str> {
&self.cow
}
pub fn as_str(&self) -> &str {
self.cow.as_ref()
}
pub fn opt_as_vstr(&self) -> Option<&vstr<R>> {
if self.surely_valid {
Some(self.as_str().assume_valid())
} else {
None
}
}
#[allow(clippy::type_complexity)]
pub fn into_validated_cow(self) -> Result<Cow<'a, vstr<R>>, (Cow<'a, str>, R::Error)> {
if self.surely_valid {
Ok(match self.cow {
Cow::Owned(s) => Cow::Owned(s.assume_valid().to_owned()),
Cow::Borrowed(s) => Cow::Borrowed(s.assume_valid()),
})
} else {
match R::validate_str(&self.cow) {
Ok(()) => Ok(match self.cow {
Cow::Owned(s) => Cow::Owned(s.assume_valid().to_owned()),
Cow::Borrowed(s) => Cow::Borrowed(s.assume_valid()),
}),
Err(e) => Err((self.cow, e)),
}
}
}
}
impl<R: ValidateString> Display for VCow<'_, R> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.cow, f)
}
}
impl<R: ValidateString> Debug for VCow<'_, R> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.cow, f)
}
}
impl<R: ValidateString> Clone for VCow<'_, R> {
fn clone(&self) -> Self {
Self {
_rule: PhantomData,
surely_valid: self.surely_valid,
cow: self.cow.clone(),
}
}
}
impl<R: ValidateString> Default for VCow<'_, R> {
fn default() -> Self {
Self::from_cow_str(Cow::default())
}
}
#[cfg(feature = "serde")]
impl<R: ValidateString> Serialize for VCow<'_, R> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.cow.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, 'a, R: ValidateString> Deserialize<'de> for VCow<'a, R> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Cow::deserialize(deserializer).map(Self::from_cow_str)
}
}
impl<'a, R: ValidateString> Deref for VCow<'a, R> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<R: ValidateString> AsRef<str> for VCow<'_, R> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<R: ValidateString, S: ValidateString> PartialEq<VCow<'_, S>> for VCow<'_, R> {
fn eq(&self, other: &VCow<'_, S>) -> bool {
self.cow == other.cow
}
}
impl<R: ValidateString> PartialEq<str> for VCow<'_, R> {
fn eq(&self, other: &str) -> bool {
self.cow == other
}
}
impl<R: ValidateString> PartialEq<VCow<'_, R>> for str {
fn eq(&self, other: &VCow<'_, R>) -> bool {
self == other.cow
}
}
impl<R: ValidateString> PartialEq<&str> for VCow<'_, R> {
fn eq(&self, other: &&str) -> bool {
self.cow == *other
}
}
impl<R: ValidateString> PartialEq<VCow<'_, R>> for &str {
fn eq(&self, other: &VCow<'_, R>) -> bool {
*self == other.cow
}
}
impl<R: ValidateString> PartialEq<String> for VCow<'_, R> {
fn eq(&self, other: &String) -> bool {
self.cow == other.as_str()
}
}
impl<R: ValidateString> Eq for VCow<'_, R> {}
impl<R: ValidateString, S: ValidateString> PartialOrd<VCow<'_, R>> for VCow<'_, S> {
fn partial_cmp(&self, other: &VCow<'_, R>) -> Option<core::cmp::Ordering> {
self.cow.partial_cmp(&other.cow)
}
}
impl<R: ValidateString> PartialOrd<str> for VCow<'_, R> {
fn partial_cmp(&self, other: &str) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(other)
}
}
impl<R: ValidateString> PartialOrd<VCow<'_, R>> for str {
fn partial_cmp(&self, other: &VCow<'_, R>) -> Option<core::cmp::Ordering> {
self.partial_cmp(other.as_str())
}
}
impl<R: ValidateString> PartialOrd<&str> for VCow<'_, R> {
fn partial_cmp(&self, other: &&str) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(*other)
}
}
impl<R: ValidateString> PartialOrd<VCow<'_, R>> for &str {
fn partial_cmp(&self, other: &VCow<'_, R>) -> Option<core::cmp::Ordering> {
self.partial_cmp(&other.as_str())
}
}
impl<R: ValidateString> Ord for VCow<'_, R> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<R: ValidateString> Hash for VCow<'_, R> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<'a, R: ValidateString> From<Cow<'a, str>> for VCow<'a, R> {
fn from(cow: Cow<'a, str>) -> Self {
Self::from_cow_str(cow)
}
}
impl<'a, R: ValidateString> From<&'a str> for VCow<'a, R> {
fn from(s: &'a str) -> Self {
Self::from_str_slice(s)
}
}
impl<R: ValidateString> From<String> for VCow<'_, R> {
fn from(s: String) -> Self {
Self::from_string(s)
}
}
impl<'a, R: ValidateString> From<&'a vstr<R>> for VCow<'a, R> {
fn from(s: &'a vstr<R>) -> Self {
Self::from_vstr(s)
}
}
impl<R: ValidateString> From<Box<vstr<R>>> for VCow<'_, R> {
fn from(s: Box<vstr<R>>) -> Self {
Self::from_vstr_owned(s)
}
}
impl<'a, R: ValidateString> From<Cow<'a, vstr<R>>> for VCow<'a, R> {
fn from(cow: Cow<'a, vstr<R>>) -> Self {
Self::from_cow_vstr(cow)
}
}
impl<'a, R: ValidateString> From<VCow<'a, R>> for Cow<'a, str> {
fn from(cow: VCow<'a, R>) -> Self {
cow.cow
}
}
impl<'a: 'b, 'b, R: ValidateString> From<&'a VCow<'b, R>> for &'b str {
fn from(cow: &'a VCow<'b, R>) -> Self {
cow.as_str()
}
}
impl<'a: 'b, 'b, R: ValidateString> TryFrom<&'a VCow<'b, R>> for &'b vstr<R> {
type Error = R::Error;
fn try_from(cow: &'a VCow<'b, R>) -> Result<Self, Self::Error> {
match VCow::decide_valid(cow) {
Ok(()) => Ok(cow.opt_as_vstr().unwrap()),
Err(e) => Err(e),
}
}
}
}
#[cfg(test)]
mod tests {
use std::{ops::Not, sync::OnceLock};
use regex::Regex;
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::*;
struct NonEmpty;
easy_rule!(NonEmpty, err = &'static str, |s: &str| {
s.is_empty().not().then(|| ()).ok_or("empty")
});
struct AsciiOnly;
impl ValidateString for AsciiOnly {
type Error = &'static str;
fn validate_str(s: &str) -> Result<(), Self::Error> {
if s.is_ascii() {
Ok(())
} else {
Err("not ascii")
}
}
}
struct CompoundNEAO;
easy_rule!(CompoundNEAO, err = &'static str, |s: &str| {
NonEmpty::validate_str(s)?;
AsciiOnly::validate_str(s)?;
Ok(())
});
impl From<CompoundNEAO> for NonEmpty {
fn from(_: CompoundNEAO) -> Self {
NonEmpty
}
}
impl From<CompoundNEAO> for AsciiOnly {
fn from(_: CompoundNEAO) -> Self {
AsciiOnly
}
}
const EMAIL_HTML5: &str = r#"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"#;
static RE_EMAIL: OnceLock<Regex> = OnceLock::new();
fn email() -> &'static Regex {
RE_EMAIL.get_or_init(|| Regex::new(EMAIL_HTML5).unwrap())
}
#[derive(Debug, Error)]
enum EmailError {
#[error("invalid email")]
Invalid,
}
struct Email;
easy_rule!(Email, err = EmailError, |s: &str| {
email().is_match(s).then(|| ()).ok_or(EmailError::Invalid)
});
#[test]
fn it_works() {
let good = VStr::<NonEmpty>::try_validate("Hello, world!");
assert!(good.is_ok());
let bad = VStr::<NonEmpty>::try_validate("");
assert!(bad.is_err());
}
#[test]
fn ascii_only_works() {
let input = "is this good?";
let good: Result<&vstr<AsciiOnly>, _> = VStr::try_validate(input);
assert!(good.is_ok());
let input = "is this good? 🤔";
let bad: Result<&vstr<AsciiOnly>, _> = VStr::try_validate(input);
assert!(bad.is_err());
let input = "";
let good: Result<&vstr<AsciiOnly>, _> = VStr::try_validate(input);
assert!(good.is_ok());
}
#[test]
fn compound_works() {
let input = "Hello, world!";
let good: Result<&vstr<CompoundNEAO>, _> = VStr::try_validate(input);
assert!(good.is_ok());
let input = "";
let bad: Result<&vstr<CompoundNEAO>, _> = VStr::try_validate(input);
assert!(bad.is_err());
let input = "Hello, world! 🤔";
let bad: Result<&vstr<CompoundNEAO>, _> = VStr::try_validate(input);
assert!(bad.is_err());
}
#[test]
fn email_works() {
let input = "hana@example.com";
let good: Result<&vstr<Email>, _> = VStr::try_validate(input);
assert!(good.is_ok());
let input = "wow";
let bad: Result<&vstr<Email>, _> = VStr::try_validate(input);
assert!(bad.is_err());
}
#[test]
fn forcing_email() {
let input = "wow";
let assume_good: &vstr<Email> = VStr::assume_valid(input);
assert_eq!(assume_good.as_ref(), "wow");
assert!(assume_good.check().is_err());
}
#[test]
fn diff_rule_still_eq() {
let rule1 = "wow".validate::<NonEmpty>().unwrap();
let rule2 = "wow".validate::<AsciiOnly>().unwrap();
assert_eq!(rule1, rule2);
}
#[test]
fn order_mixed() {
let v1 = "a".validate::<NonEmpty>().unwrap();
let v2 = "b".validate::<AsciiOnly>().unwrap();
let s1 = "c";
assert!(v1 < v2);
assert!(v2 < s1);
}
#[test]
fn hash_mixed() {
macro_rules! hget {
($a:tt, $b:tt) => {{
let mut h1 = std::collections::hash_map::DefaultHasher::new();
$a.hash(&mut h1);
let mut h2 = std::collections::hash_map::DefaultHasher::new();
$b.hash(&mut h2);
(h1.finish(), h2.finish())
}};
}
let em1 = "a@example.com".validate::<Email>().unwrap();
let em2 = "a@example.com".validate::<NonEmpty>().unwrap();
let (h1, h2) = hget!(em1, em2);
assert_eq!(h1, h2);
let st1 = "a@example.com";
let (h1, h2) = hget!(em1, st1);
assert_eq!(h1, h2);
let em3 = "b@example.com".validate::<Email>().unwrap();
let (h1, h2) = hget!(em1, em3);
assert_ne!(h1, h2);
}
#[test]
fn arc_works() {
let v1 = "a".validate::<NonEmpty>().unwrap();
let v2 = "b".validate::<AsciiOnly>().unwrap();
let s1 = "c";
let a1 = Arc::new(v1);
let a2 = Arc::new(v2);
let a3 = Arc::new(s1);
assert!(*a1 < *a2);
assert!(*a2 < *a3);
}
#[test]
fn rc_works() {
let v1 = "a".validate::<NonEmpty>().unwrap();
let v2 = "b".validate::<AsciiOnly>().unwrap();
let s1 = "c";
let a1 = Rc::new(v1);
let a2 = Rc::new(v2);
let a3 = Rc::new(s1);
assert!(*a1 < *a2);
assert!(*a2 < *a3);
}
#[test]
fn box_works() {
let v1 = "a".validate::<NonEmpty>().unwrap();
let v2 = "b".validate::<AsciiOnly>().unwrap();
let s1 = "c";
let a1 = Box::new(v1);
let a2 = Box::new(v2);
let a3 = Box::new(s1);
assert!(*a1 < *a2);
assert!(*a2 < *a3);
}
#[test]
fn box_swapping() {
let v1 = "a".validate::<NonEmpty>().unwrap();
let v2 = "b".validate::<NonEmpty>().unwrap();
let mut a1 = Box::new(v1);
let mut a2 = Box::new(v2);
std::mem::swap(&mut a1, &mut a2);
assert_eq!(*a1, "b");
assert_eq!(*a2, "a");
}
#[test]
fn test_change_rules() {
let v1 = "a".validate::<NonEmpty>().unwrap();
assert!(v1.try_change_rules::<AsciiOnly>().is_ok());
let v2 = "a".validate::<AsciiOnly>().unwrap();
assert!(v2.try_change_rules::<NonEmpty>().is_ok());
let v3 = "".validate::<AsciiOnly>().unwrap();
assert!(v3.try_change_rules::<NonEmpty>().is_err());
let v4 = "a".validate::<CompoundNEAO>().unwrap();
assert!(v4.change_rules::<NonEmpty>() == "a");
assert!(v4.erase_rules() == "a");
}
#[test]
fn test_misc1() {
struct No;
easy_rule!(No, err = &'static str, |_: &str| Err("i won't accept anything"));
let s = "hello";
let v: &vstr<No> = vstr::assume_valid(s);
assert_eq!(v, "hello");
assert!(v.check().is_err());
}
#[test]
fn test_misc2() {
struct A;
easy_rule!(A, err = &'static str, |s: &str| s.contains("wow")
.then(|| ()).ok_or("no wow"));
struct B;
easy_rule!(B, err = &'static str, |s: &str| {
if s.contains("wow") || s.contains("bad") {
Ok(())
} else {
Err("no wow or bad")
}
});
impl From<A> for B {
fn from(_: A) -> Self {
B
}
}
let good = "wow bad";
let a: &vstr<A> = vstr::assume_valid(good); let _: &vstr<B> = a.change_rules(); }
#[test]
fn test_later1() {
let v1 = "hi@example.com".validate::<Later<Email>>().unwrap();
let v1 = v1.make_strict();
assert!(v1.is_ok());
let v2 = "notgood".validate::<Later<Email>>().unwrap();
let v2 = v2.make_strict();
assert!(v2.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn test_misc3() {
type EmailRule = Email;
#[derive(Deserialize)]
struct User {
email: Box<vstr<EmailRule>>,
}
let input = r#"{"email": "notgood"}"#;
let result = serde_json::from_str::<User>(input);
assert!(result.is_err());
let input = r#"{"email": "hi@example.com"}"#;
let result = serde_json::from_str::<User>(input);
assert!(result.is_ok());
assert!(result.unwrap().email.as_str() == "hi@example.com");
}
#[cfg(feature = "serde")]
#[test]
fn test_se() {
let v1 = "a".validate::<NonEmpty>().unwrap();
let v2 = "b".validate::<AsciiOnly>().unwrap();
let v1s = serde_json::to_string(&v1).unwrap();
let v2s = serde_json::to_string(&v2).unwrap();
let v1d: String = serde_json::from_str(&v1s).unwrap();
let v2d: String = serde_json::from_str(&v2s).unwrap();
assert_eq!(*v1, *v1d);
assert_eq!(*v2, *v2d);
}
#[cfg(feature = "serde")]
#[test]
fn test_de_reject() {
let input = "notemail";
let s = serde_json::to_string(input).unwrap();
let x: Result<&vstr<Email>, _> = serde_json::from_str(&s);
assert!(x.is_err());
let x: Result<Box<vstr<Email>>, _> = serde_json::from_str(&s);
assert!(x.is_err());
}
#[cfg(feature = "serde")]
#[test]
fn test_de_accept() {
let input = "a";
let s = serde_json::to_string(input).unwrap();
let x: Result<&vstr<NonEmpty>, _> = serde_json::from_str(&s);
assert!(x.is_ok());
assert_eq!(x.unwrap().as_ref(), "a");
let x: Result<Box<vstr<NonEmpty>>, _> = serde_json::from_str(&s);
assert!(x.is_ok());
assert_eq!(x.unwrap().as_ref(), "a");
}
#[cfg(feature = "serde")]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct User {
name: Box<vstr<NonEmpty>>,
email: Box<vstr<Email>>,
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_user() {
let user = User {
name: Box::from("John".validate().unwrap()),
email: Box::from("appleseed@example.com".validate().unwrap()),
};
let s = serde_json::to_string(&user).unwrap();
let user2: User = serde_json::from_str(&s).unwrap();
assert_eq!(user, user2);
}
}