use std::fmt;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ValidationSceneError {
#[error("字符串长度超过32个字符限制")]
TooLong,
#[error("包含非法字符: {0}")]
InvalidChar(char),
}
const VALID_CHARS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$&'()*+,/:;=?@-._~";
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SceneString(String);
impl SceneString {
pub fn new(s: &str) -> Result<Self, ValidationSceneError> {
if s.len() > 32 {
return Err(ValidationSceneError::TooLong);
}
for c in s.chars() {
if !VALID_CHARS.contains(c) {
return Err(ValidationSceneError::InvalidChar(c));
}
}
Ok(SceneString(s.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl FromStr for SceneString {
type Err = ValidationSceneError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
SceneString::new(s)
}
}
impl fmt::Display for SceneString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<String> for SceneString {
type Error = ValidationSceneError;
fn try_from(value: String) -> Result<Self, Self::Error> {
SceneString::new(&value)
}
}
impl TryFrom<&str> for SceneString {
type Error = ValidationSceneError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
SceneString::new(value)
}
}
impl std::ops::Deref for SceneString {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_string() {
assert!(SceneString::new("hello123").is_ok());
assert!(SceneString::new("HELLO_WORLD-123").is_ok());
assert!(SceneString::new("test!#$&'()*+,/:;=?@-._~").is_ok());
let long_string = "a".repeat(33);
assert!(matches!(
SceneString::new(&long_string),
Err(ValidationSceneError::TooLong)
));
let max_length_string = "a".repeat(32);
assert!(SceneString::new(&max_length_string).is_ok());
assert!(matches!(
SceneString::new("hello world"), Err(ValidationSceneError::InvalidChar(' '))
));
assert!(matches!(
SceneString::new("中文"), Err(ValidationSceneError::InvalidChar(_))
));
}
#[test]
fn test_conversions() {
let from_str = "test123".parse::<SceneString>();
assert!(from_str.is_ok());
let from_string = SceneString::try_from("test123".to_string());
assert!(from_string.is_ok());
let from_str_ref = SceneString::try_from("test123");
assert!(from_str_ref.is_ok());
}
}