rust-query-macros 0.7.0

Proc-macro crate for rust-query.
Documentation
use dummy::{dummy_impl, from_expr};
use heck::{ToSnekCase, ToUpperCamelCase};
use multi::{SingleVersionTable, VersionedSchema};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Ident, ItemMod, ItemStruct};
use table::define_all_tables;

mod dummy;
mod migrations;
mod multi;
mod parse;
mod table;
mod unique;

#[proc_macro_attribute]
pub fn schema(
    attr: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let name = syn::parse_macro_input!(attr as syn::Ident);
    let item = syn::parse_macro_input!(item as ItemMod);

    match generate(name, item) {
        Ok(x) => x,
        Err(e) => e.into_compile_error(),
    }
    .into()
}

#[proc_macro_derive(Select)]
pub fn from_row(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item = syn::parse_macro_input!(item as ItemStruct);
    match dummy_impl(item) {
        Ok(x) => x,
        Err(e) => e.into_compile_error(),
    }
    .into()
}

#[proc_macro_derive(FromExpr, attributes(rust_query))]
pub fn from_expr_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let item = syn::parse_macro_input!(item as ItemStruct);
    match from_expr(item) {
        Ok(x) => x,
        Err(e) => e.into_compile_error(),
    }
    .into()
}

fn make_generic(name: &Ident) -> Ident {
    let normalized = name.to_string().to_upper_camel_case();
    format_ident!("_{normalized}")
}

fn to_lower(name: &Ident) -> Ident {
    let normalized = name.to_string().to_snek_case();
    format_ident!("{normalized}")
}

fn generate(schema_name: Ident, item: syn::ItemMod) -> syn::Result<TokenStream> {
    let schema = VersionedSchema::parse(item)?;

    let mut output = quote! {};
    let mut prev_mod = None;

    let mut iter = schema
        .versions
        .clone()
        .map(|version| Ok((version, schema.get(version)?)))
        .collect::<syn::Result<Vec<_>>>()?
        .into_iter()
        .peekable();

    while let Some((version, mut new_tables)) = iter.next() {
        let next_mod = iter
            .peek()
            .map(|(peek_version, _)| format_ident!("v{peek_version}"));
        let mut mod_output =
            define_all_tables(&schema_name, &prev_mod, &next_mod, version, &mut new_tables)?;

        let new_mod = format_ident!("v{version}");

        if let Some((peek_version, peek_tables)) = iter.peek() {
            let peek_mod = format_ident!("v{peek_version}");
            let m = migrations::migrations(
                &schema_name,
                new_tables,
                peek_tables,
                quote! {super},
                quote! {super::super::#peek_mod},
            )?;
            mod_output.extend(quote! {
                pub mod migrate {
                    #m
                }
            });
        }

        let use_items = &schema.use_items;

        output.extend(quote! {
            pub mod #new_mod {
                #(
                    #[allow(unused)]
                    #use_items
                )*
                #mod_output
            }
        });

        prev_mod = Some(new_mod);
    }

    Ok(output)
}