Skip to main content

thisversion_derive/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/arifd/thisversion/main/docs/assets/thisversion.svg",
3    html_favicon_url = "https://raw.githubusercontent.com/arifd/thisversion/main/docs/assets/thisversion.svg"
4)]
5// Cargo relocates the README when packaging; use the manifest's current path.
6#![doc = include_str!(concat!(
7    env!("CARGO_MANIFEST_DIR"),
8    "/",
9    env!("CARGO_PKG_README")
10))]
11
12mod boundary;
13mod family;
14
15use proc_macro::TokenStream;
16use proc_macro_crate::{FoundCrate, crate_name};
17use proc_macro2::Span;
18use quote::quote;
19
20/// Resolves the runtime crate path, including Cargo aliases.
21pub(crate) fn runtime() -> syn::Result<syn::Path> {
22    match crate_name("thisversion").map_err(|_| {
23        syn::Error::new(
24            Span::call_site(),
25            "could not find the `thisversion` runtime crate; add `thisversion` as a dependency",
26        )
27    })? {
28        FoundCrate::Itself => Ok(syn::parse_quote!(::thisversion)),
29        FoundCrate::Name(name) => {
30            let ident = syn::Ident::new(&name, Span::call_site());
31            Ok(syn::parse2(quote!(::#ident))?)
32        }
33    }
34}
35
36/// Connects a chronological enum through caller-provided `From` or `TryFrom`
37/// conversions. Its latest schema defines the model and error types.
38///
39/// Generates `From<Schema>` for every family variant and model conversion
40/// implementations. Infallible migrations and model conversion are the default.
41/// Add `#[thisversion(fallible)]` to the enum when a migration or the latest
42/// schema's model conversion can fail; migration errors must convert into its
43/// model error.
44#[proc_macro_derive(VersionFamily, attributes(thisversion))]
45pub fn derive_version_family(input: TokenStream) -> TokenStream {
46    syn::parse(input)
47        .and_then(family::parse)
48        .and_then(|family| Ok((family, runtime()?)))
49        .map(|(family, runtime)| family::expand(family, &runtime))
50        .unwrap_or_else(syn::Error::into_compile_error)
51        .into()
52}
53
54/// Connects a family's latest schema version to its application model.
55///
56/// Apply this derive to the final variant's payload in a `VersionFamily`. The
57/// family determines version order; this derive generates the model conversions
58/// and does not check family membership or position.
59///
60/// Requires `#[thisversion(model = Model)]` and matching field names. With no
61/// `error` option, generates `IntoModel`, its
62/// `TryIntoModel<Error = Infallible>` adapter, and the model's `IntoSchema<S>`.
63/// With `#[thisversion(model = Model, error = E)]`, generates fallible
64/// `TryIntoModel` and `IntoSchema<S>`.
65///
66/// Model -> schema conversion constructs this schema's value, then converts it
67/// into the requested `S` through `From`.
68///
69/// Mark a field with `#[thisversion(nested)]` to recursively convert a family
70/// wrapper in both directions. A bare schema value can name its family with
71/// `#[thisversion(nested = Family)]`; loading wraps it in that family and
72/// migrates it forward. Saving back to the pinned schema is available only when
73/// the child model implements `IntoSchema<S>`.
74#[proc_macro_derive(VersionBoundary, attributes(thisversion))]
75pub fn derive_version_boundary(input: TokenStream) -> TokenStream {
76    syn::parse(input)
77        .and_then(boundary::parse::parse)
78        .and_then(|boundary| Ok((boundary, runtime()?)))
79        .map(|(boundary, runtime)| boundary::expand::expand(boundary, &runtime))
80        .unwrap_or_else(syn::Error::into_compile_error)
81        .into()
82}