1#[macro_export]
2macro_rules! custom_string {
3 (
4 $(#[$meta:meta])*,
5 $owned_struct_name:ident,
6 $ref_struct_name:ident,
7 $with_trait_name:ident,
8 $field_name:ident,
9 $validate_fn:expr
10 ) => {
11
12 $(#[$meta])*
13 #[derive(Clone, Ord, PartialOrd, Eq, Hash, Debug)]
14 pub struct $owned_struct_name {
15 value: String,
16 }
17
18 $(#[$meta])*
19 #[derive(Copy, Clone, Ord, PartialOrd, Eq, Hash, Debug)]
20 pub struct $ref_struct_name<'a> {
21 value: &'a str,
22 }
23
24 impl<S: AsRef<str>> PartialEq<S> for $owned_struct_name {
25 fn eq(&self, other: &S) -> bool {
26 self.value() == other.as_ref()
27 }
28 }
29
30 impl<'a, S: AsRef<str>> PartialEq<S> for $ref_struct_name<'a> {
31 fn eq(&self, other: &S) -> bool {
32 self.value() == other.as_ref()
33 }
34 }
35
36 impl $owned_struct_name {
37 pub fn validate(value: &str) -> Result<&str, &'static str> {
44 match $validate_fn(value) {
45 Ok(()) => Ok(value),
46 Err(e) => Err(e),
47 }
48 }
49
50 pub fn is_valid(value: &str) -> bool {
52 Self::validate(value).is_ok()
53 }
54 }
55
56 impl<'a> $ref_struct_name<'a> {
57 pub fn validate(value: &str) -> Result<&str, &'static str> {
64 $owned_struct_name::validate(value)
65 }
66
67 pub fn is_valid(value: &str) -> bool {
69 Self::validate(value).is_ok()
70 }
71 }
72
73 impl $owned_struct_name {
74 #[doc = concat!("Creates a new `", stringify!($owned_struct_name), "` from the `value`.")]
77 #[doc = ""]
78 #[doc = "# Safety"]
79 #[doc = "The `value` must be valid."]
80 pub unsafe fn new_unchecked<S>(value: S) -> Self
81 where
82 S: Into<String>,
83 {
84 let value: String = value.into();
85
86 debug_assert!(Self::is_valid(value.as_str()));
87
88 Self { value }
89 }
90
91 #[doc = concat!("Creates a new `", stringify!($owned_struct_name), "` from the `value`.")]
92 pub fn new<S>(value: S) -> Result<Self, &'static str>
93 where
94 S: AsRef<str> + Into<String>,
95 {
96 Ok(unsafe { Self::new_unchecked(Self::validate(value.as_ref())?) })
97 }
98 }
99
100 impl<'a> $ref_struct_name<'a> {
101 #[doc = concat!("Creates a new `", stringify!($ref_struct_name), "` from the `value`.")]
104 #[doc = ""]
105 #[doc = "# Safety"]
106 #[doc = "The `value` must be valid."]
107 pub unsafe fn new_unchecked(value: &'a str) -> Self {
108 debug_assert!(Self::is_valid(value));
109
110 Self { value }
111 }
112
113 #[doc = concat!("Creates a new `", stringify!($ref_struct_name), "` from the `value`.")]
114 pub fn new(value: &'a str) -> Result<Self, &'static str> {
115 Ok(unsafe { Self::new_unchecked(Self::validate(value)?) })
116 }
117 }
118
119 impl $owned_struct_name {
120 pub fn value(&self) -> &str {
124 self.value.as_str()
125 }
126
127 pub fn len(&self) -> usize {
129 self.value.len()
130 }
131
132 pub fn is_empty(&self) -> bool {
134 self.value.is_empty()
135 }
136 }
137
138 impl<'a> $ref_struct_name<'a> {
139 pub fn value(&self) -> &str {
143 self.value
144 }
145
146 pub fn len(&self) -> usize {
148 self.value.len()
149 }
150
151 pub fn is_empty(&self) -> bool {
153 self.value.is_empty()
154 }
155 }
156
157 impl $owned_struct_name {
158 pub fn to_ref<'a>(&'a self) -> $ref_struct_name<'a> {
162 unsafe { $ref_struct_name::new_unchecked(self.value.as_str()) }
163 }
164 }
165
166 impl<'a> $ref_struct_name<'a> {
167 pub fn to_owned(self) -> $owned_struct_name {
171 unsafe { $owned_struct_name::new_unchecked(self.value.to_string()) }
172 }
173 }
174
175 impl<'a> From<$ref_struct_name<'a>> for $owned_struct_name {
176 fn from(reference: $ref_struct_name<'a>) -> Self {
177 reference.to_owned()
178 }
179 }
180
181 impl From<$owned_struct_name> for String {
182 fn from(value: $owned_struct_name) -> Self {
183 value.value
184 }
185 }
186
187 impl<'a> From<$ref_struct_name<'a>> for String {
188 fn from(value: $ref_struct_name<'a>) -> Self {
189 value.to_string()
190 }
191 }
192
193 impl AsRef<str> for $owned_struct_name {
194 fn as_ref(&self) -> &str {
195 self.value.as_ref()
196 }
197 }
198
199 impl<'a> AsRef<str> for $ref_struct_name<'a> {
200 fn as_ref(&self) -> &str {
201 self.value
202 }
203 }
204
205 impl std::borrow::Borrow<str> for $owned_struct_name {
206 fn borrow(&self) -> &str {
207 self.value.borrow()
208 }
209 }
210
211 impl<'a> std::borrow::Borrow<str> for $ref_struct_name<'a> {
212 fn borrow(&self) -> &str {
213 self.value
214 }
215 }
216
217 impl std::fmt::Display for $owned_struct_name {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 write!(f, "{}", self.value)
220 }
221 }
222
223 impl<'a> std::fmt::Display for $ref_struct_name<'a> {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 write!(f, "{}", self.value)
226 }
227 }
228
229 #[doc = concat!("An element with a `", stringify!($owned_struct_name), "`.")]
230 pub trait $with_trait_name {
231 #[doc = concat!("Gets the `", stringify!($field_name), "`.")]
232 fn $field_name(&self) -> $ref_struct_name<'_>;
233 }
234
235 impl $with_trait_name for $owned_struct_name {
236 fn $field_name(&self) -> $ref_struct_name<'_> {
237 $ref_struct_name { value: self.value.as_str() }
238 }
239 }
240
241 impl $with_trait_name for $ref_struct_name<'_> {
242 fn $field_name(&self) -> $ref_struct_name<'_> {
243 *self
244 }
245 }
246 };
247}
248
249#[cfg(test)]
250#[allow(dead_code)]
251mod tests {
252 custom_string!(#[doc = "A lowercase string."], Lower, LowerRef, WithLower, lower, |s: &str| if !s
253 .as_bytes()
254 .iter()
255 .all(|c| c.is_ascii_lowercase())
256 {
257 Err("not lowercase")
258 } else {
259 Ok(())
260 });
261
262 #[test]
263 fn equals() {
264 let one: Lower = Lower::new("one").unwrap();
265 let two: Lower = Lower::new("two").unwrap();
266
267 assert_eq!(one, "one");
268 assert_eq!(one, one);
269 assert_ne!(one, "two");
270 assert_ne!(one, two);
271 }
272
273 #[test]
274 fn validation() {
275 assert!(Lower::is_valid("one"));
276 assert!(!Lower::is_valid("ONE"));
277
278 assert!(Lower::validate("one").is_ok());
279 assert_eq!(Lower::validate("ONE").err().unwrap(), "not lowercase");
280 }
281}