Skip to content

Commit 9685a9d

Browse files
authored
Merge pull request #376 from visd0m/enum-other-variant-for-fragments
Enum other variant for fragments
2 parents e085850 + 641b1a0 commit 9685a9d

File tree

10 files changed

+240
-4
lines changed

10 files changed

+240
-4
lines changed

graphql_client_cli/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ OPTIONS:
5757
-s, --schema-path <schema_path> Path to GraphQL schema file (.json or .graphql).
5858
-o, --selected-operation <selected_operation>
5959
Name of target query. If you don't set this parameter, cli generate all queries in query file.
60+
--fragments-other-variant
61+
Generate an Unknow variant for enums generated by fragments.
6062
6163
6264
ARGS:

graphql_client_cli/src/generate.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub(crate) struct CliCodegenParams {
2121
pub module_visibility: Option<String>,
2222
pub output_directory: Option<PathBuf>,
2323
pub custom_scalars_module: Option<String>,
24+
pub fragments_other_variant: bool,
2425
}
2526

2627
pub(crate) fn generate_code(params: CliCodegenParams) -> CliResult<()> {
@@ -35,6 +36,7 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> CliResult<()> {
3536
schema_path,
3637
selected_operation,
3738
custom_scalars_module,
39+
fragments_other_variant,
3840
} = params;
3941

4042
let deprecation_strategy = deprecation_strategy.as_ref().and_then(|s| s.parse().ok());
@@ -48,6 +50,8 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> CliResult<()> {
4850
.into(),
4951
);
5052

53+
options.set_fragments_other_variant(fragments_other_variant);
54+
5155
if let Some(selected_operation) = selected_operation {
5256
options.set_operation_name(selected_operation);
5357
}

graphql_client_cli/src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use error::Error;
99
use log::Level;
1010
use std::path::PathBuf;
1111
use structopt::StructOpt;
12+
use Cli::Generate;
1213

1314
type CliResult<T> = Result<T, Error>;
1415

@@ -76,6 +77,10 @@ enum Cli {
7677
/// --custom-scalars-module='crate::gql::custom_scalars'
7778
#[structopt(short = "p", long = "custom-scalars-module")]
7879
custom_scalars_module: Option<String>,
80+
/// A flag indicating if the enum representing the variants of a fragment union/interface should have a "other" variant
81+
/// --fragments-other-variant
82+
#[structopt(long = "fragments-other-variant")]
83+
fragments_other_variant: bool,
7984
},
8085
}
8186

@@ -97,7 +102,7 @@ fn main() -> CliResult<()> {
97102
headers,
98103
no_ssl,
99104
),
100-
Cli::Generate {
105+
Generate {
101106
variables_derives,
102107
response_derives,
103108
deprecation_strategy,
@@ -108,6 +113,7 @@ fn main() -> CliResult<()> {
108113
schema_path,
109114
selected_operation,
110115
custom_scalars_module,
116+
fragments_other_variant,
111117
} => generate::generate_code(generate::CliCodegenParams {
112118
query_path,
113119
schema_path,
@@ -119,6 +125,7 @@ fn main() -> CliResult<()> {
119125
module_visibility,
120126
output_directory,
121127
custom_scalars_module,
128+
fragments_other_variant,
122129
}),
123130
}
124131
}

graphql_client_codegen/src/codegen/selection.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ fn calculate_selection<'a>(
197197
name: variant_name_str.into(),
198198
variant_type: Some(variant_struct_name_str.clone().into()),
199199
on: struct_id,
200+
is_default_variant: false,
200201
});
201202

202203
let expanded_type = ExpandedType {
@@ -247,9 +248,19 @@ fn calculate_selection<'a>(
247248
name: variant_name_str.into(),
248249
on: struct_id,
249250
variant_type: None,
251+
is_default_variant: false,
250252
});
251253
}
252254
}
255+
256+
if *options.fragments_other_variant() {
257+
context.push_variant(ExpandedVariant {
258+
name: "Unknown".into(),
259+
on: struct_id,
260+
variant_type: None,
261+
is_default_variant: true,
262+
});
263+
}
253264
}
254265
}
255266

@@ -430,6 +441,7 @@ struct ExpandedVariant<'a> {
430441
name: Cow<'a, str>,
431442
variant_type: Option<Cow<'a, str>>,
432443
on: ResponseTypeId,
444+
is_default_variant: bool,
433445
}
434446

435447
impl<'a> ExpandedVariant<'a> {
@@ -440,7 +452,14 @@ impl<'a> ExpandedVariant<'a> {
440452
quote!((#ident))
441453
});
442454

443-
quote!(#name_ident #optional_type_ident)
455+
if self.is_default_variant {
456+
quote! {
457+
#[serde(other)]
458+
#name_ident #optional_type_ident
459+
}
460+
} else {
461+
quote!(#name_ident #optional_type_ident)
462+
}
444463
}
445464
}
446465

graphql_client_codegen/src/codegen_options.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ pub struct GraphQLClientCodegenOptions {
4343
custom_scalars_module: Option<syn::Path>,
4444
/// List of externally defined enum types. Type names must match those used in the schema exactly.
4545
extern_enums: Vec<String>,
46+
/// Flag to trigger generation of Other variant for fragments Enum
47+
fragments_other_variant: bool,
4648
}
4749

4850
impl GraphQLClientCodegenOptions {
@@ -62,6 +64,7 @@ impl GraphQLClientCodegenOptions {
6264
normalization: Normalization::None,
6365
custom_scalars_module: Default::default(),
6466
extern_enums: Default::default(),
67+
fragments_other_variant: Default::default(),
6568
}
6669
}
6770

@@ -200,4 +203,14 @@ impl GraphQLClientCodegenOptions {
200203
pub fn set_extern_enums(&mut self, enums: Vec<String>) {
201204
self.extern_enums = enums;
202205
}
206+
207+
/// Set the graphql client codegen options's fragments other variant.
208+
pub fn set_fragments_other_variant(&mut self, fragments_other_variant: bool) {
209+
self.fragments_other_variant = fragments_other_variant;
210+
}
211+
212+
/// Get a reference to the graphql client codegen options's fragments other variant.
213+
pub fn fragments_other_variant(&self) -> &bool {
214+
&self.fragments_other_variant
215+
}
203216
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
query FooBarsQuery {
2+
fooBars {
3+
fooBars {
4+
__typename
5+
... on Foo {
6+
fooField
7+
}
8+
... on Bar {
9+
barField
10+
}
11+
... on FooBar {
12+
fooBarField
13+
}
14+
}
15+
}
16+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
schema {
2+
query: Query
3+
mutation: Mutation
4+
}
5+
6+
directive @defer on FIELD
7+
8+
type Query {
9+
fooBars: Self
10+
}
11+
12+
type Self {
13+
fooBars: Result
14+
}
15+
16+
union Result = Foo | Bar | FooBar
17+
18+
type Foo {
19+
fooField: String!
20+
}
21+
22+
type Bar {
23+
barField: String!
24+
}
25+
26+
type FooBar {
27+
fooBarField: String!
28+
}

graphql_client_codegen/src/tests/mod.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use crate::{generated_module, schema::Schema, CodegenMode, GraphQLClientCodegenOptions};
2+
13
#[test]
24
fn schema_with_keywords_works() {
3-
use crate::{generated_module, schema::Schema, CodegenMode, GraphQLClientCodegenOptions};
4-
55
let query_string = include_str!("keywords_query.graphql");
66
let query = graphql_parser::parse_query(query_string).expect("Parse keywords query");
77
let schema = graphql_parser::parse_schema(include_str!("keywords_schema.graphql"))
@@ -37,3 +37,80 @@ fn schema_with_keywords_works() {
3737
};
3838
}
3939
}
40+
41+
#[test]
42+
fn fragments_other_variant_should_generate_unknown_other_variant() {
43+
let query_string = include_str!("foobars_query.graphql");
44+
let query = graphql_parser::parse_query(query_string).expect("Parse foobars query");
45+
let schema = graphql_parser::parse_schema(include_str!("foobars_schema.graphql"))
46+
.expect("Parse foobars schema");
47+
let schema = Schema::from(schema);
48+
49+
let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Cli);
50+
51+
options.set_fragments_other_variant(true);
52+
let query = crate::query::resolve(&schema, &query).unwrap();
53+
54+
for (_id, operation) in query.operations() {
55+
let generated_tokens = generated_module::GeneratedModule {
56+
query_string,
57+
schema: &schema,
58+
operation: &operation.name,
59+
resolved_query: &query,
60+
options: &options,
61+
}
62+
.to_token_stream()
63+
.expect("Generate foobars module");
64+
let generated_code = generated_tokens.to_string();
65+
66+
let r: syn::parse::Result<proc_macro2::TokenStream> = syn::parse2(generated_tokens);
67+
match r {
68+
Ok(_) => {
69+
// Rust keywords should be escaped / renamed now
70+
assert!(generated_code.contains("# [serde (other)] Unknown"));
71+
assert!(generated_code.contains("Unknown"));
72+
}
73+
Err(e) => {
74+
panic!("Error: {}\n Generated content: {}\n", e, &generated_code);
75+
}
76+
};
77+
}
78+
}
79+
80+
#[test]
81+
fn fragments_other_variant_false_should_not_generate_unknown_other_variant() {
82+
let query_string = include_str!("foobars_query.graphql");
83+
let query = graphql_parser::parse_query(query_string).expect("Parse foobars query");
84+
let schema = graphql_parser::parse_schema(include_str!("foobars_schema.graphql"))
85+
.expect("Parse foobars schema");
86+
let schema = Schema::from(schema);
87+
88+
let options = GraphQLClientCodegenOptions::new(CodegenMode::Cli);
89+
90+
let query = crate::query::resolve(&schema, &query).unwrap();
91+
92+
for (_id, operation) in query.operations() {
93+
let generated_tokens = generated_module::GeneratedModule {
94+
query_string,
95+
schema: &schema,
96+
operation: &operation.name,
97+
resolved_query: &query,
98+
options: &options,
99+
}
100+
.to_token_stream()
101+
.expect("Generate foobars module");
102+
let generated_code = generated_tokens.to_string();
103+
104+
let r: syn::parse::Result<proc_macro2::TokenStream> = syn::parse2(generated_tokens);
105+
match r {
106+
Ok(_) => {
107+
// Rust keywords should be escaped / renamed now
108+
assert!(!generated_code.contains("# [serde (other)] Unknown"));
109+
assert!(!generated_code.contains("Unknown"));
110+
}
111+
Err(e) => {
112+
panic!("Error: {}\n Generated content: {}\n", e, &generated_code);
113+
}
114+
};
115+
}
116+
}

graphql_query_derive/src/attributes.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::str::FromStr;
2+
13
use graphql_client_codegen::deprecation::DeprecationStrategy;
24
use graphql_client_codegen::normalization::Normalization;
35

@@ -94,6 +96,13 @@ pub fn extract_normalization(ast: &syn::DeriveInput) -> Result<Normalization, sy
9496
.map_err(|_| syn::Error::new_spanned(ast, NORMALIZATION_ERROR))
9597
}
9698

99+
pub fn extract_fragments_other_variant(ast: &syn::DeriveInput) -> bool {
100+
extract_attr(&ast, "fragments_other_variant")
101+
.ok()
102+
.and_then(|s| FromStr::from_str(s.as_str()).ok())
103+
.unwrap_or(false)
104+
}
105+
97106
#[cfg(test)]
98107
mod test {
99108
use super::*;
@@ -151,4 +160,63 @@ mod test {
151160
Err(e) => assert_eq!(&format!("{}", e), DEPRECATION_ERROR),
152161
};
153162
}
163+
164+
#[test]
165+
fn test_fragments_other_variant_set_to_true() {
166+
let input = "
167+
#[derive(GraphQLQuery)]
168+
#[graphql(
169+
schema_path = \"x\",
170+
query_path = \"x\",
171+
fragments_other_variant = \"true\",
172+
)]
173+
struct MyQuery;
174+
";
175+
let parsed = syn::parse_str(input).unwrap();
176+
assert!(extract_fragments_other_variant(&parsed));
177+
}
178+
179+
#[test]
180+
fn test_fragments_other_variant_set_to_false() {
181+
let input = "
182+
#[derive(GraphQLQuery)]
183+
#[graphql(
184+
schema_path = \"x\",
185+
query_path = \"x\",
186+
fragments_other_variant = \"false\",
187+
)]
188+
struct MyQuery;
189+
";
190+
let parsed = syn::parse_str(input).unwrap();
191+
assert!(!extract_fragments_other_variant(&parsed));
192+
}
193+
194+
#[test]
195+
fn test_fragments_other_variant_set_to_invalid() {
196+
let input = "
197+
#[derive(GraphQLQuery)]
198+
#[graphql(
199+
schema_path = \"x\",
200+
query_path = \"x\",
201+
fragments_other_variant = \"invalid\",
202+
)]
203+
struct MyQuery;
204+
";
205+
let parsed = syn::parse_str(input).unwrap();
206+
assert!(!extract_fragments_other_variant(&parsed));
207+
}
208+
209+
#[test]
210+
fn test_fragments_other_variant_unset() {
211+
let input = "
212+
#[derive(GraphQLQuery)]
213+
#[graphql(
214+
schema_path = \"x\",
215+
query_path = \"x\",
216+
)]
217+
struct MyQuery;
218+
";
219+
let parsed = syn::parse_str(input).unwrap();
220+
assert!(!extract_fragments_other_variant(&parsed));
221+
}
154222
}

graphql_query_derive/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ fn build_graphql_client_derive_options(
6363
let response_derives = attributes::extract_attr(input, "response_derives").ok();
6464
let custom_scalars_module = attributes::extract_attr(input, "custom_scalars_module").ok();
6565
let extern_enums = attributes::extract_attr_list(input, "extern_enums").ok();
66+
let fragments_other_variant: bool = attributes::extract_fragments_other_variant(input);
6667

6768
let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Derive);
6869
options.set_query_file(query_path);
70+
options.set_fragments_other_variant(fragments_other_variant);
6971

7072
if let Some(variables_derives) = variables_derives {
7173
options.set_variables_derives(variables_derives);

0 commit comments

Comments
 (0)