Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use proc macros for include/select #250

Closed
tbillington opened this issue Jan 30, 2023 · 3 comments · Fixed by #382
Closed

Use proc macros for include/select #250

tbillington opened this issue Jan 30, 2023 · 3 comments · Fixed by #382

Comments

@tbillington
Copy link

Because the generated include! and select! macros are output on a single line, errors that occur when using the macros cause sub-optimal error messages.

It looks "okay" in github because github doesn't wrap, but in a terminal it's a giant wall of the macro code :)

error snippet (scroll horizontally to see full error)
> cargo c
    Blocking waiting for file lock on build directory
   Compiling schema v0.1.0 (/Users/choc/code/prisma-rust-fun/schema)
    Checking prisma-rust-fun v0.1.0 (/Users/choc/code/prisma-rust-fun)
error[E0433]: failed to resolve: `crate` in paths can only be used in start position
  --> src/tick.rs:28:22
   |
28 |             .include(cultivator::include!({ mission }))
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `crate` in paths can only be used in start position
   |
   = note: this error originates in the macro `cultivator::include` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0412]: cannot find type `Data` in this scope
  --> src/tick.rs:28:22
   |
28 |             .include(cultivator::include!({ mission }))
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this error originates in the macro `cultivator::include` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to use the associated type
  --> /Users/choc/code/prisma-rust-fun/schema/src/schema.rs:2752:1937
   |
