oxide_generation_macros/lib.rs
1//! Procedural macro for [`oxide-generation`](https://docs.rs/oxide-generation).
2//!
3//! This crate provides a procedural macro to help with creating
4//! [`oxide-generation`](https://docs.rs/oxide-generation) instances.
5//!
6//! For more information, see the documentation for [`impl_typed_generation_kinds!`].
7//!
8//! # Examples
9//!
10//! Basic usage:
11//!
12//! ```
13//! use oxide_generation::TypedGenerationKind;
14//! use oxide_generation_macros::impl_typed_generation_kinds;
15//!
16//! impl_typed_generation_kinds! {
17//! kinds = {
18//! User = {},
19//! BusinessUnit = {},
20//! },
21//! }
22//!
23//! // This generates empty UserGenerationKind and BusinessUnitGenerationKind
24//! // enums implementing TypedGenerationKind, with the tags "user" and
25//! // "business_unit" respectively. Tags are snake_case versions of type names.
26//! assert_eq!(UserGenerationKind::TAG.as_str(), "user");
27//! assert_eq!(BusinessUnitGenerationKind::TAG.as_str(), "business_unit");
28//!
29//! // The macro also generates UserGeneration and BusinessUnitGeneration type aliases.
30//! let user_generation = UserGeneration::new();
31//! let business_unit_generation = BusinessUnitGeneration::new();
32//! ```
33//!
34//! For more details and examples, see the documentation for
35//! [`impl_typed_generation_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 generation kinds.
46///
47/// This macro generates types that implement `TypedGenerationKind` and corresponding
48/// type aliases for `TypedGeneration<T>`. The macro provides an easy way to generate
49/// typed generation kinds in bulk, and also to implement `JsonSchema` support with
50/// schemars 0.8.
51///
52/// # Basic usage
53///
54/// Invoke the `impl_typed_generation_kinds!` macro within a path that's publicly
55/// visible. A dedicated crate for generation kinds is recommended.
56///
57/// By default, for a kind `Foo`, this macro generates:
58///
59/// * A `FooGenerationKind` type that implements `TypedGenerationKind`: `pub enum FooGenerationKind {}`.
60/// * A `FooGeneration` type alias: `pub type FooGeneration = TypedGeneration<FooGenerationKind>;`.
61///
62/// ## Examples
63///
64/// ```
65/// use oxide_generation::TypedGenerationKind;
66/// use oxide_generation_macros::impl_typed_generation_kinds;
67///
68/// impl_typed_generation_kinds! {
69/// kinds = {
70/// User = {},
71/// BusinessUnit = {},
72/// },
73/// }
74///
75/// // This generates empty UserGenerationKind and BusinessUnitGenerationKind
76/// // enums implementing TypedGenerationKind, with the tags "user" and
77/// // "business_unit" respectively. Tags are snake_case versions of type names.
78/// assert_eq!(UserGenerationKind::TAG.as_str(), "user");
79/// assert_eq!(BusinessUnitGenerationKind::TAG.as_str(), "business_unit");
80///
81/// // The macro also generates UserGeneration and BusinessUnitGeneration type aliases.
82/// let user_generation = UserGeneration::new();
83/// let business_unit_generation = BusinessUnitGeneration::new();
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/// `oxide-generation`).
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}GenerationKind`*.
104/// - `alias`: The name of the type alias to use for the kind (a Rust
105/// identifier). *Optional, defaults to `{Name}Generation`*.
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 oxide_generation::TypedGenerationKind;
116/// use oxide_generation_macros::impl_typed_generation_kinds;
117///
118/// impl_typed_generation_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 = OrgGeneration },
127/// },
128/// }
129///
130/// // This generates types with the specified names:
131/// assert_eq!(MyUserKind::TAG.as_str(), "user");
132/// assert_eq!(OrganizationGenerationKind::TAG.as_str(), "org");
133///
134/// let user_generation = UserGeneration::new();
135/// let org_generation = OrgGeneration::new();
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/// - `oxide_generation_crate`: The name of the `oxide-generation` crate (a Rust
149/// identifier). *Optional, defaults to `oxide_generation`.*
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 `oxide-generation`'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 generation
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 oxide_generation::TypedGenerationKind;
196/// use oxide_generation_macros::impl_typed_generation_kinds;
197///
198/// impl_typed_generation_kinds! {
199/// settings = {
200/// attrs = [#[derive(PartialOrd, Ord)]],
201/// oxide_generation_crate = oxide_generation,
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_generation = UserGeneration::new();
219/// let org_generation = OrganizationGeneration::new();
220/// let project_generation = ProjectGeneration::new();
221/// ```
222///
223/// For a working end-to-end example, see the
224/// [`e2e-example`](https://github.com/oxidecomputer/oxide-generation/tree/main/e2e-example)
225/// directory in the oxide-generation repository.
226#[proc_macro]
227pub fn impl_typed_generation_kinds(input: TokenStream) -> TokenStream {
228 internals::impl_typed_generation_kinds(input.into())
229 .into_token_stream()
230 .into()
231}