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}