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