Skip to main content

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}