wechat_minapp/new_type/
scene.rs

1use std::fmt;
2use std::str::FromStr;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum ValidationSceneError {
7    #[error("字符串长度超过32个字符限制")]
8    TooLong,
9    #[error("包含非法字符: {0}")]
10    InvalidChar(char),
11}
12
13// 合法的字符集:数字、大小写英文、!#$&'()*+,/:;=?@-._~
14const VALID_CHARS: &str =
15    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$&'()*+,/:;=?@-._~";
16
17/// 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct SceneString(String);
20
21impl SceneString {
22    /// 创建新的 SceneString,进行验证
23    pub fn new(s: &str) -> Result<Self, ValidationSceneError> {
24        if s.len() > 32 {
25            return Err(ValidationSceneError::TooLong);
26        }
27
28        for c in s.chars() {
29            if !VALID_CHARS.contains(c) {
30                return Err(ValidationSceneError::InvalidChar(c));
31            }
32        }
33
34        Ok(SceneString(s.to_string()))
35    }
36
37    /// 获取内部字符串引用
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41
42    /// 获取内部字符串
43    pub fn into_inner(self) -> String {
44        self.0
45    }
46
47    /// 检查字符串是否为空
48    pub fn is_empty(&self) -> bool {
49        self.0.is_empty()
50    }
51
52    /// 获取字符串长度
53    pub fn len(&self) -> usize {
54        self.0.len()
55    }
56}
57
58impl FromStr for SceneString {
59    type Err = ValidationSceneError;
60
61    fn from_str(s: &str) -> Result<Self, Self::Err> {
62        SceneString::new(s)
63    }
64}
65
66impl fmt::Display for SceneString {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "{}", self.0)
69    }
70}
71
72impl TryFrom<String> for SceneString {
73    type Error = ValidationSceneError;
74
75    fn try_from(value: String) -> Result<Self, Self::Error> {
76        SceneString::new(&value)
77    }
78}
79
80impl TryFrom<&str> for SceneString {
81    type Error = ValidationSceneError;
82
83    fn try_from(value: &str) -> Result<Self, Self::Error> {
84        SceneString::new(value)
85    }
86}
87
88// 为方便使用,实现 Deref
89impl std::ops::Deref for SceneString {
90    type Target = str;
91
92    fn deref(&self) -> &Self::Target {
93        &self.0
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_valid_string() {
103        // 测试合法字符串
104        assert!(SceneString::new("hello123").is_ok());
105        assert!(SceneString::new("HELLO_WORLD-123").is_ok());
106        assert!(SceneString::new("test!#$&'()*+,/:;=?@-._~").is_ok());
107
108        // 测试长度限制
109        let long_string = "a".repeat(33);
110        assert!(matches!(
111            SceneString::new(&long_string),
112            Err(ValidationSceneError::TooLong)
113        ));
114
115        // 测试边界情况
116        let max_length_string = "a".repeat(32);
117        assert!(SceneString::new(&max_length_string).is_ok());
118
119        // 测试非法字符
120        assert!(matches!(
121            SceneString::new("hello world"), // 包含空格
122            Err(ValidationSceneError::InvalidChar(' '))
123        ));
124
125        assert!(matches!(
126            SceneString::new("中文"), // 包含中文
127            Err(ValidationSceneError::InvalidChar(_))
128        ));
129    }
130
131    #[test]
132    fn test_conversions() {
133        // 测试 FromStr
134        let from_str = "test123".parse::<SceneString>();
135        assert!(from_str.is_ok());
136
137        // 测试 TryFrom
138        let from_string = SceneString::try_from("test123".to_string());
139        assert!(from_string.is_ok());
140
141        let from_str_ref = SceneString::try_from("test123");
142        assert!(from_str_ref.is_ok());
143    }
144}