cynic_codegen/query_variables_derive/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use {
    proc_macro2::TokenStream,
    quote::{format_ident, quote, quote_spanned},
    syn::visit_mut::{self, VisitMut},
};

mod input;

use crate::{generics_for_serde, variables_fields_ident};

use self::input::QueryVariablesDeriveInput;

pub fn query_variables_derive(ast: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
    use darling::FromDeriveInput;

    match QueryVariablesDeriveInput::from_derive_input(ast) {
        Ok(input) => query_variables_derive_impl(input),
        Err(e) => Ok(e.write_errors()),
    }
}

pub fn query_variables_derive_impl(
    input: QueryVariablesDeriveInput,
) -> Result<TokenStream, syn::Error> {
    let ident = &input.ident;

    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let generics_with_ser = generics_for_serde::with_serialize_bounds(&input.generics);
    let (impl_generics_with_ser, _, where_clause_with_ser) = generics_with_ser.split_for_impl();

    let vis = &input.vis;
    let schema_module = &input.schema_module();
    let fields_struct_ident = variables_fields_ident(ident);

    let input_fields = input.data.take_struct().unwrap().fields;

    let mut field_funcs = Vec::new();
    let mut variables = Vec::new();
    let mut field_inserts = Vec::new();
    let mut coercion_checks = Vec::new();
    let mut field_output_types = Vec::new();

    for (field_idx, f) in input_fields.into_iter().enumerate() {
        let name = f.ident.as_ref().unwrap();
        let ty = &f.ty;
        let mut ty_for_fields_struct = match f.graphql_type {
            None => ty.clone(),
            Some(ref graphql_type) => {
                // This enables to support generics. Normally we have every Variable type that
                // implements `CoercesTo<schema::SchemaType>`. Here that is also
                // the case, but:
                // - The XxxVariablesFields struct needs to not be generic, as it needs to be
                //   referenced explicitly by the `QueryFragment` type (to have access to the
                //   functions that enable to typecheck for the presence of variables), and we
                //   like the QueryFragment itself to not be generic on the variables type to
                //   generate the query.
                // - Normally we use the type of the fields of the XxxVariables directly in
                //   these return types of the functions of XxxVariablesFields, and that type
                //   implements `CoercesTo<schema::CorrespondingType>` (as derived by
                //   `InputObject`, or specified by cynic if it's a literal)
                // - Now we want to still typecheck that our concrete (specialized) types have
                //   all their fields implement `CoercesTo` the correct schema type, but we also
                //   want to not be generic in the `Fields` struct. This is achieved by:
                //   - Creating a proxy type that will be used in the XxxVariablesFields struct
                //   - Implementing `CoercesTo<SchemaType>` for it (so that the regular
                //     typecheck passes)
                //   - Adding a separate typecheck in the concrete type for the
                //     `CoercesTo<schema_type>` (so that we still check for correctness)

                let new_type_that_coerces_to_schema_type =
                    format_ident!("CoercionProxyForField{field_idx}");
                field_output_types.push(quote! {
                    #vis struct #new_type_that_coerces_to_schema_type;
                    cynic::impl_coercions!(#new_type_that_coerces_to_schema_type [] [], #schema_module::#graphql_type);
                });
                coercion_checks.push(quote! {
                    cynic::assert_impl!(#ty [#impl_generics] [#where_clause]: cynic::coercions::CoercesTo<#schema_module::#graphql_type>);
                });

                // Turn that from an ident into a type
                syn::parse_quote! { #new_type_that_coerces_to_schema_type }
            }
        };
        TurnLifetimesToStatic.visit_type_mut(&mut ty_for_fields_struct);
        let name_str =
            proc_macro2::Literal::string(&f.graphql_ident(input.rename_all).graphql_name());

        field_funcs.push(quote! {
            #vis fn #name() -> cynic::variables::VariableDefinition<Self, #ty_for_fields_struct> {
                cynic::variables::VariableDefinition::new(#name_str)
            }
        });

        variables.push(quote! {
            (#name_str, <#ty as #schema_module::variable::Variable>::TYPE)
        });

        match f.skip_serializing_if {
            Some(skip_check_fn) => {
                let skip_check_fn = &*skip_check_fn;
                field_inserts.push(quote! {
                    if !#skip_check_fn(&self.#name) {
                        map_serializer.serialize_entry(#name_str, &self.#name)?;
                    }
                })
            }
            None => field_inserts.push(quote! {
                map_serializer.serialize_entry(#name_str, &self.#name)?;
            }),
        }
    }

    let map_len = field_inserts.len();

    let ident_span = ident.span();
    let fields_struct = quote_spanned! { ident_span =>
        #vis struct #fields_struct_ident;

        impl cynic::QueryVariablesFields for #fields_struct_ident {}

        impl cynic::queries::VariableMatch<#fields_struct_ident> for #fields_struct_ident {}

        const _: () = {
            #(
                #field_output_types
            )*

            impl #fields_struct_ident {
                #(
                    #field_funcs
                )*
            }
        };
    };

    Ok(quote! {

        #[automatically_derived]
        impl #impl_generics cynic::QueryVariables for #ident #ty_generics #where_clause {
            type Fields = #fields_struct_ident;
            const VARIABLES: &'static [(&'static str, cynic::variables::VariableType)]
                = &[#(#variables),*];
        }

        #[automatically_derived]
        impl #impl_generics_with_ser cynic::serde::Serialize for #ident #ty_generics #where_clause_with_ser {
            fn serialize<__S>(&self, serializer: __S) -> Result<__S::Ok, __S::Error>
            where
                __S: cynic::serde::Serializer,
            {
                use cynic::serde::ser::SerializeMap;
                #(#coercion_checks)*

                let mut map_serializer = serializer.serialize_map(Some(#map_len))?;

                #(#field_inserts)*

                map_serializer.end()
            }
        }

        #fields_struct
    })
}

struct TurnLifetimesToStatic;
impl VisitMut for TurnLifetimesToStatic {
    fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) {
        i.ident = format_ident!("static");
        visit_mut::visit_lifetime_mut(self, i)
    }
}