Skip to main content

rusdantic_types/
string.rs

1//! Constrained string types.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::ops::Deref;
6
7/// A non-empty string (length >= 1).
8///
9/// Rejects empty strings at construction and deserialization time.
10///
11/// # Example
12/// ```
13/// use rusdantic_types::NonEmptyString;
14/// let s = NonEmptyString::new("hello").unwrap();
15/// assert_eq!(s.as_str(), "hello");
16/// assert!(NonEmptyString::new("").is_err());
17/// ```
18#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct NonEmptyString(String);
20
21impl NonEmptyString {
22    /// Create a new non-empty string.
23    pub fn new(value: impl Into<String>) -> Result<Self, String> {
24        let s = value.into();
25        if s.is_empty() {
26            Err("string must not be empty".to_string())
27        } else {
28            Ok(Self(s))
29        }
30    }
31
32    /// Get the inner string.
33    pub fn into_inner(self) -> String {
34        self.0
35    }
36
37    /// Get a string slice.
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41}
42
43impl Deref for NonEmptyString {
44    type Target = str;
45    fn deref(&self) -> &str {
46        &self.0
47    }
48}
49
50impl fmt::Debug for NonEmptyString {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "NonEmptyString({:?})", self.0)
53    }
54}
55
56impl fmt::Display for NonEmptyString {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62impl Serialize for NonEmptyString {
63    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
64        self.0.serialize(serializer)
65    }
66}
67
68impl<'de> Deserialize<'de> for NonEmptyString {
69    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
70        let s = String::deserialize(deserializer)?;
71        Self::new(s).map_err(serde::de::Error::custom)
72    }
73}
74
75/// A validated email string.
76///
77/// Validates the email format at construction and deserialization time
78/// using the same regex as the `email` validator.
79///
80/// # Example
81/// ```
82/// use rusdantic_types::EmailStr;
83/// let e = EmailStr::new("user@example.com").unwrap();
84/// assert!(EmailStr::new("invalid").is_err());
85/// ```
86#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct EmailStr(String);
88
89/// Get the shared email regex from rusdantic-core (single source of truth).
90fn get_email_regex() -> &'static regex::Regex {
91    rusdantic_core::rules::email::get_email_regex()
92}
93
94impl EmailStr {
95    /// Create a new validated email string.
96    pub fn new(value: impl Into<String>) -> Result<Self, String> {
97        let s = value.into();
98        if s.is_empty() || !s.contains('@') || s.len() > 254 {
99            return Err("invalid email format".to_string());
100        }
101        if !get_email_regex().is_match(&s) {
102            return Err("invalid email format".to_string());
103        }
104        Ok(Self(s))
105    }
106
107    /// Get the inner string.
108    pub fn into_inner(self) -> String {
109        self.0
110    }
111
112    /// Get a string slice.
113    pub fn as_str(&self) -> &str {
114        &self.0
115    }
116}
117
118impl Deref for EmailStr {
119    type Target = str;
120    fn deref(&self) -> &str {
121        &self.0
122    }
123}
124
125impl fmt::Debug for EmailStr {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "EmailStr({:?})", self.0)
128    }
129}
130
131impl fmt::Display for EmailStr {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{}", self.0)
134    }
135}
136
137impl Serialize for EmailStr {
138    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
139        self.0.serialize(serializer)
140    }
141}
142
143impl<'de> Deserialize<'de> for EmailStr {
144    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
145        let s = String::deserialize(deserializer)?;
146        Self::new(s).map_err(serde::de::Error::custom)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_non_empty_string_valid() {
156        assert!(NonEmptyString::new("hello").is_ok());
157        assert!(NonEmptyString::new("a").is_ok());
158    }
159
160    #[test]
161    fn test_non_empty_string_invalid() {
162        assert!(NonEmptyString::new("").is_err());
163    }
164
165    #[test]
166    fn test_non_empty_string_deref() {
167        let s = NonEmptyString::new("hello").unwrap();
168        assert_eq!(&*s, "hello");
169        assert_eq!(s.len(), 5);
170    }
171
172    #[test]
173    fn test_non_empty_string_serialize() {
174        let s = NonEmptyString::new("test").unwrap();
175        let json = serde_json::to_value(&s).unwrap();
176        assert_eq!(json, "test");
177    }
178
179    #[test]
180    fn test_non_empty_string_deserialize() {
181        let s: NonEmptyString = serde_json::from_value(serde_json::json!("hello")).unwrap();
182        assert_eq!(s.as_str(), "hello");
183    }
184
185    #[test]
186    fn test_non_empty_string_deserialize_empty() {
187        let result: Result<NonEmptyString, _> = serde_json::from_value(serde_json::json!(""));
188        assert!(result.is_err());
189    }
190
191    #[test]
192    fn test_email_str_valid() {
193        assert!(EmailStr::new("user@example.com").is_ok());
194        assert!(EmailStr::new("first.last@sub.domain.com").is_ok());
195    }
196
197    #[test]
198    fn test_email_str_invalid() {
199        assert!(EmailStr::new("").is_err());
200        assert!(EmailStr::new("not-email").is_err());
201        assert!(EmailStr::new("@example.com").is_err());
202    }
203
204    #[test]
205    fn test_email_str_serialize() {
206        let e = EmailStr::new("user@example.com").unwrap();
207        let json = serde_json::to_value(&e).unwrap();
208        assert_eq!(json, "user@example.com");
209    }
210
211    #[test]
212    fn test_email_str_deserialize() {
213        let e: EmailStr = serde_json::from_value(serde_json::json!("user@example.com")).unwrap();
214        assert_eq!(e.as_str(), "user@example.com");
215    }
216}