refinement_types/type_regex.rs
1//! Type-level regular expressions.
2
3pub use regex::Regex;
4
5use crate::static_str::StaticStr;
6
7#[doc(hidden)]
8pub mod import {
9 pub use std::{fmt, sync::LazyLock};
10}
11
12/// Represents static regular expressions (as returned in [`get`] of [`TypeRegex`]).
13///
14/// [`get`]: TypeRegex::get
15pub type StaticRegex = &'static Regex;
16
17/// Represents type-level regular expressions.
18pub trait TypeRegex {
19 /// Returns the compiled regular expression.
20 fn get() -> StaticRegex;
21}
22
23/// The `invalid regex` literal.
24pub const INVALID: StaticStr = "invalid regex";
25
26/// Lifts strings to type-level regular expressions.
27///
28/// ```
29/// use refinement_types::type_regex;
30///
31/// type_regex!(Integer = "^0|[1-9][0-9]*$");
32/// ```
33///
34/// Is equivalent to:
35///
36/// ```
37/// use std::{fmt, sync::LazyLock};
38///
39/// use refinement_types::{Regex, StaticRegex, TypeRegex};
40///
41/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
42/// struct Integer;
43///
44/// impl TypeRegex for Integer {
45/// fn get() -> StaticRegex {
46/// static REGEX: LazyLock<Regex> = LazyLock::new(|| {
47/// Regex::new("^0|[1-9][0-9]*$").expect("invalid regex")
48/// });
49///
50/// LazyLock::force(®EX)
51/// }
52/// }
53///
54/// impl fmt::Display for Integer {
55/// fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56/// Self::get().fmt(formatter)
57/// }
58/// }
59/// ```
60#[macro_export]
61macro_rules! type_regex {
62 ($vis: vis $name: ident = $regex: expr $(=> $doc: expr)?) => {
63 $(
64 #[doc = $doc]
65 )?
66 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
67 $vis struct $name;
68
69 impl $crate::type_regex::TypeRegex for $name {
70 fn get() -> $crate::type_regex::StaticRegex {
71 use $crate::type_regex::import::LazyLock;
72
73 static REGEX: LazyLock<$crate::type_regex::Regex> = LazyLock::new(|| {
74 $crate::type_regex::Regex::new($regex).expect($crate::type_regex::INVALID)
75 });
76
77 LazyLock::force(®EX)
78 }
79 }
80
81 impl $crate::type_regex::import::fmt::Display for $name {
82 fn fmt(
83 &self, formatter: &mut $crate::type_regex::import::fmt::Formatter<'_>
84 ) -> $crate::type_regex::import::fmt::Result {
85 use $crate::type_regex::TypeRegex;
86
87 Self::get().fmt(formatter)
88 }
89 }
90 };
91}