@@ -4,7 +4,10 @@ use std::path::Path;
4
4
5
5
use proc_macro2:: { Ident , Span , TokenStream } ;
6
6
use quote:: { quote, ToTokens } ;
7
- use syn:: { self , parse:: Parse , parse:: ParseStream , parse_macro_input, Expr , FnArg , LitStr , Token } ;
7
+ use syn:: {
8
+ self , parse:: Parse , parse:: ParseStream , parse_macro_input, Expr , ExprArray , FnArg , LitStr ,
9
+ Token ,
10
+ } ;
8
11
9
12
#[ derive( Clone , Default ) ]
10
13
struct ShopifyFunctionArgs {
@@ -123,6 +126,7 @@ struct ShopifyFunctionTargetArgs {
123
126
schema_path : Option < LitStr > ,
124
127
input_stream : Option < Expr > ,
125
128
output_stream : Option < Expr > ,
129
+ extern_enums : Option < ExprArray > ,
126
130
}
127
131
128
132
impl ShopifyFunctionTargetArgs {
@@ -156,6 +160,8 @@ impl Parse for ShopifyFunctionTargetArgs {
156
160
args. input_stream = Some ( Self :: parse :: < kw:: input_stream , Expr > ( & input) ?) ;
157
161
} else if lookahead. peek ( kw:: output_stream) {
158
162
args. output_stream = Some ( Self :: parse :: < kw:: output_stream , Expr > ( & input) ?) ;
163
+ } else if lookahead. peek ( kw:: extern_enums) {
164
+ args. extern_enums = Some ( Self :: parse :: < kw:: extern_enums , ExprArray > ( & input) ?) ;
159
165
} else {
160
166
return Err ( lookahead. error ( ) ) ;
161
167
}
@@ -170,6 +176,7 @@ struct GenerateTypeArgs {
170
176
schema_path : Option < LitStr > ,
171
177
input_stream : Option < Expr > ,
172
178
output_stream : Option < Expr > ,
179
+ extern_enums : Option < ExprArray > ,
173
180
}
174
181
175
182
impl GenerateTypeArgs {
@@ -199,6 +206,8 @@ impl Parse for GenerateTypeArgs {
199
206
args. input_stream = Some ( Self :: parse :: < kw:: input_stream , Expr > ( & input) ?) ;
200
207
} else if lookahead. peek ( kw:: output_stream) {
201
208
args. output_stream = Some ( Self :: parse :: < kw:: output_stream , Expr > ( & input) ?) ;
209
+ } else if lookahead. peek ( kw:: extern_enums) {
210
+ args. extern_enums = Some ( Self :: parse :: < kw:: extern_enums , ExprArray > ( & input) ?) ;
202
211
} else {
203
212
return Err ( lookahead. error ( ) ) ;
204
213
}
@@ -256,6 +265,27 @@ fn extract_shopify_function_return_type(ast: &syn::ItemFn) -> Result<&syn::Ident
256
265
Ok ( & path. path . segments . last ( ) . as_ref ( ) . unwrap ( ) . ident )
257
266
}
258
267
268
+ /// Generates code for a Function using an explicitly-named target. This will:
269
+ /// - Generate a module to host the generated types.
270
+ /// - Generate types based on the GraphQL schema for the Function input and output.
271
+ /// - Define a wrapper function that's exported to Wasm. The wrapper handles
272
+ /// decoding the input from STDIN, and encoding the output to STDOUT.
273
+ ///
274
+ ///
275
+ /// The macro takes the following parameters:
276
+ /// - `query_path`: A path to a GraphQL query, whose result will be used
277
+ /// as the input for the function invocation. The query MUST be named "Input".
278
+ /// - `schema_path`: A path to Shopify's GraphQL schema definition. You
279
+ /// can find it in the `example` folder of the repo, or use the CLI
280
+ /// to download a fresh copy.
281
+ /// - `target` (optional): The API-specific handle for the target if the function name does not match the target handle as `snake_case`
282
+ /// - `module_name` (optional): The name of the generated module.
283
+ /// - default: The target handle as `snake_case`
284
+ /// - `extern_enums` (optional): A list of enums for which an external type should be used.
285
+ /// For those, code generation will be skipped. This is useful to skip
286
+ /// codegen for large enums, or share enums between multiple functions.
287
+ /// Example: `extern_enums = ["LanguageCode"]`
288
+ /// - default: `["LanguageCode", "CountryCode", "CurrencyCode"]`
259
289
#[ proc_macro_attribute]
260
290
pub fn shopify_function_target (
261
291
attr : proc_macro:: TokenStream ,
@@ -282,17 +312,21 @@ pub fn shopify_function_target(
282
312
283
313
let query_path = args. query_path . expect ( "No value given for query_path" ) ;
284
314
let schema_path = args. schema_path . expect ( "No value given for schema_path" ) ;
315
+ let extern_enums = args. extern_enums . as_ref ( ) . map ( extract_extern_enums) ;
285
316
let output_query_file_name = format ! ( ".{}{}" , & target_handle_string, OUTPUT_QUERY_FILE_NAME ) ;
286
317
287
318
let input_struct = generate_struct (
288
319
"Input" ,
289
320
query_path. value ( ) . as_str ( ) ,
290
321
schema_path. value ( ) . as_str ( ) ,
322
+ extern_enums. as_deref ( ) ,
291
323
) ;
324
+
292
325
let output_struct = generate_struct (
293
326
"Output" ,
294
327
& output_query_file_name,
295
328
schema_path. value ( ) . as_str ( ) ,
329
+ extern_enums. as_deref ( ) ,
296
330
) ;
297
331
if let Err ( error) = extract_shopify_function_return_type ( & ast) {
298
332
return error. to_compile_error ( ) . into ( ) ;
@@ -353,12 +387,17 @@ const OUTPUT_QUERY_FILE_NAME: &str = ".output.graphql";
353
387
/// modules generate Rust types from the GraphQL schema file for the Function input
354
388
/// and output respectively.
355
389
///
356
- /// The macro takes two parameters:
390
+ /// The macro takes the following parameters:
357
391
/// - `query_path`: A path to a GraphQL query, whose result will be used
358
392
/// as the input for the function invocation. The query MUST be named "Input".
359
393
/// - `schema_path`: A path to Shopify's GraphQL schema definition. You
360
394
/// can find it in the `example` folder of the repo, or use the CLI
361
- /// to download a fresh copy (not implemented yet).
395
+ /// to download a fresh copy.
396
+ /// - `extern_enums`: An list of enums for which an external type should be used.
397
+ /// For those, code generation will be skipped. This is useful to skip
398
+ /// codegen for large enums, or share enums between multiple functions.
399
+ /// Example: `extern_enums = ["LanguageCode"]`
400
+ /// Defaults to: `["LanguageCode", "CountryCode", "CurrencyCode"]`
362
401
///
363
402
/// Note: This macro creates a file called `.output.graphql` in the root
364
403
/// directory of the project. It can be safely added to your `.gitignore`. We
@@ -375,9 +414,19 @@ pub fn generate_types(attr: proc_macro::TokenStream) -> proc_macro::TokenStream
375
414
. schema_path
376
415
. expect ( "No value given for schema_path" )
377
416
. value ( ) ;
378
-
379
- let input_struct = generate_struct ( "Input" , query_path. as_str ( ) , schema_path. as_str ( ) ) ;
380
- let output_struct = generate_struct ( "Output" , OUTPUT_QUERY_FILE_NAME , schema_path. as_str ( ) ) ;
417
+ let extern_enums = args. extern_enums . as_ref ( ) . map ( extract_extern_enums) ;
418
+ let input_struct = generate_struct (
419
+ "Input" ,
420
+ query_path. as_str ( ) ,
421
+ schema_path. as_str ( ) ,
422
+ extern_enums. as_deref ( ) ,
423
+ ) ;
424
+ let output_struct = generate_struct (
425
+ "Output" ,
426
+ OUTPUT_QUERY_FILE_NAME ,
427
+ schema_path. as_str ( ) ,
428
+ extern_enums. as_deref ( ) ,
429
+ ) ;
381
430
let output_query =
382
431
"mutation Output($result: FunctionResult!) {\n handleResult(result: $result)\n }\n " ;
383
432
@@ -390,16 +439,28 @@ pub fn generate_types(attr: proc_macro::TokenStream) -> proc_macro::TokenStream
390
439
. into ( )
391
440
}
392
441
393
- fn generate_struct ( name : & str , query_path : & str , schema_path : & str ) -> TokenStream {
442
+ const DEFAULT_EXTERN_ENUMS : & [ & str ] = & [ "LanguageCode" , "CountryCode" , "CurrencyCode" ] ;
443
+
444
+ fn generate_struct (
445
+ name : & str ,
446
+ query_path : & str ,
447
+ schema_path : & str ,
448
+ extern_enums : Option < & [ String ] > ,
449
+ ) -> TokenStream {
394
450
let name_ident = Ident :: new ( name, Span :: mixed_site ( ) ) ;
395
451
452
+ let extern_enums = extern_enums
453
+ . map ( |e| e. to_owned ( ) )
454
+ . unwrap_or_else ( || DEFAULT_EXTERN_ENUMS . iter ( ) . map ( |e| e. to_string ( ) ) . collect ( ) ) ;
455
+
396
456
quote ! {
397
457
#[ derive( graphql_client:: GraphQLQuery , Clone , Debug , serde:: Deserialize , PartialEq ) ]
398
458
#[ graphql(
399
459
query_path = #query_path,
400
460
schema_path = #schema_path,
401
461
response_derives = "Clone,Debug,PartialEq,Deserialize,Serialize" ,
402
462
variables_derives = "Clone,Debug,PartialEq,Deserialize" ,
463
+ extern_enums( #( #extern_enums) , * ) ,
403
464
skip_serializing_none
404
465
) ]
405
466
pub struct #name_ident;
@@ -415,6 +476,24 @@ fn write_output_query_file(output_query_file_name: &str, contents: &str) {
415
476
. unwrap_or_else ( |_| panic ! ( "Could not write to {}" , output_query_file_name) ) ;
416
477
}
417
478
479
+ fn extract_extern_enums ( extern_enums : & ExprArray ) -> Vec < String > {
480
+ let extern_enum_error_msg = r#"The `extern_enums` attribute expects comma separated string literals\n\n= help: use `extern_enums = ["Enum1", "Enum2"]`"# ;
481
+ extern_enums
482
+ . elems
483
+ . iter ( )
484
+ . map ( |expr| {
485
+ let value = match expr {
486
+ Expr :: Lit ( lit) => lit. lit . clone ( ) ,
487
+ _ => panic ! ( "{}" , extern_enum_error_msg) ,
488
+ } ;
489
+ match value {
490
+ syn:: Lit :: Str ( lit) => lit. value ( ) ,
491
+ _ => panic ! ( "{}" , extern_enum_error_msg) ,
492
+ }
493
+ } )
494
+ . collect ( )
495
+ }
496
+
418
497
#[ cfg( test) ]
419
498
mod tests { }
420
499
@@ -425,4 +504,5 @@ mod kw {
425
504
syn:: custom_keyword!( schema_path) ;
426
505
syn:: custom_keyword!( input_stream) ;
427
506
syn:: custom_keyword!( output_stream) ;
507
+ syn:: custom_keyword!( extern_enums) ;
428
508
}
0 commit comments