lsor-proc-macro 0.1.0

Proc macros for lsor
Documentation
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Ident};

use crate::util;

pub fn expand_derive_sort(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();

    let fields = match &ast.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(named) => named,
            _ => panic!("sort can only be implemented for structs with named fields"),
        },
        _ => panic!("sort can only be implemented for structs with named fields"),
    };

    let has_json_attr = util::has_json_attr(&ast.attrs);

    let ident = &ast.ident;
    let sort_ident = util::concat_idents(ident, &Ident::new("Sort", Span::call_site()));

    let mut sort_ident_variant_decls = Vec::new();
    let mut push_to_driver_impls = Vec::new();
    let mut order_impls = Vec::new();
    let mut flip_impls = Vec::new();
    let mut push_to_driver_with_order_impls = Vec::new();
    let mut cursor_impls = Vec::new();

    for field in fields.named.iter() {
        let skip = util::has_skip_sort_attr(&field.attrs);
        if skip {
            continue;
        }

        let field_ident = field.ident.as_ref().unwrap();
        let field_ident_camel_case = Ident::new(
            &util::snake_case_to_camel_case(field_ident.to_string().as_str()),
            Span::call_site(),
        );
        let field_ty = &field.ty;

        let flat = util::has_flatten_attr(&field.attrs);

        sort_ident_variant_decls
            .push(quote! { #field_ident_camel_case(<#field_ty as ::lsor::sort::Sortable>::Sort), });
        if flat {
            push_to_driver_impls.push(quote! {
                #sort_ident::#field_ident_camel_case(sort) => {
                    use ::lsor::driver::PushPrql;

                    sort.push_to_driver(driver);
                },
            });
        } else if has_json_attr {
            push_to_driver_impls.push(quote! {
                #sort_ident::#field_ident_camel_case(sort) => {
                    sort.push_to_driver_with_lhs(&::lsor::column::json(lhs).get(stringify!(#field_ident)), driver);
                },
            });
        } else {
            push_to_driver_impls.push(quote! {
                #sort_ident::#field_ident_camel_case(sort) => {
                    sort.push_to_driver_with_lhs(&::lsor::column::col(stringify!(#field_ident)), driver);
                },
            });
        }
        order_impls.push(quote! {
            #sort_ident::#field_ident_camel_case(sort) => sort.order(),
        });
        flip_impls.push(quote! {
            #sort_ident::#field_ident_camel_case(sort) => #sort_ident::#field_ident_camel_case(sort.flip_as_self()),
        });
        if flat {
            push_to_driver_with_order_impls.push(quote! {
                #sort_ident::#field_ident_camel_case(sort) => {
                    use ::lsor::sort::Sorting;

                    sort.push_to_driver_with_order(driver);
                },
            });
        } else if has_json_attr {
            push_to_driver_with_order_impls.push(quote! {
                #sort_ident::#field_ident_camel_case(sort) => {
                    sort.push_to_driver_with_order_with_lhs(&::lsor::column::json(lhs).get(stringify!(#field_ident)), driver);
                },
            });
        } else {
            push_to_driver_with_order_impls.push(quote! {
                #sort_ident::#field_ident_camel_case(sort) => {
                    sort.push_to_driver_with_order_with_lhs(&::lsor::column::col(stringify!(#field_ident)), driver);
                },
            });
        }
        cursor_impls.push(quote! {
            #sort_ident::#field_ident_camel_case(x) => {
                use ::lsor::cursor::Iterable;
                x.cursor()
            }
        });
    }

    let trait_impls = if !has_json_attr {
        Some(quote! {
            impl ::lsor::driver::PushPrql for #sort_ident {
                fn push_to_driver(&self, driver: &mut ::lsor::driver::Driver) {
                    match &self {
                        #(#push_to_driver_impls)*
                    }
                }
            }

            impl ::lsor::sort::Sorting for #sort_ident {
                fn order(&self) -> ::lsor::sort::Order {
                    match self {
                        #(#order_impls)*
                    }
                }

                fn flip(&self) -> impl ::lsor::sort::Sorting {
                    match self {
                        #(#flip_impls)*
                    }
                }

                fn push_to_driver_with_order(&self, driver: &mut ::lsor::driver::Driver) {
                    match &self {
                        #(#push_to_driver_with_order_impls)*
                    }
                }
            }
        })
    } else {
        None
    };

    let non_trait_order_impl = if has_json_attr {
        Some(quote! {
            pub fn order(&self) -> ::lsor::sort::Order {
                match self {
                    #(#order_impls)*
                }
            }
        })
    } else {
        None
    };
    let non_trait_impls = quote! {
        impl #sort_ident {
            pub fn push_to_driver_with_lhs(&self, lhs: &dyn ::lsor::driver::PushPrql, driver: &mut ::lsor::driver::Driver) {
                match &self {
                    #(#push_to_driver_impls)*
                }
            }

            pub fn push_to_driver_with_order_with_lhs(&self, lhs: &dyn ::lsor::driver::PushPrql, driver: &mut ::lsor::driver::Driver) {
                match &self {
                    #(#push_to_driver_with_order_impls)*
                }
            }

            pub fn flip_as_self(&self) -> #sort_ident {
                match self {
                    #(#flip_impls)*
                }
            }

            #non_trait_order_impl
        }
    };

    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    let expanded = quote! {
        impl #impl_generics ::lsor::sort::Sortable for #ident #ty_generics #where_clause {
            type Sort = #sort_ident;
        }

        impl #sort_ident {
            pub fn cursor(&self) -> ::lsor::cursor::Cursor {
                match self {
                    #(#cursor_impls)*
                }
            }
        }

        #[derive(::std::clone::Clone, ::std::fmt::Debug, ::async_graphql::OneofObject)]
        #[graphql(rename_fields = "snake_case")]
        pub enum #sort_ident {
            #(#sort_ident_variant_decls)*
        }

        #non_trait_impls

        #trait_impls
    };

    TokenStream::from(expanded)
}