1#![cfg_attr(not(feature = "std"), no_std)]
110
111#[cfg(not(feature = "std"))]
112extern crate alloc;
113
114#[cfg(feature = "std")]
115use std::string::String;
116
117#[cfg(not(feature = "std"))]
118use alloc::string::String;
119
120#[cfg(feature = "std")]
121use std::fmt::{Display, Formatter, Result};
122
123#[cfg(not(feature = "std"))]
124use core::fmt::{Display, Formatter, Result};
125
126#[cfg(feature = "std")]
127use std::ops::Deref;
128
129#[cfg(not(feature = "std"))]
130use core::ops::Deref;
131
132pub trait Strip {
149 type Output;
150
151 fn strip(&self) -> Self::Output;
152}
153
154macro_rules! non {
155 (string, $name: ident<$param: ident>, $func: expr, $guarantee: expr, $guarantee2: expr) => {
156 #[doc = "A string that is known to "]
157 #[doc = $guarantee2]
158 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
159 #[repr(transparent)]
160 pub struct $name<$param = String>($param);
161
162 impl<$param> $name<$param>
163 where
164 $param: AsRef<str>
165 {
166 #[doc = "Creates a new `" ]
167 #[doc = stringify!($name) ]
168 #[doc = "` string if the string is "]
169 #[doc = $guarantee]
170 #[inline]
171 pub fn new(value: $param) -> Option<Self> {
172 if $func(&value) {
173 Some($name(value))
174 } else {
175 None
176 }
177 }
178
179 #[doc = "Creates a new `" ]
180 #[doc = stringify!($name) ]
181 #[doc = "` without checking the value."]
182 #[doc = "The value must be "]
185 #[doc = $guarantee]
186 #[inline]
187 pub unsafe fn new_unchecked(value: $param) -> Self {
188 Self(value)
189 }
190
191 #[inline]
193 pub fn into_inner(self) -> $param {
194 self.0
195 }
196
197 #[inline]
199 pub fn inner(&self) -> &$param {
200 &self.0
201 }
202 }
203
204 impl<$param> Deref for $name<$param> {
205 type Target = $param;
206
207 fn deref(&self) -> &Self::Target {
208 &self.0
209 }
210 }
211
212 impl<$param> From<$name<$param>> for String
213 where
214 $param: Into<String>
215 {
216 fn from(value: $name<$param>) -> Self {
217 value.0.into()
218 }
219 }
220
221 impl<$param> AsRef<str> for $name<$param>
222 where
223 $param: AsRef<str>
224 {
225 fn as_ref(&self) -> &str {
226 self.0.as_ref()
227 }
228 }
229
230 impl<$param> Display for $name<$param>
231 where
232 $param: Display
233 {
234 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
235 self.0.fmt(f)
236 }
237 }
238
239 impl<$param, Z> Strip for $name<$param>
240 where
241 $param: NotString + AsRef<str> + Deref<Target = Z>,
242 Z: AsRef<str> + Clone
243 {
244 type Output = $name<Z>;
245 fn strip(&self) -> Self::Output{
246 unsafe { $name::<Z>::new_unchecked((*self.0).clone()) }
247 }
248 }
249 };
250 (string, $name: ident<$param: ident, const $param2: ident: $param3: ident>, $func: expr, $guarantee: expr, $guarantee2: expr) => {
251 #[doc = "A string that is known to "]
252 #[doc = $guarantee2]
253 pub struct $name<const $param2: $param3, $param = String>($param);
254
255 impl<$param, const $param2: $param3> $name<$param2, $param>
256 where
257 $param: AsRef<str>
258 {
259 #[doc = "Creates a new `" ]
260 #[doc = stringify!($name) ]
261 #[doc = "` string if the string is "]
262 #[doc = $guarantee]
263 #[inline]
264 pub fn new(value: $param) -> Option<Self> {
265 if $func(&value) {
266 Some($name(value))
267 } else {
268 None
269 }
270 }
271
272 #[doc = "Creates a new `" ]
273 #[doc = stringify!($name) ]
274 #[doc = "` without checking the value."]
275 #[doc = "The value must be "]
278 #[doc = $guarantee]
279 #[inline]
280 pub unsafe fn new_unchecked(value: $param) -> Self {
281 Self(value)
282 }
283
284 #[inline]
286 pub fn into_inner(self) -> $param {
287 self.0
288 }
289
290 #[inline]
292 pub fn inner(&self) -> &$param {
293 &self.0
294 }
295 }
296
297 impl<$param, const $param2: $param3> Deref for $name<$param2, $param> {
298 type Target = $param;
299
300 fn deref(&self) -> &Self::Target {
301 &self.0
302 }
303 }
304
305 impl<$param, const $param2: $param3> From<$name<$param2, $param>> for String
306 where
307 $param: Into<String>
308 {
309 fn from(value: $name<$param2, $param>) -> Self {
310 value.0.into()
311 }
312 }
313
314 impl<$param, const $param2: $param3> AsRef<str> for $name<$param2, $param>
315 where
316 $param: AsRef<str>
317 {
318 fn as_ref(&self) -> &str {
319 self.0.as_ref()
320 }
321 }
322
323 impl<$param, const $param2: $param3> Display for $name<$param2, $param>
324 where
325 $param: Display
326 {
327 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
328 self.0.fmt(f)
329 }
330 }
331
332 impl<$param, Z, const $param2: $param3> Strip for $name<$param2, $param>
333 where
334 $param: NotString + AsRef<str> + Deref<Target = Z>,
335 Z: AsRef<str> + Clone
336 {
337 type Output = $name<$param2, Z>;
338 fn strip(&self) -> Self::Output{
339 unsafe { $name::<$param2, Z>::new_unchecked((*self.0).clone()) }
340 }
341 }
342 };
343}
344
345mod sealed {
346 pub trait NotString {}
347 impl<T> NotString for T where T: AsRef<str> + Clone {}
348}
349use sealed::NotString;
350
351non!(string, NonEmpty<T>,|x: &T| !x.as_ref().is_empty(),"not empty", "not be empty");
352non!(string, NonBlank<T>,|x: &T| !x.as_ref().chars().all(|y| y.is_whitespace()),"not only whitespace", "not be only whitespace");
353non!(string, ASCII<T>,|x: &T| x.as_ref().is_ascii(),"ascii encoded", "be ascii encoded");
354non!(string, ExactLength<T, const N: usize>, |x: &T| x.as_ref().chars().count() == N, "exactly N characters long", "have exactly N characters");
355non!(string, LowerCase<T>, |x: &T| { let s = x.as_ref(); s.to_lowercase() == s }, "all lowercase", "be all lowercase");
356non!(string, UpperCase<T>, |x: &T| { let s = x.as_ref(); s.to_uppercase() == s }, "all uppercase", "be all uppercase");
357non!(string, Trimmed<T>, |x: &T| x.as_ref() == x.as_ref().trim(), "trimmed (no leading or trailing whitespace)", "be trimmed");
358non!(string, Alphanumeric<T>, |x: &T| x.as_ref().chars().all(|c| c.is_alphanumeric()), "only alphanumeric characters", "contain only alphanumeric characters");
359
360#[cfg(test)]
361mod test {
362 use super::*;
363
364 #[test]
365 fn test_non_empty_new_some() {
366 let s = String::from("helo world");
367 let non_empty = NonEmpty::new(s);
368 assert!(non_empty.is_some());
369 }
370
371 #[test]
372 fn test_non_empty_new_none() {
373 let s = String::new();
374 let empty = NonEmpty::new(s);
375 assert!(empty.is_none());
376 }
377
378 #[test]
379 fn test_non_empty_new_unchecked() {
380 let s = String::from("helo world");
381 let non_empty = unsafe { NonEmpty::new_unchecked(s.clone()) };
382 assert_eq!(*non_empty, s);
383 }
384
385 #[test]
386 fn test_ascii_new_some() {
387 let s = String::from("helo world");
388 let ascii = ASCII::new(s);
389 assert!(ascii.is_some());
390 }
391
392 #[test]
393 fn test_ascii_new_none() {
394 let s = String::from("cześć");
395 let ascii = ASCII::new(s);
396 assert!(ascii.is_none());
397 }
398
399 #[test]
400 fn test_ascii_new_unchecked() {
401 let s = String::from("helo world");
402 let ascii = unsafe { ASCII::new_unchecked(s.clone()) };
403 assert_eq!(*ascii, s);
404 }
405
406 #[test]
407 fn test_non_blank_new_some() {
408 let s = String::from("helo world");
409 let nonblank = NonBlank::new(s);
410 assert!(nonblank.is_some());
411 }
412
413 #[test]
414 fn test_non_blank_new_none() {
415 let s = String::from(" \n");
416 let nonblank = NonBlank::new(s);
417 assert!(nonblank.is_none());
418 }
419
420 #[test]
421 fn test_non_blank_new_unchecked() {
422 let s = String::from("helo world");
423 let nonblank = unsafe { NonBlank::new_unchecked(s.clone()) };
424 assert_eq!(*nonblank, s);
425 }
426
427 #[test]
428 fn test_exact_length_new_some() {
429 let s = String::from("hi");
430 let exact_length = ExactLength::<2>::new(s);
431 assert!(exact_length.is_some());
432 }
433
434 #[test]
435 fn test_exact_length_new_none() {
436 let s = String::from("helo world");
437 let exact_length = ExactLength::<2>::new(s);
438 assert!(exact_length.is_none());
439 }
440
441 #[test]
442 fn test_exact_length_new_unchecked() {
443 let s = String::from("hi!");
444 let exact_length = unsafe { ExactLength::<3>::new_unchecked(s.clone()) };
445 assert_eq!(*exact_length, s);
446 }
447
448 #[test]
449 fn test_combinations() {
450 let s = String::from("helo world");
451 let nonblank = NonBlank::new(s.clone());
452 assert!(nonblank.is_some());
453 let ascii = ASCII::new(nonblank.unwrap());
454 assert!(ascii.is_some());
455 let ascii = ascii.unwrap();
456 assert_eq!(ascii.as_ref(), s);
457 }
458
459 #[test]
460 fn test_strip() {
461 let s = String::from("helo world");
462 let nonblank = NonBlank::new(s.clone());
463 assert!(nonblank.is_some());
464 let ascii = ASCII::new(nonblank.unwrap());
465 assert!(ascii.is_some());
466 let ascii = ascii.unwrap();
467 unsafe { assert_eq!(ascii.strip(), ASCII::new_unchecked(s)); }
468 }
469}