domain_key/macros.rs
1//! Macros for convenient key creation and domain definition in domain-key
2//!
3//! This module provides helpful macros that simplify the creation and usage
4//! of domain-specific keys, reducing boilerplate and improving ergonomics.
5
6// ============================================================================
7// STATIC KEY MACRO
8// ============================================================================
9
10/// Create a validated static key
11///
12/// This macro creates a static key with a compile-time emptiness check
13/// and full runtime validation via `try_from_static`, which enforces the
14/// domain's actual `MAX_LENGTH`. If validation fails, the macro **panics**.
15///
16/// # Arguments
17///
18/// * `$key_type` - The key type (e.g., `UserKey`)
19/// * `$key_str` - The string literal for the key
20///
21/// # Examples
22///
23/// ```rust
24/// use domain_key::{Key, Domain, KeyDomain, static_key};
25///
26/// #[derive(Debug)]
27/// struct AdminDomain;
28///
29/// impl Domain for AdminDomain {
30/// const DOMAIN_NAME: &'static str = "admin";
31/// }
32/// impl KeyDomain for AdminDomain {}
33///
34/// type AdminKey = Key<AdminDomain>;
35///
36/// // Basic checks at compile time, full validation at runtime (panics on failure)
37/// let admin_key = static_key!(AdminKey, "system_admin");
38/// assert_eq!(admin_key.as_str(), "system_admin");
39/// ```
40#[macro_export]
41macro_rules! static_key {
42 ($key_type:ty, $key_str:literal) => {{
43 // Compile-time validation - check that the key is non-empty.
44 // Length is validated against the domain's actual MAX_LENGTH at
45 // runtime via try_from_static below; a compile-time check against
46 // DEFAULT_MAX_KEY_LENGTH would be incorrect for domains that
47 // override MAX_LENGTH with a smaller value.
48 const _: () = {
49 let bytes = $key_str.as_bytes();
50 if bytes.is_empty() {
51 panic!(concat!("Static key cannot be empty: ", $key_str));
52 }
53 };
54
55 // Use the safe validation method
56 match <$key_type>::try_from_static($key_str) {
57 Ok(key) => key,
58 Err(e) => panic!("Invalid static key '{}': {}", $key_str, e),
59 }
60 }};
61}
62
63// ============================================================================
64// DOMAIN DEFINITION MACRO
65// ============================================================================
66
67/// Define a key domain with minimal boilerplate
68///
69/// This macro simplifies the definition of key domains by generating the
70/// required trait implementations automatically.
71///
72/// # Arguments
73///
74/// * `$name` - The domain struct name
75/// * `$domain_name` - The string name for the domain
76/// * `$max_length` - Optional maximum length (defaults to `DEFAULT_MAX_KEY_LENGTH`)
77///
78/// # Examples
79///
80/// ```rust
81/// use domain_key::{define_domain, Key};
82///
83/// // Simple domain with default settings
84/// define_domain!(UserDomain, "user");
85/// type UserKey = Key<UserDomain>;
86///
87/// // Domain with custom max length
88/// define_domain!(SessionDomain, "session", 128);
89/// type SessionKey = Key<SessionDomain>;
90///
91/// let user = UserKey::new("john_doe")?;
92/// let session = SessionKey::new("sess_abc123")?;
93/// # Ok::<(), domain_key::KeyParseError>(())
94/// ```
95#[macro_export]
96macro_rules! define_domain {
97 ($vis:vis $name:ident, $domain_name:literal) => {
98 $crate::define_domain!($vis $name, $domain_name, $crate::DEFAULT_MAX_KEY_LENGTH);
99 };
100
101 ($vis:vis $name:ident, $domain_name:literal, $max_length:expr) => {
102 #[derive(Debug)]
103 $vis struct $name;
104
105 impl $crate::Domain for $name {
106 const DOMAIN_NAME: &'static str = $domain_name;
107 }
108
109 impl $crate::KeyDomain for $name {
110 const MAX_LENGTH: usize = $max_length;
111 }
112 };
113}
114
115// ============================================================================
116// KEY TYPE ALIAS MACRO
117// ============================================================================
118
119/// Create a key type alias
120///
121/// This macro creates a type alias for a key.
122///
123/// # Arguments
124///
125/// * `$key_name` - The name for the key type alias
126/// * `$domain` - The domain type
127///
128/// # Examples
129///
130/// ```rust
131/// use domain_key::{define_domain, key_type};
132///
133/// define_domain!(UserDomain, "user");
134/// key_type!(UserKey, UserDomain);
135///
136/// let user = UserKey::new("john")?;
137/// # Ok::<(), domain_key::KeyParseError>(())
138/// ```
139#[macro_export]
140macro_rules! key_type {
141 ($vis:vis $key_name:ident, $domain:ty) => {
142 $vis type $key_name = $crate::Key<$domain>;
143 };
144}
145
146// ============================================================================
147// ID DOMAIN DEFINITION MACRO
148// ============================================================================
149
150/// Define an ID domain with minimal boilerplate
151///
152/// This macro simplifies the definition of ID domains by generating the
153/// required trait implementations automatically.
154///
155/// # Arguments
156///
157/// * `$name` - The domain struct name
158/// * `$domain_name` - The string name for the domain
159///
160/// # Examples
161///
162/// ```rust
163/// use domain_key::{define_id_domain, Id};
164///
165/// define_id_domain!(UserIdDomain, "user");
166/// type UserId = Id<UserIdDomain>;
167///
168/// let id = UserId::new(42).unwrap();
169/// assert_eq!(id.domain(), "user");
170/// ```
171#[macro_export]
172macro_rules! define_id_domain {
173 // Without explicit name — uses stringify
174 ($vis:vis $name:ident) => {
175 $crate::define_id_domain!(@inner $vis $name, stringify!($name));
176 };
177 // With explicit string literal
178 ($vis:vis $name:ident, $domain_name:literal) => {
179 $crate::define_id_domain!(@inner $vis $name, $domain_name);
180 };
181 (@inner $vis:vis $name:ident, $domain_name:expr) => {
182 #[derive(Debug)]
183 $vis struct $name;
184
185 impl $crate::Domain for $name {
186 const DOMAIN_NAME: &'static str = $domain_name;
187 }
188
189 impl $crate::IdDomain for $name {}
190 };
191}
192
193// ============================================================================
194// UUID DOMAIN DEFINITION MACRO
195// ============================================================================
196
197/// Define a UUID domain with minimal boilerplate
198///
199/// This macro simplifies the definition of UUID domains by generating the
200/// required trait implementations automatically.
201///
202/// Requires the `uuid` feature.
203///
204/// # Arguments
205///
206/// * `$name` - The domain struct name
207/// * `$domain_name` - The string name for the domain
208///
209/// # Examples
210///
211/// ```rust
212/// # #[cfg(feature = "uuid")]
213/// # {
214/// use domain_key::{define_uuid_domain, Uuid};
215///
216/// define_uuid_domain!(OrderUuidDomain, "order");
217/// type OrderUuid = Uuid<OrderUuidDomain>;
218///
219/// let id = OrderUuid::nil();
220/// assert_eq!(id.domain(), "order");
221/// # }
222/// ```
223#[cfg(feature = "uuid")]
224#[macro_export]
225macro_rules! define_uuid_domain {
226 // Without explicit name — uses stringify
227 ($vis:vis $name:ident) => {
228 $crate::define_uuid_domain!(@inner $vis $name, stringify!($name));
229 };
230 // With explicit string literal
231 ($vis:vis $name:ident, $domain_name:literal) => {
232 $crate::define_uuid_domain!(@inner $vis $name, $domain_name);
233 };
234 (@inner $vis:vis $name:ident, $domain_name:expr) => {
235 #[derive(Debug)]
236 $vis struct $name;
237
238 impl $crate::Domain for $name {
239 const DOMAIN_NAME: &'static str = $domain_name;
240 }
241
242 impl $crate::UuidDomain for $name {}
243 };
244}
245
246// ============================================================================
247// ID TYPE ALIAS MACRO
248// ============================================================================
249
250/// Create an Id type alias
251///
252/// This macro creates a type alias for a numeric Id.
253///
254/// # Arguments
255///
256/// * `$id_name` - The name for the Id type alias
257/// * `$domain` - The domain type (must implement `IdDomain`)
258///
259/// # Examples
260///
261/// ```rust
262/// use domain_key::{define_id_domain, id_type};
263///
264/// define_id_domain!(UserIdDomain, "user");
265/// id_type!(UserId, UserIdDomain);
266///
267/// let id = UserId::new(1).unwrap();
268/// assert_eq!(id.get(), 1);
269/// ```
270#[macro_export]
271macro_rules! id_type {
272 ($vis:vis $id_name:ident, $domain:ty) => {
273 $vis type $id_name = $crate::Id<$domain>;
274 };
275}
276
277// ============================================================================
278// UUID TYPE ALIAS MACRO
279// ============================================================================
280
281/// Create a Uuid type alias
282///
283/// This macro creates a type alias for a typed Uuid.
284///
285/// Requires the `uuid` feature.
286///
287/// # Arguments
288///
289/// * `$uuid_name` - The name for the Uuid type alias
290/// * `$domain` - The domain type (must implement `UuidDomain`)
291///
292/// # Examples
293///
294/// ```rust
295/// # #[cfg(feature = "uuid")]
296/// # {
297/// use domain_key::{define_uuid_domain, uuid_type};
298///
299/// define_uuid_domain!(OrderUuidDomain, "order");
300/// uuid_type!(OrderUuid, OrderUuidDomain);
301///
302/// let id = OrderUuid::nil();
303/// assert!(id.is_nil());
304/// # }
305/// ```
306#[cfg(feature = "uuid")]
307#[macro_export]
308macro_rules! uuid_type {
309 ($vis:vis $uuid_name:ident, $domain:ty) => {
310 $vis type $uuid_name = $crate::Uuid<$domain>;
311 };
312}
313
314// ============================================================================
315// COMBINED DOMAIN + TYPE ALIAS MACROS
316// ============================================================================
317
318/// Define an Id domain and type alias in one step
319///
320/// This is a convenience macro that combines [`define_id_domain!`] and [`id_type!`].
321///
322/// # Examples
323///
324/// ```rust
325/// use domain_key::{define_id, Id};
326///
327/// define_id!(UserIdDomain => UserId);
328///
329/// let id = UserId::new(42).unwrap();
330/// assert_eq!(id.domain(), "UserId");
331/// ```
332#[macro_export]
333macro_rules! define_id {
334 ($vis:vis $domain:ident => $alias:ident) => {
335 $crate::define_id_domain!(@inner $vis $domain, stringify!($alias));
336 $crate::id_type!($vis $alias, $domain);
337 };
338}
339
340/// Define a Uuid domain and type alias in one step
341///
342/// This is a convenience macro that combines [`define_uuid_domain!`] and [`uuid_type!`].
343///
344/// Requires the `uuid` feature.
345///
346/// # Examples
347///
348/// ```rust
349/// # #[cfg(feature = "uuid")]
350/// # {
351/// use domain_key::{define_uuid, Uuid};
352///
353/// define_uuid!(OrderUuidDomain => OrderUuid);
354///
355/// let id = OrderUuid::nil();
356/// assert_eq!(id.domain(), "OrderUuid");
357/// # }
358/// ```
359#[cfg(feature = "uuid")]
360#[macro_export]
361macro_rules! define_uuid {
362 ($vis:vis $domain:ident => $alias:ident) => {
363 $crate::define_uuid_domain!(@inner $vis $domain, stringify!($alias));
364 $crate::uuid_type!($vis $alias, $domain);
365 };
366}
367
368// ============================================================================
369// BATCH KEY CREATION MACRO
370// ============================================================================
371
372/// Create multiple keys at once with error handling
373///
374/// This macro simplifies the creation of multiple keys from string literals
375/// or expressions, with automatic error collection.
376///
377/// # Examples
378///
379/// ```rust
380/// use domain_key::{define_domain, key_type, batch_keys};
381///
382/// define_domain!(UserDomain, "user");
383/// key_type!(UserKey, UserDomain);
384///
385/// // Create multiple keys, collecting any errors
386/// let result = batch_keys!(UserKey => [
387/// "user_1",
388/// "user_2",
389/// "user_3",
390/// ]);
391///
392/// match result {
393/// Ok(keys) => println!("Created {} keys", keys.len()),
394/// Err(errors) => println!("Failed to create {} keys", errors.len()),
395/// }
396/// ```
397#[macro_export]
398macro_rules! batch_keys {
399 ($key_type:ty => [$($key_str:expr),* $(,)?]) => {{
400 use $crate::__private::{Vec, ToString};
401 let mut keys = Vec::new();
402 let mut errors = Vec::new();
403
404 $(
405 match <$key_type>::new($key_str) {
406 Ok(key) => keys.push(key),
407 Err(e) => errors.push(($key_str.to_string(), e)),
408 }
409 )*
410
411 if errors.is_empty() {
412 Ok(keys)
413 } else {
414 Err(errors)
415 }
416 }};
417}
418
419// ============================================================================
420// TESTING HELPERS
421// ============================================================================
422
423/// Generate test cases for key domains
424///
425/// This macro creates comprehensive test cases for a domain,
426/// testing both valid and invalid keys. The macro generates a `domain_tests`
427/// submodule with test functions.
428///
429/// **Important**: This macro must be used at module level, not inside functions.
430///
431/// # Arguments
432///
433/// * `$domain` - The domain type to test
434/// * `valid` - Array of string literals that should be valid keys
435/// * `invalid` - Array of string literals that should be invalid keys
436///
437/// # Examples
438///
439/// ```rust
440/// use domain_key::{define_domain, test_domain};
441///
442/// define_domain!(MyTestDomain, "test");
443///
444/// // This creates a `domain_tests` module with test functions
445/// test_domain!(MyTestDomain {
446/// valid: [
447/// "valid_key",
448/// "another_valid",
449/// "key123",
450/// ],
451/// invalid: [
452/// "",
453/// "key with spaces",
454/// ]
455/// });
456/// ```
457///
458/// The generated tests will:
459/// - Test that all valid keys can be created successfully
460/// - Test that all invalid keys fail to create with appropriate errors
461/// - Test basic domain properties (name, max length, etc.)
462///
463/// Note: This macro should be used at module level, not inside functions.
464#[macro_export]
465macro_rules! test_domain {
466 // With explicit module name: test_domain!(MyDomain as my_domain_tests { ... })
467 //
468 // Use this form when invoking the macro more than once in the same module to
469 // avoid the `mod domain_tests` name collision.
470 ($domain:ty as $mod_name:ident {
471 valid: [$($valid:literal),* $(,)?],
472 invalid: [$($invalid:literal),* $(,)?] $(,)?
473 }) => {
474 #[cfg(test)]
475 mod $mod_name {
476 use super::*;
477
478 type TestKey = $crate::Key<$domain>;
479
480 #[test]
481 fn test_valid_keys() {
482 $(
483 let key = TestKey::new($valid);
484 assert!(key.is_ok(), "Key '{}' should be valid: {:?}", $valid, key.err());
485 )*
486 }
487
488 #[test]
489 fn test_invalid_keys() {
490 $(
491 let key = TestKey::new($invalid);
492 assert!(key.is_err(), "Key '{}' should be invalid", $invalid);
493 )*
494 }
495
496 #[test]
497 fn test_domain_properties() {
498 use $crate::Domain;
499 use $crate::KeyDomain;
500
501 // Test domain constants
502 assert!(!<$domain>::DOMAIN_NAME.is_empty());
503 assert!(<$domain>::MAX_LENGTH > 0);
504
505 // Test validation help if available
506 if let Some(help) = <$domain>::validation_help() {
507 assert!(!help.is_empty());
508 }
509 }
510 }
511 };
512
513 // Without explicit module name (backward-compatible) — defaults to `domain_tests`.
514 //
515 // Note: Only one such invocation is allowed per module. If you need a second
516 // invocation in the same module, supply an explicit name with the `as` form above.
517 ($domain:ty {
518 valid: [$($valid:literal),* $(,)?],
519 invalid: [$($invalid:literal),* $(,)?] $(,)?
520 }) => {
521 $crate::test_domain!($domain as domain_tests {
522 valid: [$($valid),*],
523 invalid: [$($invalid),*]
524 });
525 };
526}
527
528// ============================================================================
529// TESTS
530// ============================================================================
531
532#[cfg(test)]
533mod tests {
534 use crate::{Domain, Key, KeyDomain};
535 #[cfg(not(feature = "std"))]
536 use alloc::string::ToString;
537 #[cfg(not(feature = "std"))]
538 use alloc::vec::Vec;
539
540 // Test define_domain macro
541 define_domain!(MacroTestDomain, "macro_test");
542 type MacroTestKey = Key<MacroTestDomain>;
543
544 // Test define_domain with custom max length
545 define_domain!(LongDomain, "long", 256);
546 #[allow(dead_code)]
547 type LongKey = Key<LongDomain>;
548
549 #[test]
550 fn define_domain_sets_name_and_max_length() {
551 assert_eq!(MacroTestDomain::DOMAIN_NAME, "macro_test");
552 assert_eq!(MacroTestDomain::MAX_LENGTH, crate::DEFAULT_MAX_KEY_LENGTH);
553
554 assert_eq!(LongDomain::DOMAIN_NAME, "long");
555 assert_eq!(LongDomain::MAX_LENGTH, 256);
556 }
557
558 #[test]
559 fn static_key_validates_and_creates_key() {
560 let key = static_key!(MacroTestKey, "static_test");
561 assert_eq!(key.as_str(), "static_test");
562 assert_eq!(key.domain(), "macro_test");
563 }
564
565 #[test]
566 fn key_type_creates_usable_alias() {
567 key_type!(TestKey, MacroTestDomain);
568 let key = TestKey::new("test_key").unwrap();
569 assert_eq!(key.as_str(), "test_key");
570 }
571
572 #[test]
573 fn batch_keys_collects_all_valid_keys() {
574 let result = batch_keys!(MacroTestKey => [
575 "key1",
576 "key2",
577 "key3",
578 ]);
579
580 assert!(result.is_ok());
581 let keys = result.unwrap();
582 assert_eq!(keys.len(), 3);
583 assert_eq!(keys[0].as_str(), "key1");
584 assert_eq!(keys[1].as_str(), "key2");
585 assert_eq!(keys[2].as_str(), "key3");
586 }
587
588 #[test]
589 fn batch_keys_returns_errors_for_invalid_entries() {
590 let result = batch_keys!(MacroTestKey => [
591 "valid_key",
592 "", // This should fail
593 "another_valid",
594 ]);
595
596 assert!(result.is_err());
597 let errors = result.unwrap_err();
598 assert_eq!(errors.len(), 1);
599 assert_eq!(errors[0].0, "");
600 }
601
602 #[test]
603 fn define_id_creates_domain_and_alias() {
604 define_id!(TestIdDomain2 => TestId2);
605 let id = TestId2::new(42).unwrap();
606 assert_eq!(id.get(), 42);
607 assert_eq!(id.domain(), "TestId2");
608 }
609
610 #[cfg(feature = "uuid")]
611 #[test]
612 fn define_uuid_creates_domain_and_alias() {
613 define_uuid!(TestUuidDomain2 => TestUuid2);
614 let id = TestUuid2::nil();
615 assert!(id.is_nil());
616 assert_eq!(id.domain(), "TestUuid2");
617 }
618
619 // Test the test_domain macro - use it at module level
620 #[cfg(test)]
621 mod test_domain_macro_test {
622
623 // Define a test domain specifically for this test
624 define_domain!(pub TestMacroDomain, "test_macro");
625
626 // Apply the test_domain macro
627 test_domain!(TestMacroDomain {
628 valid: ["valid_key", "another_valid", "key123",],
629 invalid: ["",]
630 });
631 }
632}