use crate::stdx::error::is_empty_or_blank_string::StringContentError;
#[cfg(feature = "serde")]
use serde::{de::Error, Deserialize, Serialize};
use std::{
fmt::{Display, Formatter, Result as FmtResult},
marker::PhantomData,
};
pub trait StringContentValidator: Sized {
fn validate_and_create(input: String) -> Result<ValidatedString<Self>, StringContentError>;
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct ValidatedString<T: StringContentValidator>(String, PhantomData<T>);
impl<T: StringContentValidator> ValidatedString<T> {
pub fn new(string: String) -> Result<Self, StringContentError> {
T::validate_and_create(string)
}
pub unsafe fn new_unchecked(string: String) -> Self {
Self(string, PhantomData)
}
pub fn into_inner(self) -> String {
self.0
}
}
impl<T: StringContentValidator> Display for ValidatedString<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.0.fmt(f)
}
}
#[cfg(feature = "serde")]
impl<'de, T: StringContentValidator> Deserialize<'de> for ValidatedString<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
ValidatedString::<T>::new(string).map_err(Error::custom)
}
}
pub struct NonEmptyValidator;
impl StringContentValidator for NonEmptyValidator {
fn validate_and_create(input: String) -> Result<ValidatedString<Self>, StringContentError> {
if input.is_empty() {
return Err(StringContentError::Empty);
}
Ok(ValidatedString(input, PhantomData))
}
}
pub struct NonBlankValidator;
impl StringContentValidator for NonBlankValidator {
fn validate_and_create(input: String) -> Result<ValidatedString<Self>, StringContentError> {
if input.trim().is_empty() {
return Err(StringContentError::Blank(input.to_string()));
}
Ok(ValidatedString(input, PhantomData))
}
}
pub type NonEmptyString = ValidatedString<NonEmptyValidator>;
pub type NonBlankString = ValidatedString<NonBlankValidator>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_non_empty_string_new_success() {
let non_blank_input = "Hello".to_string();
let non_blank_result = NonEmptyString::new(non_blank_input.clone());
assert!(non_blank_result.is_ok());
assert_eq!(non_blank_result.unwrap().into_inner(), non_blank_input);
let blank_input = " \t\n".to_string();
let blank_result = NonEmptyString::new(blank_input.clone());
assert!(blank_result.is_ok());
assert_eq!(blank_result.unwrap().into_inner(), blank_input);
}
#[test]
fn test_non_empty_string_new_empty() {
let input = "".to_string();
let result = NonEmptyString::new(input);
assert!(result.is_err());
}
#[test]
fn test_non_empty_string_new_unchecked() {
let non_blank_input = "Hello".to_string();
let non_blank_result = unsafe { NonEmptyString::new_unchecked(non_blank_input.clone()) };
assert_eq!(non_blank_result.into_inner(), non_blank_input);
let blank_input = " \t\n".to_string();
let blank_result = unsafe { NonEmptyString::new_unchecked(blank_input.clone()) };
assert_eq!(blank_result.into_inner(), blank_input);
}
#[test]
fn test_non_blank_string_new_success() {
let input = "Hello".to_string();
let result = NonBlankString::new(input.clone());
assert!(result.is_ok());
assert_eq!(result.unwrap().into_inner(), input);
}
#[test]
fn test_non_blank_string_new_blank() {
let input = " \t\n".to_string();
let result = NonBlankString::new(input);
assert!(result.is_err());
}
#[test]
fn test_non_blank_string_new_unchecked() {
let input = "Hello".to_string();
let result = unsafe { NonBlankString::new_unchecked(input.clone()) };
assert_eq!(result.into_inner(), input);
}
#[test]
fn test_display_non_empty_string() {
let input = "Display Test".to_string();
let non_empty = NonEmptyString::new(input.clone()).unwrap();
assert_eq!(format!("{}", non_empty), input);
}
#[test]
fn test_display_non_blank_string() {
let input = "Display Test".to_string();
let non_blank = NonBlankString::new(input.clone()).unwrap();
assert_eq!(format!("{}", non_blank), input);
}
#[test]
#[cfg(feature = "serde")]
fn test_non_empty_string_serde() {
let input = "Serialize Test".to_string();
let non_empty = NonEmptyString::new(input.clone()).unwrap();
let serialized = serde_json::to_string(&non_empty).unwrap();
assert_eq!(serialized, format!("\"{}\"", input));
let deserialized: NonEmptyString = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.into_inner(), input);
}
#[test]
#[cfg(feature = "serde")]
fn test_non_blank_string_serde() {
let input = "Serialize Test".to_string();
let non_blank = NonBlankString::new(input.clone()).unwrap();
let serialized = serde_json::to_string(&non_blank).unwrap();
assert_eq!(serialized, format!("\"{}\"", input));
let deserialized: NonBlankString = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.into_inner(), input);
}
}