Skip to content

Commit

Permalink
filter! macro (#369)
Browse files Browse the repository at this point in the history
* man what the hell

* remove unused import

* remove unnecessary semicolon

* use module path in filter

* use $crate and proper unknown field error
  • Loading branch information
Brendonovich authored Jul 27, 2023
1 parent 8cd6f70 commit b5eb76f
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 15 deletions.
32 changes: 32 additions & 0 deletions crates/generator/src/models/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use prisma_client_rust_sdk::prisma::prisma_models::walkers::{ModelWalker, RefinedFieldWalker};

use crate::prelude::*;

pub fn r#macro(model: ModelWalker, module_path: &TokenStream) -> TokenStream {
let model_name_snake = snake_ident(model.name());

let name = format_ident!("_{}_filter", model.name().to_case(Case::Snake, true));

let fields = model.fields().map(|field| {
let field_name_snake = snake_ident(field.name());

let variant = match field.refine() {
RefinedFieldWalker::Scalar(_) => quote!(Scalar),
RefinedFieldWalker::Relation(relation_field) => {
let related_model_name_snake = snake_ident(relation_field.related_model().name());

quote!(Relation(#module_path #related_model_name_snake))
}
};

quote!((#field_name_snake, #variant))
});

quote! {
::prisma_client_rust::macros::filter_factory!(
#name,
#module_path #model_name_snake,
[#(#fields),*]
);
}
}
18 changes: 9 additions & 9 deletions crates/generator/src/models/include_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn model_macro<'a>(
let model_name_snake_raw = snake_ident_raw(model.name());
let macro_name = format_ident!("_{variant}_{model_name_snake_raw}");

let model_module = quote!(#module_path #model_name_snake);
let model_module = quote!($crate::#module_path #model_name_snake);

let selection_type = variant.type_trait();
let selection_param = variant.param();
Expand Down Expand Up @@ -89,7 +89,7 @@ fn model_macro<'a>(

quote! {
(@field_module; #field_name_snake #selections_pattern_produce) => {
#module_path #relation_model_name_snake::#variant_ident!(@definitions; ; $($selections)+);
$crate::#module_path #relation_model_name_snake::#variant_ident!(@definitions; ; $($selections)+);
};
}
});
Expand All @@ -103,15 +103,15 @@ fn model_macro<'a>(
RefinedFieldWalker::Relation(relation_field) =>{
let relation_model_name_snake = snake_ident(relation_field.related_model().name());

let relation_model_module = quote!(#module_path #relation_model_name_snake);
let relation_model_module = quote!($crate::#module_path #relation_model_name_snake);

match relation_field.ast_field().arity {
FieldArity::List => {
quote! {
(@selection_field_to_selection_param; #field_name_snake $(#filters_pattern_produce)? #selections_pattern_produce) => {{
Into::<#model_module::#selection_param>::into(
#field_module::#variant_pascal::$selection_mode(
#relation_model_module::ManyArgs::new(#module_path #relation_model_name_snake::#variant_ident!(
#relation_model_module::ManyArgs::new($crate::#module_path #relation_model_name_snake::#variant_ident!(
@filters_to_args;
$($($filters)+)?
)) $($(.$arg($($arg_params)*))*)?,
Expand All @@ -125,7 +125,7 @@ fn model_macro<'a>(
(@selection_field_to_selection_param; #field_name_snake $(#filters_pattern_produce)?) => {{
Into::<#model_module::#selection_param>::into(
#field_module::#variant_pascal::Fetch(
#relation_model_module::ManyArgs::new(#module_path #relation_model_name_snake::#variant_ident!(
#relation_model_module::ManyArgs::new($crate::#module_path #relation_model_name_snake::#variant_ident!(
@filters_to_args;
$($($filters)+)?
)) $($(.$arg($($arg_params)*))*)?
Expand Down Expand Up @@ -173,7 +173,7 @@ fn model_macro<'a>(
let field_type = f.type_tokens(module_path);

let specta_rename = cfg!(feature = "specta").then(|| {
quote!(#[specta(rename_from_path = #module_path #model_name_snake::#field_name_snake::NAME)])
quote!(#[specta(rename_from_path = $crate::#module_path #model_name_snake::#field_name_snake::NAME)])
});

quote! {
Expand Down Expand Up @@ -380,13 +380,13 @@ fn model_macro<'a>(
let selection = {
let scalar_selections = matches!(variant, Variant::Include).then(||
quote! {
<#module_path #model_name_snake::Types as ::prisma_client_rust::ModelTypes>::scalar_selections()
<$crate::#module_path #model_name_snake::Types as ::prisma_client_rust::ModelTypes>::scalar_selections()
}
);

quote!(Selection(
[
#module_path #model_name_snake::#variant_ident!(
$crate::#module_path #model_name_snake::#variant_ident!(
@selections_to_params; : #variant_ident
{ $(#selection_pattern_consume)+ }
)
Expand Down Expand Up @@ -487,7 +487,7 @@ fn model_macro<'a>(
(@selection_field_to_selection_param; $($tokens:tt)*) => { compile_error!(stringify!($($tokens)*)) }; // ::prisma_client_rust::Selection::builder("").build() };

(@selections_to_params; : $macro_name:ident {$(#selection_pattern_produce)+}) => {
[ $(#module_path #model_name_snake::$macro_name!(@selection_field_to_selection_param; #selection_pattern_consume),)+]
[ $($crate::#module_path #model_name_snake::$macro_name!(@selection_field_to_selection_param; #selection_pattern_consume),)+]
};

(@filters_to_args;) => {
Expand Down
3 changes: 3 additions & 0 deletions crates/generator/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod actions;
mod create;
mod data;
mod filter;
mod include_select;
mod order_by;
mod pagination;
Expand Down Expand Up @@ -101,6 +102,7 @@ pub fn modules(args: &GenerateArgs, module_path: &TokenStream) -> Vec<Module> {
let types_struct = types::r#struct(model, module_path);
let data_struct = data::r#struct(model);
let partial_unchecked_macro = partial_unchecked::r#macro(model, &module_path);
let filter_macro = filter::r#macro(model, module_path);

let mongo_raw_types = cfg!(feature = "mongodb").then(|| quote! {
pub type FindRawQuery<'a, T: #pcr::Data> = #pcr::FindRaw<'a, Types, T>;
Expand All @@ -114,6 +116,7 @@ pub fn modules(args: &GenerateArgs, module_path: &TokenStream) -> Vec<Module> {

pub const NAME: &str = #model_name;

#filter_macro
#field_stuff
#create_types
#types_struct
Expand Down
4 changes: 2 additions & 2 deletions crates/generator/src/models/partial_unchecked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn r#macro(model: ModelWalker, module_path: &TokenStream) -> TokenStream {
let model_name_snake_raw = snake_ident_raw(model.name());
let macro_name = format_ident!("_partial_unchecked_{model_name_snake_raw}");

let model_module = quote!(#module_path #model_name_snake);
let model_module = quote!($crate::#module_path #model_name_snake);

let struct_fields = model.scalar_fields().map(|scalar_field| {
let field_name_str = scalar_field.name();
Expand All @@ -24,7 +24,7 @@ pub fn r#macro(model: ModelWalker, module_path: &TokenStream) -> TokenStream {
quote! {
#[serde(rename = #field_name_str)]
#double_option_attrs
pub #field_name_snake: #module_path #model_name_snake::#field_name_snake::Type
pub #field_name_snake: $crate::#module_path #model_name_snake::#field_name_snake::Type
}
});

Expand Down
222 changes: 222 additions & 0 deletions crates/macros/src/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
braced, bracketed, parenthesized,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Ident, Path, Token,
};

enum Arity {
Scalar,
Relation(Path),
}

impl Parse for Arity {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident = input.parse::<Ident>()?;

Ok(match ident.to_string().as_str() {
"Scalar" => Self::Scalar,
"Relation" => Self::Relation({
let content;
parenthesized!(content in input);
content.parse()?
}),
_ => {
return Err(syn::Error::new_spanned(
ident,
"expected `Scalar` or `Relation`",
))
}
})
}
}

struct FieldTuple {
name: Ident,
arity: Arity,
}

impl Parse for FieldTuple {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
parenthesized!(content in input);

Ok(Self {
name: content.parse()?,
arity: {
content.parse::<Token![,]>()?;
content.parse()?
},
})
}
}

struct Method {
name: Ident,
value: TokenStream,
}

impl Parse for Method {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
value: {
input.parse::<Token![:]>()?;
input.parse()?
},
})
}
}

struct Filter {
field: Ident,
methods: Punctuated<Method, Token![,]>,
}

impl Parse for Filter {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
field: input.parse()?,
methods: {
input.parse::<Token![:]>()?;

let content;
braced!(content in input);

Punctuated::parse_terminated(&content)?
},
})
}
}

struct Input {
dollar_crate: Ident,
module_path: Path,
fields: Punctuated<FieldTuple, Token![,]>,
filter: Punctuated<Filter, Token![,]>,
}

impl Parse for Input {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self {
dollar_crate: input.parse()?,
module_path: {
input.parse::<Token![,]>()?;
input.parse()?
},
fields: {
input.parse::<Token![,]>()?;

let content;
bracketed!(content in input);
Punctuated::parse_terminated(&content)?
},
filter: {
input.parse::<Token![,]>()?;

let content;
braced!(content in input);

Punctuated::parse_terminated(&content)?
},
})
}
}

pub fn proc_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let Input {
dollar_crate,
module_path: model_path,
fields,
filter,
} = parse_macro_input!(input as Input);

let items = filter
.into_iter()
.map(|Filter { field, methods }| {
let Some(field) = fields.iter().find(|schema_field| schema_field.name == field) else {
let all_fields = fields.iter().map(|field| format!("'{}'", field.name.to_string())).collect::<Vec<_>>().join(", ");

let error = format!("Field '{field}' not found. Available fields are {all_fields}.");

return quote_spanned!(field.span() => compile_error!(#error))
};

let field_name = &field.name;

match &field.arity {
Arity::Scalar => {
let methods = methods.into_iter().map(
|Method { name, value }| quote!(#dollar_crate::#model_path::#field_name::#name(#value)),
);

quote!(#(#methods),*)
}
Arity::Relation(related_model_path) => {
let methods = methods.into_iter().map(
|Method { name, value }| quote!(#dollar_crate::#model_path::#field_name::#name(#dollar_crate::#related_model_path::filter! #value)),
);

quote!(#(#methods),*)
}
}
})
.collect::<Vec<_>>();

quote! {
vec![
#(#items),*
]
}
.into()
}

// factory means rustfmt can work!
pub fn proc_macro_factory(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
struct FactoryInput {
name: Ident,
model_path: Path,
rest: TokenStream,
}

impl Parse for FactoryInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
model_path: {
input.parse::<Token![,]>()?;
input.parse()?
},
rest: {
input.parse::<Token![,]>()?;
input.parse()?
},
})
}
}

let FactoryInput {
name,
model_path,
rest,
} = parse_macro_input!(input as FactoryInput);

quote! {
#[macro_export]
macro_rules! #name {
($($inner:tt)+) => {
::prisma_client_rust::macros::filter!(
$crate,
#model_path,
#rest,
{ $($inner)+ }
)
};
}
pub use #name as filter;
}
.into()
}
11 changes: 11 additions & 0 deletions crates/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod filter;
mod partial_unchecked;

#[proc_macro]
Expand All @@ -10,3 +11,13 @@ pub fn to_pascal_case(input: proc_macro::TokenStream) -> proc_macro::TokenStream
pub fn partial_unchecked(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
partial_unchecked::proc_macro(input)
}

#[proc_macro]
pub fn filter(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
filter::proc_macro(input)
}

#[proc_macro]
pub fn filter_factory(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
filter::proc_macro_factory(input)
}
Loading

1 comment on commit b5eb76f

@vercel
Copy link

@vercel vercel bot commented on b5eb76f Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.