Skip to main content

typedflake_macros/
lib.rs

1//! Procedural macros for [TypedFlake](https://docs.rs/typedflake).
2//!
3//! Use them through the `typedflake` crate, which re-exports and documents
4//! them.
5
6use proc_macro::TokenStream;
7use syn::{DeriveInput, parse_macro_input};
8
9mod id;
10mod integrations;
11mod node;
12mod util;
13
14/// Declares a Snowflake-style ID type.
15///
16/// Apply it to a tuple struct with one private `i64` or `u64` field, above any
17/// `#[derive]`:
18///
19/// ```ignore
20/// #[typedflake(epoch = "2025-01-01")]
21/// pub struct UserId(i64);
22/// ```
23///
24/// # Options
25///
26/// - `epoch = "YYYY-MM-DD"`: instant timestamps are measured from, at midnight
27///   UTC. Required unless `format` is given.
28/// - `bits(timestamp = N, node = N, sequence = N)`: bit allocation. Defaults to
29///   10 node bits, 12 sequence bits, and the remaining bits for the timestamp
30///   (41 for `i64`, 42 for `u64`). The widths may add up to less than the
31///   integer's capacity.
32/// - `format = CONSTANT`: a shared `Format` constant, instead of `epoch` and
33///   `bits`.
34/// - `node = Type`: a `TypedNode` struct that splits the node field into named
35///   parts. Defaults to a plain `u32` node number.
36/// - `alphabet = CONSTANT`: an `Alphabet` for a compact text form. Adds
37///   `encode` and `decode`, and implements `EncodedId`.
38///
39/// # Generated items
40///
41/// The type implements `Id`, `Debug`, `Display`, `FromStr`, `Clone`, `Copy`,
42/// `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `TryFrom<i64>`,
43/// `TryFrom<u64>`, and `From<Self>` for its integer, and is
44/// `#[repr(transparent)]`. Every `Id` method is also available as an inherent
45/// method, so no trait import is needed.
46#[proc_macro_attribute]
47pub fn typedflake(args: TokenStream, input: TokenStream) -> TokenStream {
48    id::expand(args.into(), input.into())
49        .unwrap_or_else(syn::Error::into_compile_error)
50        .into()
51}
52
53/// Splits the node field of an ID into named parts.
54///
55/// Apply it to a struct with named `u8`, `u16`, or `u32` fields, each marked
56/// with `#[node(bits = N)]`. Fields pack in declaration order, most
57/// significant first, and their widths must add up to the node bits of the ID
58/// format.
59///
60/// ```ignore
61/// #[derive(Debug, Clone, Copy, TypedNode)]
62/// pub struct AppNode {
63///     #[node(bits = 5)]
64///     pub worker: u8,
65///     #[node(bits = 5)]
66///     pub process: u8,
67/// }
68///
69/// #[typedflake(epoch = "2025-01-01", node = AppNode)]
70/// pub struct UserId(i64);
71/// ```
72///
73/// The field order is part of the persisted ID format.
74#[proc_macro_derive(TypedNode, attributes(node))]
75pub fn derive_typed_node(input: TokenStream) -> TokenStream {
76    let input = parse_macro_input!(input as DeriveInput);
77    node::expand(&input)
78        .unwrap_or_else(syn::Error::into_compile_error)
79        .into()
80}
81
82/// Implements Serde's `Serialize` and `Deserialize` for an ID type.
83///
84/// IDs serialize as decimal strings and deserialize from strings or integers,
85/// with the same validation as `TryFrom`. Requires the `serde` feature.
86#[proc_macro_derive(Serde)]
87pub fn derive_serde(input: TokenStream) -> TokenStream {
88    let input = parse_macro_input!(input as DeriveInput);
89    integrations::serde(&input)
90        .unwrap_or_else(syn::Error::into_compile_error)
91        .into()
92}
93
94/// Implements Serde's `Serialize` and `Deserialize` for an ID type, using its
95/// encoded text.
96///
97/// IDs serialize as `encode()` writes them and deserialize only from that
98/// exact text, with the same validation as `decode()`. The ID type needs the
99/// `alphabet` option. Use this or `Serde` on a type, not both. Requires the
100/// `serde` feature.
101#[proc_macro_derive(SerdeEncoded)]
102pub fn derive_serde_encoded(input: TokenStream) -> TokenStream {
103    let input = parse_macro_input!(input as DeriveInput);
104    integrations::serde_encoded(&input)
105        .unwrap_or_else(syn::Error::into_compile_error)
106        .into()
107}
108
109/// Implements SQLx's PostgreSQL `Type`, `Encode`, `Decode`, and array support
110/// for an `i64` ID type, stored as `BIGINT`.
111///
112/// Decoding validates the value. Requires the `sqlx-postgres` feature.
113#[proc_macro_derive(SqlxPostgres)]
114pub fn derive_sqlx_postgres(input: TokenStream) -> TokenStream {
115    let input = parse_macro_input!(input as DeriveInput);
116    integrations::sqlx_postgres(&input)
117        .unwrap_or_else(syn::Error::into_compile_error)
118        .into()
119}
120
121/// Implements `postgres-types`' `ToSql` and `FromSql` for an `i64` ID type,
122/// stored as `BIGINT`.
123///
124/// Works with `tokio-postgres` and the synchronous `postgres` client. Decoding
125/// validates the value. Requires the `postgres` feature.
126#[proc_macro_derive(Postgres)]
127pub fn derive_postgres(input: TokenStream) -> TokenStream {
128    let input = parse_macro_input!(input as DeriveInput);
129    integrations::postgres(&input)
130        .unwrap_or_else(syn::Error::into_compile_error)
131        .into()
132}