newtype_uuid_macros/lib.rs
1//! Procedural macro for [`newtype-uuid`](https://docs.rs/newtype-uuid).
2//!
3//! This crate provides a procedural macro to help with creating
4//! [`newtype-uuid`](https://docs.rs/newtype-uuid) instances.
5//!
6//! For more information, see the documentation for [`impl_typed_uuid_kinds!`].
7//!
8//! # Examples
9//!
10//! Basic usage:
11//!
12//! ```
13//! use newtype_uuid::TypedUuidKind;
14//! use newtype_uuid_macros::impl_typed_uuid_kinds;
15//!
16//! impl_typed_uuid_kinds! {
17//! kinds = {
18//! User = {},
19//! BusinessUnit = {},
20//! },
21//! }
22//!
23//! // This generates empty UserKind and BusinessUnitKind enums implementing
24//! // TypedUuidKind, with the tags "user" and "business_unit" respectively.
25//! // Tags are snake_case versions of type names.
26//! assert_eq!(UserKind::tag().as_str(), "user");
27//! assert_eq!(BusinessUnitKind::tag().as_str(), "business_unit");
28//!
29//! // The macro also generates UserUuid and BusinessUnitUuid type aliases.
30//! let user_uuid = UserUuid::new_v4();
31//! let business_unit_uuid = BusinessUnitUuid::new_v4();
32//! ```
33//!
34//! For more details and examples, see the documentation for
35//! [`impl_typed_uuid_kinds!`].
36
37#![forbid(unsafe_code)]
38#![warn(missing_docs)]
39
40mod internals;
41
42use proc_macro::TokenStream;
43use quote::ToTokens;
44
45/// A function-like procedural macro for implementing typed UUID kinds.
46///
47/// This macro generates types that implement `TypedUuidKind` and corresponding
48/// type aliases for `TypedUuid<T>`. The macro provides an easy way to generate
49/// typed UUID kinds in bulk, and also to implement `JsonSchema` support with
50/// schemars 0.8.
51///
52/// # Basic usage
53///
54/// Invoke the `impl_typed_uuid_kinds!` macro within a path that's publicly
55/// visible. A dedicated crate for UUID kinds is recommended.
56///
57/// By default, for a kind `Foo`, this macro generates:
58///
59/// * A `FooKind` type that implements `TypedUuidKind`: `pub enum FooKind {}`.
60/// * A `FooUuid` type alias: `pub type FooUuid = TypedUuid<FooKind>;`.
61///
62/// ## Examples
63///
64/// ```
65/// use newtype_uuid::TypedUuidKind;
66/// use newtype_uuid_macros::impl_typed_uuid_kinds;
67///
68/// impl_typed_uuid_kinds! {
69/// kinds = {
70/// User = {},
71/// BusinessUnit = {},
72/// },
73/// }
74///
75/// // This generates empty UserKind and BusinessUnitKind enums implementing
76/// // TypedUuidKind, with the tags "user" and "business_unit" respectively.
77/// // Tags are snake_case versions of type names.
78/// assert_eq!(UserKind::tag().as_str(), "user");
79/// assert_eq!(BusinessUnitKind::tag().as_str(), "business_unit");
80///
81/// // The macro also generates UserUuid and BusinessUnitUuid type aliases.
82/// let user_uuid = UserUuid::new_v4();
83/// let business_unit_uuid = BusinessUnitUuid::new_v4();
84/// ```
85///
86/// * The generated `Kind` types always implement `Clone`, `Copy`, `Debug`,
87/// `Eq`, and `PartialEq`.
88/// * The `Kind` types are all empty enums, also known as *marker* or
89/// *uninhabited* enums. This means that values for these types cannot be
90/// created. (Using empty enums is the recommended approach for
91/// `newtype-uuid`).
92///
93/// # Per-kind settings
94///
95/// Kinds can be customized with the following settings:
96///
97/// - `attrs`: Attributes to apply to the kind enum, such as
98/// `#[derive(SomeTrait)]` or `#[cfg(feature = "some-feature")]`. *Optional,
99/// defaults to the global `attrs`.*
100/// - `tag`: The tag to use for the kind (a string literal). *Optional, defaults
101/// to the snake_case version of the type name.*
102/// - `type_name`: The name of the type to use for the kind (a Rust identifier).
103/// *Optional, defaults to `{Name}Kind`*.
104/// - `alias`: The name of the type alias to use for the kind (a Rust
105/// identifier). *Optional, defaults to `{Name}Uuid`*.
106///
107/// Per-kind customizations should generally be unnecessary; the conventionally
108/// generated type names should be sufficient for most use cases.
109///
110/// ## Examples
111///
112/// In this example, we derive `PartialOrd` and `Ord` for `MyUserKind`.
113///
114/// ```
115/// use newtype_uuid::TypedUuidKind;
116/// use newtype_uuid_macros::impl_typed_uuid_kinds;
117///
118/// impl_typed_uuid_kinds! {
119/// kinds = {
120/// User = {
121/// // This is a list of attributes surrounded by square brackets.
122/// attrs = [#[derive(PartialOrd, Ord)]],
123/// tag = "user",
124/// type_name = MyUserKind,
125/// },
126/// Organization = { tag = "org", alias = OrgUuid },
127/// },
128/// }
129///
130/// // This generates types with the specified names:
131/// assert_eq!(MyUserKind::tag().as_str(), "user");
132/// assert_eq!(OrganizationKind::tag().as_str(), "org");
133///
134/// let user_uuid = UserUuid::new_v4();
135/// let org_uuid = OrgUuid::new_v4();
136///
137/// // MyUserKind also implements `Ord`.
138/// static_assertions::assert_impl_all!(MyUserKind: Ord);
139/// ```
140///
141/// # Global settings
142///
143/// This macro accepts global settings under a top-level `settings` map:
144///
145/// - `attrs`: A list of attributes to apply to all generated `Kind` types.
146/// Per-kind attributes, if provided, will override these. *Optional, defaults
147/// to the empty list.*
148/// - `newtype_uuid_crate`: The name of the `newtype-uuid` crate (a Rust
149/// identifier). *Optional, defaults to `newtype_uuid`.*
150/// - `schemars08`: If defined, generates JSON Schema support for the given
151/// types using [`schemars` 0.8]. *Optional.*
152///
153/// ## JSON Schema support
154///
155/// If the `schemars08` global setting is defined, the macro generates JSON
156/// Schema support for the `Kind` instances using [schemars 0.8].
157///
158/// **To enable JSON Schema support, you'll need to enable `newtype-uuid`'s
159/// `schemars08` feature.**
160///
161/// Within `settings.schemars08`, the options are:
162///
163/// - `attrs`: A list of attributes to apply to all generated `JsonSchema`
164/// implementations. For example, if `schemars` is an optional dependency
165/// for the crate where the macro is being invoked, you might specify something
166/// like `[#[cfg(feature = "schemars")]]`.
167/// - `rust_type`: If defined, adds the `x-rust-type` extension to the schema,
168/// enabling automatic replacement with [`typify`] and other tools that
169/// support it. *Optional, defaults to not adding the extension.*
170///
171/// Automatic replacement enables an end-to-end workflow where the same UUID
172/// kinds can be shared between servers and clients.
173///
174/// `rust_type` is a map of the following options:
175///
176/// - `crate`: The crate name consumers will use to access these types.
177/// *Required.*
178/// - `version`: The versions of the crate that automatic replacement is
179/// supported for. *Required.*
180/// - `path`: The path to the module these types can be accessed from,
181/// including the crate name. *Required.*
182///
183/// For more about `x-rust-type`, see the [`typify` documentation].
184///
185/// [`schemars` 0.8]: https://docs.rs/schemars/0.8/schemars/
186/// [`typify`]: https://docs.rs/typify
187/// [`typify` documentation]:
188/// https://github.com/oxidecomputer/typify#rust---schema---rust
189///
190/// ## Examples
191///
192/// An example with all global settings defined:
193///
194/// ```
195/// use newtype_uuid::TypedUuidKind;
196/// use newtype_uuid_macros::impl_typed_uuid_kinds;
197///
198/// impl_typed_uuid_kinds! {
199/// settings = {
200/// attrs = [#[derive(PartialOrd, Ord)]],
201/// newtype_uuid_crate = newtype_uuid,
202/// schemars08 = {
203/// attrs = [#[cfg(feature = "schemars")]],
204/// rust_type = {
205/// crate = "my-crate",
206/// version = "0.1.0",
207/// path = "my_crate::types",
208/// },
209/// },
210/// },
211/// kinds = {
212/// User = {},
213/// Organization = {},
214/// Project = {},
215/// },
216/// }
217///
218/// let user_uuid = UserUuid::new_v4();
219/// let org_uuid = OrganizationUuid::new_v4();
220/// let project_uuid = ProjectUuid::new_v4();
221/// ```
222///
223/// For a working end-to-end example, see the
224/// [`e2e-example`](https://github.com/oxidecomputer/newtype-uuid/tree/main/e2e-example)
225/// directory in the newtype-uuid repository.
226#[proc_macro]
227pub fn impl_typed_uuid_kinds(input: TokenStream) -> TokenStream {
228 internals::impl_typed_uuid_kinds(input.into())
229 .into_token_stream()
230 .into()
231}