cynic_codegen/schema_for_derives/
mod.rs

1// TODO: docstring.
2use darling::{util::SpannedValue, FromMeta};
3use proc_macro2::TokenStream;
4
5mod utils;
6
7use utils::Derive;
8
9#[derive(Debug, FromMeta)]
10struct AddSchemaAttrParams {
11    file: SpannedValue<String>,
12
13    #[darling(default)]
14    module: Option<String>,
15}
16
17pub fn add_schema_attrs_to_derives(
18    args: Vec<darling::ast::NestedMeta>,
19    query_module: syn::ItemMod,
20) -> Result<TokenStream, syn::Error> {
21    match AddSchemaAttrParams::from_list(&args) {
22        Ok(args) => Ok(add_schema_attrs_to_derives_impl(args, query_module)),
23        Err(e) => Ok(e.write_errors()),
24    }
25}
26
27#[derive(Debug, FromMeta)]
28struct QueryModuleParams {
29    schema_path: SpannedValue<String>,
30    // TODO: consider getting rid of query_module at some point (or at least making optional)
31    query_module: Option<String>,
32}
33
34fn add_schema_attrs_to_derives_impl(
35    args: AddSchemaAttrParams,
36    query_module: syn::ItemMod,
37) -> TokenStream {
38    use quote::quote;
39
40    if query_module.content.is_none() {
41        return quote! { #query_module };
42    }
43
44    let (_, module_items) = query_module.content.unwrap();
45
46    let module_items = module_items
47        .into_iter()
48        .map(|item| insert_cynic_attrs(&args, item));
49
50    let attrs = query_module.attrs;
51    let visibility = query_module.vis;
52    let module_name = query_module.ident;
53
54    quote! {
55        #(#attrs)*
56        #visibility mod #module_name {
57            #(#module_items)*
58        }
59    }
60}
61
62fn insert_cynic_attrs(args: &AddSchemaAttrParams, item: syn::Item) -> syn::Item {
63    use syn::Item;
64
65    let derives = utils::find_derives(&item);
66    let derive = derives.first();
67    if derive.is_none() {
68        return item;
69    }
70
71    let derive = derive.unwrap();
72
73    let required_attrs = RequiredAttributes::for_derive(derive);
74
75    match derive {
76        Derive::InlineFragments | Derive::Enum => {
77            if let Item::Enum(mut en) = item {
78                let attrs = required_attrs.with_current_attrs(&en.attrs);
79                attrs.add_missing_attributes(&mut en.attrs, args);
80                Item::Enum(en)
81            } else {
82                item
83            }
84        }
85        Derive::QueryFragment | Derive::QueryVariables | Derive::InputObject | Derive::Scalar => {
86            if let Item::Struct(mut st) = item {
87                let attrs = required_attrs.with_current_attrs(&st.attrs);
88                attrs.add_missing_attributes(&mut st.attrs, args);
89                Item::Struct(st)
90            } else {
91                item
92            }
93        }
94    }
95}
96
97#[derive(Debug)]
98struct RequiredAttributes {
99    needs_schema_path: bool,
100    needs_schema_module: bool,
101}
102
103impl RequiredAttributes {
104    fn for_derive(d: &Derive) -> RequiredAttributes {
105        match d {
106            Derive::QueryVariables | Derive::Scalar => RequiredAttributes {
107                needs_schema_path: false,
108                needs_schema_module: true,
109            },
110            _ => RequiredAttributes {
111                needs_schema_path: true,
112                needs_schema_module: true,
113            },
114        }
115    }
116
117    fn with_current_attrs(mut self, attrs: &[syn::Attribute]) -> Self {
118        use darling::ast::NestedMeta;
119        use syn::Meta;
120
121        for attr in attrs {
122            if attr.path().is_ident("cynic") {
123                if let Meta::List(meta_list) = &attr.meta {
124                    for nested in
125                        NestedMeta::parse_meta_list(meta_list.tokens.clone()).unwrap_or_default()
126                    {
127                        if let NestedMeta::Meta(Meta::NameValue(name_val)) = nested {
128                            if name_val.path.is_ident("schema_path") {
129                                self.needs_schema_path = false;
130                            } else if name_val.path.is_ident("schema_module") {
131                                self.needs_schema_module = false;
132                            }
133                        }
134                    }
135                }
136            }
137        }
138
139        self
140    }
141
142    fn add_missing_attributes(self, attrs: &mut Vec<syn::Attribute>, args: &AddSchemaAttrParams) {
143        if self.needs_schema_path {
144            let schema_path = proc_macro2::Literal::string(&args.file);
145            attrs.push(syn::parse_quote! {
146                #[cynic(schema_path = #schema_path)]
147            })
148        }
149
150        if self.needs_schema_module {
151            let query_module =
152                proc_macro2::Literal::string(args.module.as_deref().unwrap_or("schema"));
153
154            attrs.push(syn::parse_quote! {
155                #[cynic(schema_module = #query_module)]
156            })
157        }
158    }
159}
160
161impl From<QueryModuleParams> for AddSchemaAttrParams {
162    fn from(params: QueryModuleParams) -> Self {
163        AddSchemaAttrParams {
164            file: params.schema_path,
165            module: params.query_module,
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    fn args() -> AddSchemaAttrParams {
175        AddSchemaAttrParams {
176            file: "test.graphql".to_string().into(),
177            module: "schema".to_string().into(),
178        }
179    }
180
181    #[test]
182    fn test_insert_cynic_attrs() {
183        let item: syn::Item = syn::parse_quote! {
184            #[derive(cynic::QueryFragment)]
185            struct Test {
186                a: String
187            }
188        };
189
190        let result = insert_cynic_attrs(&args(), item);
191
192        assert_eq!(
193            result,
194            syn::parse_quote! {
195                #[derive(cynic::QueryFragment)]
196                #[cynic(schema_path = "test.graphql")]
197                #[cynic(schema_module = "schema")]
198                struct Test {
199                    a: String
200                }
201            }
202        )
203    }
204
205    #[test]
206    fn test_insert_cynic_attrs_when_already_inserted() {
207        let item: syn::Item = syn::parse_quote! {
208            #[derive(cynic::QueryFragment)]
209            #[cynic(schema_path = "other.graphql", schema_module = "something")]
210            struct Test {
211                a: String
212            }
213        };
214
215        let result = insert_cynic_attrs(&args(), item);
216
217        assert_eq!(
218            result,
219            syn::parse_quote! {
220                #[derive(cynic::QueryFragment)]
221                #[cynic(schema_path = "other.graphql", schema_module = "something")]
222                struct Test {
223                    a: String
224                }
225            }
226        )
227    }
228}