paramdef/subtype/
text.rs

1//! Standard text subtypes.
2//!
3//! Text subtypes provide semantic meaning for string values:
4//!
5//! ## Basic
6//! - [`Plain`] - Plain text
7//! - [`MultiLine`] - Multi-line text
8//!
9//! ## Network
10//! - [`Email`] - Email address
11//! - [`Url`] - URL
12//! - [`Domain`] - Domain name
13//! - [`IpAddressV4`] - IPv4 address
14//! - [`IpAddressV6`] - IPv6 address
15//! - [`Hostname`] - Hostname
16//! - [`MacAddress`] - MAC address
17//!
18//! ## Paths
19//! - [`FilePath`] - File path
20//! - [`DirPath`] - Directory path
21//! - [`FileName`] - File name
22//!
23//! ## Security
24//! - [`Secret`] - Generic secret
25//! - [`Password`] - Password
26//! - [`ApiKey`] - API key
27//! - [`BearerToken`] - Bearer token
28//!
29//! ## Identifiers
30//! - [`Uuid`] - UUID
31//! - [`Slug`] - URL slug
32//!
33//! ## Date/Time
34//! - [`DateTime`] - ISO 8601 datetime
35//! - [`Date`] - ISO 8601 date
36//! - [`Time`] - ISO 8601 time
37//! - [`Iso8601Duration`] - ISO 8601 duration (PT1H30M)
38//! - [`Cron`] - Cron expression
39//! - [`Timezone`] - Timezone identifier
40//!
41//! ## Structured Data
42//! - [`Json`] - JSON
43//! - [`Yaml`] - YAML
44//! - [`Toml`] - TOML
45//! - [`Xml`] - XML
46//! - [`Markdown`] - Markdown text
47//! - [`Html`] - HTML markup
48//!
49//! ## Code
50//! - [`Sql`] - SQL query
51//! - [`Regex`] - Regular expression
52//! - [`Expression`] - Expression/formula
53//! - [`JavaScript`] - JavaScript code
54//! - [`Python`] - Python code
55//! - [`Rust`] - Rust code
56//!
57//! ## Contact
58//! - [`PhoneNumber`] - Phone number (E.164)
59//!
60//! ## Color
61//! - [`HexColor`] - Hex color (#RRGGBB)
62//!
63//! ## Localization
64//! - [`Country`] - ISO 3166 country code
65//! - [`Language`] - ISO 639 language code
66//! - [`CurrencyCode`] - ISO 4217 currency code
67//!
68//! ## Versioning
69//! - [`Semver`] - Semantic version
70
71use crate::define_text_subtype;
72
73// === Basic ===
74
75define_text_subtype!(Plain, "plain");
76define_text_subtype!(MultiLine, "multiline", multiline: true);
77
78// === Network ===
79
80define_text_subtype!(Email, "email", pattern: r"^[^@\s]+@[^@\s]+\.[^@\s]+$", placeholder: "user@example.com");
81define_text_subtype!(Url, "url", pattern: r"^https?://", placeholder: "https://example.com");
82define_text_subtype!(Domain, "domain", placeholder: "example.com");
83define_text_subtype!(IpAddressV4, "ip_v4", pattern: r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", placeholder: "192.168.1.1");
84define_text_subtype!(IpAddressV6, "ip_v6", placeholder: "::1");
85define_text_subtype!(Hostname, "hostname", placeholder: "localhost");
86define_text_subtype!(MacAddress, "mac_address", pattern: r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", placeholder: "00:1A:2B:3C:4D:5E");
87
88// === Paths ===
89
90define_text_subtype!(FilePath, "file_path", placeholder: "/path/to/file");
91define_text_subtype!(DirPath, "dir_path", placeholder: "/path/to/directory");
92define_text_subtype!(FileName, "file_name", placeholder: "filename.ext");
93
94// === Security ===
95
96define_text_subtype!(Secret, "secret", sensitive: true);
97define_text_subtype!(Password, "password", sensitive: true);
98define_text_subtype!(ApiKey, "api_key", sensitive: true);
99define_text_subtype!(BearerToken, "bearer_token", sensitive: true);
100
101// === Identifiers ===
102
103define_text_subtype!(Uuid, "uuid", pattern: r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", placeholder: "00000000-0000-0000-0000-000000000000");
104define_text_subtype!(Slug, "slug", pattern: r"^[a-z0-9]+(?:-[a-z0-9]+)*$", placeholder: "my-slug");
105
106// === Date/Time ===
107
108define_text_subtype!(DateTime, "datetime", placeholder: "2024-01-01T00:00:00Z");
109define_text_subtype!(Date, "date", pattern: r"^\d{4}-\d{2}-\d{2}$", placeholder: "2024-01-01");
110define_text_subtype!(Time, "time", pattern: r"^\d{2}:\d{2}(:\d{2})?$", placeholder: "12:00:00");
111define_text_subtype!(Iso8601Duration, "iso8601_duration", pattern: r"^P", placeholder: "PT1H30M");
112define_text_subtype!(Cron, "cron", placeholder: "*/5 * * * *");
113define_text_subtype!(Timezone, "timezone", placeholder: "America/New_York");
114
115// === Structured Data ===
116
117define_text_subtype!(Json, "json", multiline: true);
118define_text_subtype!(Yaml, "yaml", multiline: true);
119define_text_subtype!(Toml, "toml", multiline: true);
120define_text_subtype!(Xml, "xml", multiline: true);
121define_text_subtype!(Markdown, "markdown", multiline: true);
122define_text_subtype!(Html, "html", multiline: true);
123
124// === Code ===
125
126define_text_subtype!(Sql, "sql", code: "sql");
127define_text_subtype!(Regex, "regex", placeholder: "^pattern$");
128define_text_subtype!(Expression, "expression", placeholder: "{{ value }}");
129define_text_subtype!(JavaScript, "javascript", code: "javascript");
130define_text_subtype!(Python, "python", code: "python");
131define_text_subtype!(Rust, "rust", code: "rust");
132
133// === Contact ===
134
135define_text_subtype!(PhoneNumber, "phone_number", placeholder: "+1 555 123 4567");
136
137// === Color ===
138
139define_text_subtype!(HexColor, "hex_color", pattern: r"^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$", placeholder: "#FF5733");
140
141// === Localization ===
142
143define_text_subtype!(Country, "country", pattern: r"^[A-Z]{2}$", placeholder: "US");
144define_text_subtype!(Language, "language", pattern: r"^[a-z]{2}(-[A-Z]{2})?$", placeholder: "en");
145define_text_subtype!(CurrencyCode, "currency_code", pattern: r"^[A-Z]{3}$", placeholder: "USD");
146
147// === Versioning ===
148
149define_text_subtype!(Semver, "semver", pattern: r"^\d+\.\d+\.\d+", placeholder: "1.0.0");
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::subtype::TextSubtype;
155
156    // === Basic Tests ===
157
158    #[test]
159    fn test_plain() {
160        assert_eq!(Plain::name(), "plain");
161        assert!(!Plain::is_multiline());
162        assert!(!Plain::is_sensitive());
163    }
164
165    #[test]
166    fn test_multiline() {
167        assert_eq!(MultiLine::name(), "multiline");
168        assert!(MultiLine::is_multiline());
169    }
170
171    // === Network Tests ===
172
173    #[test]
174    fn test_email() {
175        assert_eq!(Email::name(), "email");
176        assert!(Email::pattern().is_some());
177        assert_eq!(Email::placeholder(), Some("user@example.com"));
178    }
179
180    #[test]
181    fn test_url() {
182        assert_eq!(Url::name(), "url");
183        assert!(Url::pattern().is_some());
184        assert_eq!(Url::placeholder(), Some("https://example.com"));
185    }
186
187    #[test]
188    fn test_domain() {
189        assert_eq!(Domain::name(), "domain");
190        assert_eq!(Domain::placeholder(), Some("example.com"));
191    }
192
193    #[test]
194    fn test_ip_v4() {
195        assert_eq!(IpAddressV4::name(), "ip_v4");
196        assert!(IpAddressV4::pattern().is_some());
197    }
198
199    #[test]
200    fn test_ip_v6() {
201        assert_eq!(IpAddressV6::name(), "ip_v6");
202    }
203
204    #[test]
205    fn test_hostname() {
206        assert_eq!(Hostname::name(), "hostname");
207        assert_eq!(Hostname::placeholder(), Some("localhost"));
208    }
209
210    // === Path Tests ===
211
212    #[test]
213    fn test_file_path() {
214        assert_eq!(FilePath::name(), "file_path");
215    }
216
217    #[test]
218    fn test_dir_path() {
219        assert_eq!(DirPath::name(), "dir_path");
220    }
221
222    #[test]
223    fn test_file_name() {
224        assert_eq!(FileName::name(), "file_name");
225    }
226
227    // === Security Tests ===
228
229    #[test]
230    fn test_secret() {
231        assert_eq!(Secret::name(), "secret");
232        assert!(Secret::is_sensitive());
233    }
234
235    #[test]
236    fn test_password() {
237        assert_eq!(Password::name(), "password");
238        assert!(Password::is_sensitive());
239    }
240
241    #[test]
242    fn test_api_key() {
243        assert_eq!(ApiKey::name(), "api_key");
244        assert!(ApiKey::is_sensitive());
245    }
246
247    #[test]
248    fn test_bearer_token() {
249        assert_eq!(BearerToken::name(), "bearer_token");
250        assert!(BearerToken::is_sensitive());
251    }
252
253    // === Identifier Tests ===
254
255    #[test]
256    fn test_uuid() {
257        assert_eq!(Uuid::name(), "uuid");
258        assert!(Uuid::pattern().is_some());
259    }
260
261    #[test]
262    fn test_slug() {
263        assert_eq!(Slug::name(), "slug");
264        assert!(Slug::pattern().is_some());
265    }
266
267    // === Date/Time Tests ===
268
269    #[test]
270    fn test_datetime() {
271        assert_eq!(DateTime::name(), "datetime");
272    }
273
274    #[test]
275    fn test_date() {
276        assert_eq!(Date::name(), "date");
277        assert!(Date::pattern().is_some());
278    }
279
280    #[test]
281    fn test_time() {
282        assert_eq!(Time::name(), "time");
283        assert!(Time::pattern().is_some());
284    }
285
286    // === Structured Data Tests ===
287
288    #[test]
289    fn test_json() {
290        assert_eq!(Json::name(), "json");
291        assert!(Json::is_multiline());
292    }
293
294    #[test]
295    fn test_yaml() {
296        assert_eq!(Yaml::name(), "yaml");
297        assert!(Yaml::is_multiline());
298    }
299
300    #[test]
301    fn test_toml() {
302        assert_eq!(Toml::name(), "toml");
303        assert!(Toml::is_multiline());
304    }
305
306    #[test]
307    fn test_xml() {
308        assert_eq!(Xml::name(), "xml");
309        assert!(Xml::is_multiline());
310    }
311
312    // === Code Tests ===
313
314    #[test]
315    fn test_sql() {
316        assert_eq!(Sql::name(), "sql");
317        assert!(Sql::is_multiline());
318        assert_eq!(Sql::code_language(), Some("sql"));
319    }
320
321    #[test]
322    fn test_regex() {
323        assert_eq!(Regex::name(), "regex");
324        assert_eq!(Regex::placeholder(), Some("^pattern$"));
325    }
326
327    #[test]
328    fn test_expression() {
329        assert_eq!(Expression::name(), "expression");
330    }
331
332    #[test]
333    fn test_javascript() {
334        assert_eq!(JavaScript::name(), "javascript");
335        assert_eq!(JavaScript::code_language(), Some("javascript"));
336    }
337
338    #[test]
339    fn test_python() {
340        assert_eq!(Python::name(), "python");
341        assert_eq!(Python::code_language(), Some("python"));
342    }
343
344    #[test]
345    fn test_rust() {
346        assert_eq!(Rust::name(), "rust");
347        assert_eq!(Rust::code_language(), Some("rust"));
348    }
349
350    // === Network (new) Tests ===
351
352    #[test]
353    fn test_mac_address() {
354        assert_eq!(MacAddress::name(), "mac_address");
355        assert!(MacAddress::pattern().is_some());
356        assert_eq!(MacAddress::placeholder(), Some("00:1A:2B:3C:4D:5E"));
357    }
358
359    // === Date/Time (new) Tests ===
360
361    #[test]
362    fn test_iso8601_duration() {
363        assert_eq!(Iso8601Duration::name(), "iso8601_duration");
364        assert!(Iso8601Duration::pattern().is_some());
365        assert_eq!(Iso8601Duration::placeholder(), Some("PT1H30M"));
366    }
367
368    #[test]
369    fn test_cron() {
370        assert_eq!(Cron::name(), "cron");
371        assert_eq!(Cron::placeholder(), Some("*/5 * * * *"));
372    }
373
374    #[test]
375    fn test_timezone() {
376        assert_eq!(Timezone::name(), "timezone");
377        assert_eq!(Timezone::placeholder(), Some("America/New_York"));
378    }
379
380    // === Structured Data (new) Tests ===
381
382    #[test]
383    fn test_markdown() {
384        assert_eq!(Markdown::name(), "markdown");
385        assert!(Markdown::is_multiline());
386    }
387
388    #[test]
389    fn test_html() {
390        assert_eq!(Html::name(), "html");
391        assert!(Html::is_multiline());
392    }
393
394    // === Contact Tests ===
395
396    #[test]
397    fn test_phone_number() {
398        assert_eq!(PhoneNumber::name(), "phone_number");
399        assert_eq!(PhoneNumber::placeholder(), Some("+1 555 123 4567"));
400    }
401
402    // === Color Tests ===
403
404    #[test]
405    fn test_hex_color() {
406        assert_eq!(HexColor::name(), "hex_color");
407        assert!(HexColor::pattern().is_some());
408        assert_eq!(HexColor::placeholder(), Some("#FF5733"));
409    }
410
411    // === Localization Tests ===
412
413    #[test]
414    fn test_country() {
415        assert_eq!(Country::name(), "country");
416        assert!(Country::pattern().is_some());
417        assert_eq!(Country::placeholder(), Some("US"));
418    }
419
420    #[test]
421    fn test_language() {
422        assert_eq!(Language::name(), "language");
423        assert!(Language::pattern().is_some());
424        assert_eq!(Language::placeholder(), Some("en"));
425    }
426
427    #[test]
428    fn test_currency_code() {
429        assert_eq!(CurrencyCode::name(), "currency_code");
430        assert!(CurrencyCode::pattern().is_some());
431        assert_eq!(CurrencyCode::placeholder(), Some("USD"));
432    }
433
434    // === Versioning Tests ===
435
436    #[test]
437    fn test_semver() {
438        assert_eq!(Semver::name(), "semver");
439        assert!(Semver::pattern().is_some());
440        assert_eq!(Semver::placeholder(), Some("1.0.0"));
441    }
442}