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 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}