specta_macros/lib.rs
1//! Easily export your Rust types to other languages
2//!
3//! This crate contains the macro which are reexported by the `specta` crate.
4//! You shouldn't need to use this crate directly.
5//! Checkout [Specta](https://docs.rs/specta).
6#![doc(
7 html_logo_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png",
8 html_favicon_url = "https://github.com/specta-rs/specta/raw/main/.github/logo-128.png"
9)]
10
11#[cfg(feature = "DO_NOT_USE_function")]
12mod specta;
13mod r#type;
14mod utils;
15
16use quote::quote;
17use syn::{Error, LitStr, Type, parse_macro_input};
18
19/// Implements `specta::Type` for a given struct or enum.
20///
21/// # Attributes
22///
23/// `Type` supports `#[specta(...)]` attributes on containers, variants, and fields.
24/// It also understands selected Rust and Serde attributes.
25///
26/// ## `#[specta(...)]` container attributes
27///
28/// These can be used on the struct or enum deriving `Type`.
29///
30/// - `#[specta(type = T)]` overrides the generated type definition with `T`.
31/// `T` may be a path or Rust type, such as `String`, `Vec<T>`, or `(String, i32)`.
32/// - `#[specta(crate = path::to::specta)]` uses a custom path to the `specta` crate.
33/// - `#[specta(inline)]` or `#[specta(inline = true)]` inlines this type where it is referenced.
34/// Use `#[specta(inline = false)]` to disable it.
35/// - `#[specta(remote = path::ToType)]` implements `Type` for a remote type instead of the
36/// local derive input name.
37/// - `#[specta(collect)]` or `#[specta(collect = true)]` enables collection for this type when
38/// the `collect` feature is used. Use `#[specta(collect = false)]` to prevent collection.
39/// - `#[specta(skip_attr = "attr_name")]` ignores attributes named `attr_name` while parsing and
40/// while collecting runtime attributes. This may be repeated.
41/// - `#[specta(transparent)]` or `#[specta(transparent = true)]` treats a struct as its single
42/// non-skipped field. Use `#[specta(transparent = false)]` to disable it.
43/// - `#[specta(bound = "T: Type")]` replaces the automatically inferred `Type` bounds.
44/// Use `#[specta(bound = "")]` to emit no inferred bounds.
45///
46/// `#[specta(type = ...)]` cannot be combined with `#[specta(transparent)]`.
47/// `#[specta(transparent)]` is only valid on structs with exactly one non-skipped field.
48///
49/// ## `#[specta(...)]` variant attributes
50///
51/// These can be used on enum variants.
52///
53/// - `#[specta(type = T)]` or `#[specta(r#type = T)]` overrides the generated variant payload
54/// type with `T`.
55/// - `#[specta(skip)]` or `#[specta(skip = true)]` marks the variant as skipped.
56/// Use `#[specta(skip = false)]` to disable it.
57/// - `#[specta(inline)]` or `#[specta(inline = true)]` inlines the first unnamed field of the
58/// variant. Use `#[specta(inline = false)]` to disable it.
59///
60/// ## `#[specta(...)]` field attributes
61///
62/// These can be used on struct fields and enum variant fields.
63///
64/// - `#[specta(type = T)]` overrides the generated field type with `T`.
65/// - `#[specta(inline)]` or `#[specta(inline = true)]` inlines the field type.
66/// Use `#[specta(inline = false)]` to disable it.
67/// - `#[specta(skip)]` or `#[specta(skip = true)]` skips generating the field type.
68/// Use `#[specta(skip = false)]` to disable it.
69/// - `#[specta(optional)]` or `#[specta(optional = true)]` marks the field as optional.
70/// This is commonly used with `Option<T>` to export `{ a?: T | null }` instead of
71/// `{ a: T | null }`.
72/// - `#[specta(default)]` or `#[specta(default = true)]` is an alias for `optional`.
73///
74/// ## Rust attributes
75///
76/// These are read on containers, variants, and fields.
77///
78/// - `#[doc = "..."]` is exported as documentation.
79/// - `#[deprecated]` marks the item as deprecated.
80/// - `#[deprecated = "..."]` marks the item as deprecated with a note.
81/// - `#[deprecated(note = "...")]` marks the item as deprecated with a note.
82/// - `#[deprecated(since = "...", note = "...")]` marks the item as deprecated with a version
83/// and note.
84///
85/// `#[repr(transparent)]` is also accepted on containers as an alias for
86/// `#[specta(transparent)]`.
87///
88/// ## Serde attributes
89///
90/// Specta can read selected `#[serde(...)]` attributes. Prefer Serde attributes when the same
91/// behavior should apply to Serde and Specta.
92///
93/// These are always understood by the derive macro:
94///
95/// - Container: `#[serde(transparent)]` acts like `#[specta(transparent)]`.
96/// - Field: `#[serde(skip)]` acts like `#[specta(skip)]`.
97/// - Variant: `#[serde(skip)]` acts like `#[specta(skip)]`.
98///
99/// With the `serde` feature enabled, the following Serde attributes are also preserved as
100/// runtime attributes.
101/// To apply these attributes during export, use
102/// [`specta_serde::Format`](https://docs.rs/specta-serde/latest/specta_serde/struct.Format.html)
103/// or
104/// [`specta_serde::FormatPhases`](https://docs.rs/specta-serde/latest/specta_serde/struct.FormatPhases.html).
105/// See the [`specta-serde` docs](https://docs.rs/specta-serde) for details.
106///
107/// Container attributes:
108///
109/// - `#[serde(rename = "...")]`
110/// - `#[serde(rename(serialize = "..."))]`
111/// - `#[serde(rename(deserialize = "..."))]`
112/// - `#[serde(rename_all = "...")]`
113/// - `#[serde(rename_all(serialize = "..."))]`
114/// - `#[serde(rename_all(deserialize = "..."))]`
115/// - `#[serde(rename_all_fields = "...")]`
116/// - `#[serde(rename_all_fields(serialize = "..."))]`
117/// - `#[serde(rename_all_fields(deserialize = "..."))]`
118/// - `#[serde(tag = "...")]`
119/// - `#[serde(content = "...")]`
120/// - `#[serde(untagged)]`
121/// - `#[serde(default)]` or `#[serde(default = "...")]`
122/// - `#[serde(transparent)]`
123/// - `#[serde(from = "T")]`
124/// - `#[serde(try_from = "T")]`
125/// - `#[serde(into = "T")]`
126/// - `#[serde(variant_identifier)]`
127/// - `#[serde(field_identifier)]`
128///
129/// Variant attributes:
130///
131/// - `#[serde(rename = "...")]`
132/// - `#[serde(rename(serialize = "..."))]`
133/// - `#[serde(rename(deserialize = "..."))]`
134/// - `#[serde(alias = "...")]`
135/// - `#[serde(rename_all = "...")]`
136/// - `#[serde(rename_all(serialize = "..."))]`
137/// - `#[serde(rename_all(deserialize = "..."))]`
138/// - `#[serde(skip)]`
139/// - `#[serde(skip_serializing)]`
140/// - `#[serde(skip_deserializing)]`
141/// - `#[serde(serialize_with = "...")]`
142/// - `#[serde(deserialize_with = "...")]`
143/// - `#[serde(with = "...")]`
144/// - `#[serde(other)]`
145/// - `#[serde(untagged)]`
146///
147/// Field attributes:
148///
149/// - `#[serde(rename = "...")]`
150/// - `#[serde(rename(serialize = "..."))]`
151/// - `#[serde(rename(deserialize = "..."))]`
152/// - `#[serde(alias = "...")]`
153/// - `#[serde(default)]` or `#[serde(default = "...")]`
154/// - `#[serde(flatten)]`
155/// - `#[serde(skip)]`
156/// - `#[serde(skip_serializing)]`
157/// - `#[serde(skip_deserializing)]`
158/// - `#[serde(skip_serializing_if = "...")]`
159/// - `#[serde(serialize_with = "...")]`
160/// - `#[serde(deserialize_with = "...")]`
161/// - `#[serde(with = "...")]`
162///
163/// Supported rename casing values are:
164///
165/// - `"lowercase"`
166/// - `"UPPERCASE"`
167/// - `"PascalCase"`
168/// - `"camelCase"`
169/// - `"snake_case"`
170/// - `"SCREAMING_SNAKE_CASE"`
171/// - `"kebab-case"`
172/// - `"SCREAMING-KEBAB-CASE"`
173///
174/// ## Example
175///
176/// ```ignore
177/// use specta::Type;
178///
179/// // Use it on structs
180/// #[derive(Type)]
181/// pub struct MyCustomStruct {
182/// pub name: String,
183/// }
184///
185/// #[derive(Type)]
186/// pub struct MyCustomStruct2(String, i32, bool);
187///
188/// // Use it on enums
189/// #[derive(Type)]
190/// pub enum MyCustomType {
191/// VariantOne,
192/// VariantTwo(String, i32),
193/// VariantThree { name: String, age: i32 },
194/// }
195/// ```
196///
197/// ## Known limitations
198///
199/// - Const generics will not be exported within user-defined types which define const generics
200/// - Associated constants or types can't be used
201///
202#[proc_macro_derive(Type, attributes(specta))]
203pub fn derive_type(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
204 r#type::derive(input).unwrap_or_else(|err| err.into_compile_error().into())
205}
206
207/// Parses a string literal into a Rust type token stream.
208///
209/// This is an internal helper proc macro used by Specta macros to turn a
210/// literal like `"Option<String>"` into a Rust type at compile time.
211#[proc_macro]
212pub fn parse_type_from_lit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
213 let lit = parse_macro_input!(input as LitStr);
214
215 match syn::parse_str::<Type>(&lit.value()) {
216 Ok(ty) => quote!(#ty).into(),
217 Err(err) => Error::new_spanned(lit, format!("invalid type literal: {err}"))
218 .to_compile_error()
219 .into(),
220 }
221}
222
223/// Prepares a function to have its types extracted using `specta::function::fn_datatype!`
224///
225/// ## Example
226///
227/// ```ignore
228/// #[specta::specta]
229/// fn my_function(arg1: i32, arg2: bool) -> &'static str {
230/// "Hello World"
231/// }
232/// ```
233#[proc_macro_attribute]
234#[cfg(feature = "DO_NOT_USE_function")]
235pub fn specta(
236 attr: proc_macro::TokenStream,
237 item: proc_macro::TokenStream,
238) -> proc_macro::TokenStream {
239 specta::attribute(attr, item).unwrap_or_else(|err| err.into_compile_error().into())
240}