cynic_codegen/schema_for_derives/
mod.rs1use 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 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}