275|     macro_rules ! _include_cultivator { ($ (($ ($ func_arg : ident : $ func_arg_ty : ty) , +) =>) ? $ module_name : ident { $ ($ field : ident $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ? $ (: $ selection_mode : ident { $ ($ selections : tt) + }) ?) + }) => { # [allow (warnings)] pub mod $ module_name { $ crate :: crate :: schema :: cultivator :: include ! (@ definitions ; $ module_name ; $ ($ field $ (($ ($ filters) +) $ (. $ arg ($ ($ arg_params) *)) *) ? $ (: $ selection_mode { $ ($ selections) + }) ?) +) ; use super :: * ; pub struct Selection (Vec < :: prisma_client_rust :: Selection >) ; impl :: prisma_client_rust :: IncludeType for Selection { type Data = Data ; type ModelData = $ crate :: crate :: schema :: cultivator :: Data ; fn to_selections (self) -> Vec < :: prisma_client_rust :: Selection > { self . 0 } } pub fn include ($ ($ ($ func_arg : $ func_arg_ty) , +) ?) -> Selection { Selection ([$ crate :: crate :: schema :: cultivator :: include ! (@ selections_to_params ; : include { $ ($ field $ (($ ($ filters) +) $ (. $ arg ($ ($ arg_params) *)) *) ? $ (: $ selection_mode { $ ($ selections) + }) ?) + }) . into_iter () . map (| p | p . to_selection ()) . collect :: < Vec < _ >> () , < $ crate :: crate :: schema :: cultivator :: Types as :: prisma_client_rust :: ModelTypes > :: scalar_selections ()] . into_iter () . flatten () . collect :: < Vec < _ >> ()) } } } ; ({ $ ($ field : ident $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ? $ (: $ selection_mode : ident { $ ($ selections : tt) + }) ?) + }) => { { $ crate :: crate :: schema :: cultivator :: include ! (@ definitions ; ; $ ($ field $ (($ ($ filters) +) $ (. $ arg ($ ($ arg_params) *)) *) ? $ (: $ selection_mode { $ ($ selections) + }) ?) +) ; pub struct Selection (Vec < :: prisma_client_rust :: Selection >) ; impl :: prisma_client_rust :: IncludeType for Selection { type Data = Self::Data ; type ModelData = $ crate :: crate :: schema :: cultivator :: Data ; fn to_selections (self) -> Vec < :: prisma_client_rust :: Selection > { self . 0 } } Selection ([$ crate :: crate :: schema :: cultivator :: include ! (@ selections_to_params ; : include { $ ($ field $ (($ ($ filters) +) $ (. $ arg ($ ($ arg_params) *)) *) ? $ (: $ selection_mode { $ ($ selections) + }) ?) + }) . into_iter () . map (| p | p . to_selection ()) . collect :: < Vec < _ >> () , < $ crate :: crate :: schema :: cultivator :: Types as :: prisma_client_rust :: ModelTypes > :: scalar_selections ()] . into_iter () . flatten () . collect :: < Vec < _ >> ()) } } ; (@ definitions ; $ ($ module_name : ident) ? ; $ ($ field : ident $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ? $ (: $ selection_mode : ident { $ ($ selections : tt) + }) ?) +) => { # [allow (warnings)] enum Fields { sect , mission } # [allow (warnings)] impl Fields { fn selections () { $ (let _ = Fields :: $ field ;) + } } # [allow (warnings)] # [derive (std :: fmt :: Debug , Clone)] pub struct Data { pub id : String , pub created_at : :: prisma_client_rust :: chrono :: DateTime < :: prisma_client_rust :: chrono :: FixedOffset , > , pub updated_at : :: prisma_client_rust :: chrono :: DateTime < :: prisma_client_rust :: chrono :: FixedOffset , > , pub sect_id : String , pub mission_id : Option < String > , $ (pub $ field : $ crate :: crate :: schema :: cultivator :: include ! (@ field_type ; $ field $ (: $ selection_mode { $ ($ selections) + }) ?) ,) + } impl :: serde :: Serialize for Data { fn serialize < S > (& self , serializer : S) -> Result < S :: Ok , S :: Error > where S : :: serde :: Serializer , { use :: serde :: ser :: SerializeStruct ; let mut state = serializer . serialize_struct ("Data" , [$ (stringify ! ($ field) ,) + stringify ! (id) , stringify ! (created_at) , stringify ! (updated_at) , stringify ! (sect_id) , stringify ! (mission_id)] . len ()) ? ; $ (state . serialize_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; $ field) , & self . $ field) ? ;) * state . serialize_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; id) , & self . id) ? ; state . serialize_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; created_at) , & self . created_at) ? ; state . serialize_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; updated_at) , & self . updated_at) ? ; state . serialize_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; sect_id) , & self . sect_id) ? ; state . serialize_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; mission_id) , & self . mission_id) ? ; state . end () } } impl < 'de > :: serde :: Deserialize < 'de > for Data { fn deserialize < D > (deserializer : D) -> Result < Self , D :: Error > where D : :: serde :: Deserializer < 'de > , { # [allow (warnings)] enum Field { $ ($ field) , + , id , created_at , updated_at , sect_id , mission_id } impl < 'de > :: serde :: Deserialize < 'de > for Field { fn deserialize < D > (deserializer : D) -> Result < Field , D :: Error > where D : :: serde :: Deserializer < 'de > , { struct FieldVisitor ; impl < 'de > :: serde :: de :: Visitor < 'de > for FieldVisitor { type Value = Field ; fn expecting (& self , formatter : & mut :: std :: fmt :: Formatter) -> :: std :: fmt :: Result { formatter . write_str (concat ! ($ ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; $ field) , ", ") , + , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; id) , ", " , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; created_at) , ", " , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; updated_at) , ", " , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; sect_id) , ", " , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; mission_id) , ", ")) } fn visit_str < E > (self , value : & str) -> Result < Field , E > where E : :: serde :: de :: Error , { match value { $ ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; $ field) => Ok (Field :: $ field)) , * , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; id) => Ok (Field :: id) , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; created_at) => Ok (Field :: created_at) , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; updated_at) => Ok (Field :: updated_at) , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; sect_id) => Ok (Field :: sect_id) , $ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; mission_id) => Ok (Field :: mission_id) , _ => Err (:: serde :: de :: Error :: unknown_field (value , FIELDS)) , } } } deserializer . deserialize_identifier (FieldVisitor) } } struct DataVisitor ; impl < 'de > :: serde :: de :: Visitor < 'de > for DataVisitor { type Value = Data ; fn expecting (& self , formatter : & mut std :: fmt :: Formatter) -> std :: fmt :: Result { formatter . write_str ("struct Data") } fn visit_map < V > (self , mut map : V) -> Result < Data , V :: Error > where V : :: serde :: de :: MapAccess < 'de > , { $ (let mut $ field = None ;) * let mut id = None ; let mut created_at = None ; let mut updated_at = None ; let mut sect_id = None ; let mut mission_id = None ; while let Some (key) = map . next_key () ? { match key { Field :: id => { if id . is_some () { return Err (:: serde :: de :: Error :: duplicate_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; id))) ; } id = Some (map . next_value () ?) ; } Field :: created_at => { if created_at . is_some () { return Err (:: serde :: de :: Error :: duplicate_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; created_at))) ; } created_at = Some (map . next_value () ?) ; } Field :: updated_at => { if updated_at . is_some () { return Err (:: serde :: de :: Error :: duplicate_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; updated_at))) ; } updated_at = Some (map . next_value () ?) ; } Field :: sect_id => { if sect_id . is_some () { return Err (:: serde :: de :: Error :: duplicate_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; sect_id))) ; } sect_id = Some (map . next_value () ?) ; } Field :: mission_id => { if mission_id . is_some () { return Err (:: serde :: de :: Error :: duplicate_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; mission_id))) ; } mission_id = Some (map . next_value () ?) ; } $ (Field :: $ field => { if $ field . is_some () { return Err (:: serde :: de :: Error :: duplicate_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; $ field))) ; } $ field = Some (map . next_value () ?) ; }) * } } $ (let $ field = $ field . ok_or_else (|| serde :: de :: Error :: missing_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; $ field))) ? ;) * let id = id . ok_or_else (|| serde :: de :: Error :: missing_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; id))) ? ; let created_at = created_at . ok_or_else (|| serde :: de :: Error :: missing_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; created_at))) ? ; let updated_at = updated_at . ok_or_else (|| serde :: de :: Error :: missing_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; updated_at))) ? ; let sect_id = sect_id . ok_or_else (|| serde :: de :: Error :: missing_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; sect_id))) ? ; let mission_id = mission_id . ok_or_else (|| serde :: de :: Error :: missing_field ($ crate :: crate :: schema :: cultivator :: include ! (@ field_serde_name ; mission_id))) ? ; Ok (Data { id , created_at , updated_at , sect_id , mission_id , $ ($ field) , * }) } } const FIELDS : & 'static [& 'static str] = & ["id" , "createdAt" , "updatedAt" , "sect" , "sectId" , "mission" , "missionId"] ; deserializer . deserialize_struct ("Data" , FIELDS , DataVisitor) } } $ ($ (pub mod $ field { $ crate :: crate :: schema :: cultivator :: $ selection_mode ! (@ field_module ; $ field : $ selection_mode { $ ($ selections) + }) ; }) ?) + } ; (@ field_type ; sect : $ selection_mode : ident { $ ($ selections : tt) + }) => { sect :: Data } ; (@ field_type ; sect) => { crate :: crate :: schema :: sect :: Data } ; (@ field_type ; mission : $ selection_mode : ident { $ ($ selections : tt) + }) => { Option < mission :: Data > } ; (@ field_type ; mission) => { Option < crate :: crate :: schema :: mission :: Data > } ; (@ field_type ; $ field : ident $ ($ tokens : tt) *) => { compile_error ! (stringify ! (Cannot include nonexistent relation $ field on model "Cultivator" , available relations are "sect, mission")) } ; (@ field_module ; sect : $ selection_mode : ident { $ ($ selections : tt) + }) => { $ crate :: crate :: schema :: sect :: include ! (@ definitions ; ; $ ($ selections) +) ; } ; (@ field_module ; mission : $ selection_mode : ident { $ ($ selections : tt) + }) => { $ crate :: crate :: schema :: mission :: include ! (@ definitions ; ; $ ($ selections) +) ; } ; (@ field_module ; $ ($ tokens : tt) *) => { } ; (@ selection_field_to_selection_param ; sect $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ? : $ selection_mode : ident { $ ($ selections : tt) + }) => { { Into :: < $ crate :: crate :: schema :: cultivator :: IncludeParam > :: into ($ crate :: crate :: schema :: cultivator :: sect :: Include :: $ selection_mode ($ crate :: crate :: schema :: sect :: select ! (@ selections_to_params ; : $ selection_mode { $ ($ selections) + }) . into_iter () . collect ())) } } ; (@ selection_field_to_selection_param ; sect $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ?) => { { Into :: < $ crate :: crate :: schema :: cultivator :: IncludeParam > :: into ($ crate :: crate :: schema :: cultivator :: sect :: Include :: Fetch) } } ; (@ selection_field_to_selection_param ; mission $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ? : $ selection_mode : ident { $ ($ selections : tt) + }) => { { Into :: < $ crate :: crate :: schema :: cultivator :: IncludeParam > :: into ($ crate :: crate :: schema :: cultivator :: mission :: Include :: $ selection_mode ($ crate :: crate :: schema :: mission :: select ! (@ selections_to_params ; : $ selection_mode { $ ($ selections) + }) . into_iter () . collect ())) } } ; (@ selection_field_to_selection_param ; mission $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ?) => { { Into :: < $ crate :: crate :: schema :: cultivator :: IncludeParam > :: into ($ crate :: crate :: schema :: cultivator :: mission :: Include :: Fetch) } } ; (@ selection_field_to_selection_param ; $ ($ tokens : tt) *) => { compile_error ! (stringify ! ($ ($ tokens) *)) } ; (@ selections_to_params ; : $ macro_name : ident { $ ($ field : ident $ (($ ($ filters : tt) +) $ (. $ arg : ident ($ ($ arg_params : tt) *)) *) ? $ (: $ selection_mode : ident { $ ($ selections : tt) + }) ?) + }) => { [$ ($ crate :: crate :: schema :: cultivator :: $ macro_name ! (@ selection_field_to_selection_param ; $ field $ (($ ($ filters) +) $ (. $ arg ($ ($ arg_params) *)) *) ? $ (: $ selection_mode { $ ($ selections) + }) ?) ,) +] } ; (@ filters_to_args ;) => { vec ! [] } ; (@ filters_to_args ; $ ($ t : tt) *) => { $ ($ t) * } ; (@ field_serde_name ; id) => { "id" } ; (@ field_serde_name ; created_at) => { "createdAt" } ; (@ field_serde_name ; updated_at) => { "updatedAt" } ; (@ field_serde_name ; sect) => { "sect" } ; (@ field_serde_name ; sect_id) => { "sectId" } ; (@ field_serde_name ; mission) => { "mission" } ; (@ field_serde_name ; mission_id) => { "missionId" } ; }
   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 ~~~~~~~~~~
help: consider importing one of these items
   |
1  | use async_graphql::Data;
   |
1  | use crate::tick::cultivator::Data;
   |
1  | use crate::tick::mission::Data;
   |
1  | use crate::tick::user::Data;
   |
     and 6 other candidates

warning: unused import: `mission`
 --> src/tick.rs:4:26
  |
4 | use schema::{cultivator, mission, user, PrismaClient};
  |                          ^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

Some errors have detailed explanations: E0412, E0433.
For more information about an error, try `rustc --explain E0412`.
warning: `prisma-rust-fun` (bin "prisma-rust-fun") generated 1 warning
error: could not compile `prisma-rust-fun` due to 5 previous errors; 1 warning emitted

Unfortunately I can't use rustfmt to format it because it gives up on lines over a certain length.

@Brendonovich
Copy link
Owner

With the current implementation of include and select, this is a tricky issue to address. Splitting over multiple lines isn't possible since quote! doesn't include whitespace, and I'm not sure rustfmt would even work since it's not always great at formatting macro code.

In your example, I don't think there's a good way to alleviate the terminal spam since it's a result of an invalid configuration ( which I addressed in Discord). But for errors that are syntactic, there's definitely some extra error handling I could add.
Take this for example, where non_existent isn't actually a field on FilePath.

file_path::select!({ non_existent })

The correct error is produced.

error: Cannot include nonexistent relation non_existent on model "FilePath",
       available relations are
       "id, is_dir, cas_id, ..."

But the same terminal spam is produced from some other branch of the macro.

error: no rules expected the token `non_existent`
     --> core/src/prisma.rs:14116:3432
      |
14116 |     macro_rules ! _select_file_path ...

This could definitely be worked on.

@Brendonovich Brendonovich added this to the 0.7.0 milestone Jan 31, 2023
@Brendonovich
Copy link
Owner

Brendonovich commented Feb 2, 2023

ok maybe proc macros are a really good idea

image

damn even rustfmt can work

image

@tbillington
Copy link
Author

😍 nice!

@Brendonovich Brendonovich changed the title line length of generated client macros makes errors difficult to understand Use proc macros for include/select Mar 2, 2023
@Brendonovich Brendonovich removed this from the 0.7.0 milestone Mar 29, 2023
@Brendonovich Brendonovich added this to the 0.7.0 milestone May 1, 2023
@Brendonovich Brendonovich linked a pull request Aug 19, 2023 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants