Skip to content

Commit da80b9a

Browse files
authored
Merge pull request #359 from jakmeier/external_enum
Option for externally defined enums
2 parents c51adb1 + 1dc2c1b commit da80b9a

File tree

10 files changed

+179
-1
lines changed

10 files changed

+179
-1
lines changed

graphql_client/tests/extern_enums.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use graphql_client::*;
2+
use serde::Deserialize;
3+
4+
/*
5+
* Enums under test
6+
*
7+
* They rename the fields to use SCREAMING_SNAKE_CASE for deserialization, as it is the standard for GraphQL enums.
8+
*/
9+
#[derive(Deserialize, Debug, PartialEq)]
10+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11+
pub enum Direction {
12+
North,
13+
East,
14+
South,
15+
West,
16+
}
17+
18+
#[derive(Deserialize, Debug, PartialEq)]
19+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
20+
pub enum DistanceUnit {
21+
Meter,
22+
Feet,
23+
SomethingElseWithMultipleWords,
24+
}
25+
26+
/* Queries */
27+
28+
// Minimal setup using extern enum.
29+
#[derive(GraphQLQuery)]
30+
#[graphql(
31+
schema_path = "tests/extern_enums/schema.graphql",
32+
query_path = "tests/extern_enums/single_extern_enum_query.graphql",
33+
extern_enums("DistanceUnit")
34+
)]
35+
pub struct SingleExternEnumQuery;
36+
37+
// Tests using multiple externally defined enums. Also covers mixing with derived traits and with nullable GraphQL enum values.
38+
#[derive(GraphQLQuery)]
39+
#[graphql(
40+
schema_path = "tests/extern_enums/schema.graphql",
41+
query_path = "tests/extern_enums/multiple_extern_enums_query.graphql",
42+
response_derives = "Debug, PartialEq",
43+
extern_enums("Direction", "DistanceUnit")
44+
)]
45+
pub struct MultipleExternEnumsQuery;
46+
47+
/* Tests */
48+
49+
#[test]
50+
fn single_extern_enum() {
51+
const RESPONSE: &str = include_str!("extern_enums/single_extern_enum_response.json");
52+
53+
println!("{:?}", RESPONSE);
54+
let response_data: single_extern_enum_query::ResponseData =
55+
serde_json::from_str(RESPONSE).unwrap();
56+
57+
println!("{:?}", response_data.unit);
58+
59+
let expected = single_extern_enum_query::ResponseData {
60+
unit: DistanceUnit::Meter,
61+
};
62+
63+
assert_eq!(response_data.unit, expected.unit);
64+
}
65+
66+
#[test]
67+
fn multiple_extern_enums() {
68+
const RESPONSE: &str = include_str!("extern_enums/multiple_extern_enums_response.json");
69+
70+
println!("{:?}", RESPONSE);
71+
let response_data: multiple_extern_enums_query::ResponseData =
72+
serde_json::from_str(RESPONSE).unwrap();
73+
74+
println!("{:?}", response_data);
75+
76+
let expected = multiple_extern_enums_query::ResponseData {
77+
distance: 100,
78+
direction: Some(Direction::North),
79+
unit: DistanceUnit::SomethingElseWithMultipleWords,
80+
};
81+
82+
assert_eq!(response_data, expected);
83+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
query MultipleExternEnumsQuery {
2+
distance
3+
unit
4+
direction
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"distance": 100,
3+
"unit": "SOMETHING_ELSE_WITH_MULTIPLE_WORDS",
4+
"direction": "NORTH"
5+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
schema {
2+
query: ExternEnumQueryRoot
3+
}
4+
5+
enum Direction {
6+
NORTH
7+
EAST
8+
SOUTH
9+
WEST
10+
}
11+
12+
enum DistanceUnit {
13+
METER
14+
FEET
15+
SOMETHING_ELSE_WITH_MULTIPLE_WORDS
16+
}
17+
18+
type ExternEnumQueryRoot {
19+
distance: Int!
20+
unit: DistanceUnit!
21+
direction: Direction
22+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
query SingleExternEnumQuery {
2+
unit
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"unit": "METER"
3+
}

graphql_client_codegen/src/codegen/enums.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>(
2323
);
2424
let normalization = options.normalization();
2525

26-
all_used_types.enums(query.schema).map(move |(_id, r#enum)| {
26+
all_used_types.enums(query.schema)
27+
.filter(move |(_id, r#enum)| !options.extern_enums().contains(&r#enum.name))
28+
.map(move |(_id, r#enum)| {
2729
let variant_names: Vec<TokenStream> = r#enum
2830
.variants
2931
.iter()

graphql_client_codegen/src/codegen_options.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub struct GraphQLClientCodegenOptions {
4141
normalization: Normalization,
4242
/// Custom scalar definitions module path
4343
custom_scalars_module: Option<syn::Path>,
44+
/// List of externally defined enum types. Type names must match those used in the schema exactly.
45+
extern_enums: Vec<String>,
4446
}
4547

4648
impl GraphQLClientCodegenOptions {
@@ -59,6 +61,7 @@ impl GraphQLClientCodegenOptions {
5961
schema_file: Default::default(),
6062
normalization: Normalization::None,
6163
custom_scalars_module: Default::default(),
64+
extern_enums: Default::default(),
6265
}
6366
}
6467

@@ -187,4 +190,14 @@ impl GraphQLClientCodegenOptions {
187190
pub fn set_custom_scalars_module(&mut self, module: syn::Path) {
188191
self.custom_scalars_module = Some(module)
189192
}
193+
194+
/// Get the externally defined enums type names
195+
pub fn extern_enums(&self) -> &[String] {
196+
&self.extern_enums
197+
}
198+
199+
/// Set the externally defined enums type names
200+
pub fn set_extern_enums(&mut self, enums: Vec<String>) {
201+
self.extern_enums = enums;
202+
}
190203
}

graphql_query_derive/src/attributes.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,42 @@ pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, syn::E
3636
Err(syn::Error::new_spanned(ast, "Attribute not found"))
3737
}
3838

39+
/// Extract a list of configuration parameter values specified in the `graphql` attribute.
40+
pub fn extract_attr_list(ast: &syn::DeriveInput, attr: &str) -> Result<Vec<String>, syn::Error> {
41+
let attributes = &ast.attrs;
42+
let graphql_path = path_to_match();
43+
let attribute = attributes
44+
.iter()
45+
.find(|attr| attr.path == graphql_path)
46+
.ok_or_else(|| syn::Error::new_spanned(ast, "The graphql attribute is missing"))?;
47+
if let syn::Meta::List(items) = &attribute.parse_meta().expect("Attribute is well formatted") {
48+
for item in items.nested.iter() {
49+
if let syn::NestedMeta::Meta(syn::Meta::List(value_list)) = item {
50+
if let Some(ident) = value_list.path.get_ident() {
51+
if ident == attr {
52+
return value_list
53+
.nested
54+
.iter()
55+
.map(|lit| {
56+
if let syn::NestedMeta::Lit(syn::Lit::Str(lit)) = lit {
57+
Ok(lit.value())
58+
} else {
59+
Err(syn::Error::new_spanned(
60+
lit,
61+
"Attribute inside value list must be a literal",
62+
))
63+
}
64+
})
65+
.collect();
66+
}
67+
}
68+
}
69+
}
70+
}
71+
72+
Err(syn::Error::new_spanned(ast, "Attribute not found"))
73+
}
74+
3975
/// Get the deprecation from a struct attribute in the derive case.
4076
pub fn extract_deprecation_strategy(
4177
ast: &syn::DeriveInput,

graphql_query_derive/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ fn build_graphql_client_derive_options(
7171
let variables_derives = attributes::extract_attr(input, "variables_derives").ok();
7272
let response_derives = attributes::extract_attr(input, "response_derives").ok();
7373
let custom_scalars_module = attributes::extract_attr(input, "custom_scalars_module").ok();
74+
let extern_enums = attributes::extract_attr_list(input, "extern_enums").ok();
7475

7576
let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Derive);
7677
options.set_query_file(query_path);
@@ -105,6 +106,11 @@ fn build_graphql_client_derive_options(
105106
options.set_custom_scalars_module(custom_scalars_module);
106107
}
107108

109+
// The user can specify a list of enums types that are defined externally, rather than generated by this library
110+
if let Some(extern_enums) = extern_enums {
111+
options.set_extern_enums(extern_enums);
112+
}
113+
108114
options.set_struct_ident(input.ident.clone());
109115
options.set_module_visibility(input.vis.clone());
110116
options.set_operation_name(input.ident.to_string());

0 commit comments

Comments
 (0)