Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions graphql_client/tests/extern_enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use graphql_client::*;
use serde::Deserialize;

/*
* Enums under test
*
* They rename the fields to use SCREAMING_SNAKE_CASE for deserialization, as it is the standard for GraphQL enums.
*/
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Direction {
North,
East,
South,
West,
}

#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DistanceUnit {
Meter,
Feet,
SomethingElseWithMultipleWords,
}

/* Queries */

// Minimal setup using extern enum.
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "tests/extern_enums/schema.graphql",
query_path = "tests/extern_enums/single_extern_enum_query.graphql",
extern_enums("DistanceUnit")
)]
pub struct SingleExternEnumQuery;

// Tests using multiple externally defined enums. Also covers mixing with derived traits and with nullable GraphQL enum values.
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "tests/extern_enums/schema.graphql",
query_path = "tests/extern_enums/multiple_extern_enums_query.graphql",
response_derives = "Debug, PartialEq",
extern_enums("Direction", "DistanceUnit")
)]
pub struct MultipleExternEnumsQuery;

/* Tests */

#[test]
fn single_extern_enum() {
const RESPONSE: &str = include_str!("extern_enums/single_extern_enum_response.json");

println!("{:?}", RESPONSE);
let response_data: single_extern_enum_query::ResponseData =
serde_json::from_str(RESPONSE).unwrap();

println!("{:?}", response_data.unit);

let expected = single_extern_enum_query::ResponseData {
unit: DistanceUnit::Meter,
};

assert_eq!(response_data.unit, expected.unit);
}

#[test]
fn multiple_extern_enums() {
const RESPONSE: &str = include_str!("extern_enums/multiple_extern_enums_response.json");

println!("{:?}", RESPONSE);
let response_data: multiple_extern_enums_query::ResponseData =
serde_json::from_str(RESPONSE).unwrap();

println!("{:?}", response_data);

let expected = multiple_extern_enums_query::ResponseData {
distance: 100,
direction: Some(Direction::North),
unit: DistanceUnit::SomethingElseWithMultipleWords,
};

assert_eq!(response_data, expected);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query MultipleExternEnumsQuery {
distance
unit
direction
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"distance": 100,
"unit": "SOMETHING_ELSE_WITH_MULTIPLE_WORDS",
"direction": "NORTH"
}
22 changes: 22 additions & 0 deletions graphql_client/tests/extern_enums/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
schema {
query: ExternEnumQueryRoot
}

enum Direction {
NORTH
EAST
SOUTH
WEST
}

enum DistanceUnit {
METER
FEET
SOMETHING_ELSE_WITH_MULTIPLE_WORDS
}

type ExternEnumQueryRoot {
distance: Int!
unit: DistanceUnit!
direction: Direction
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query SingleExternEnumQuery {
unit
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"unit": "METER"
}
4 changes: 3 additions & 1 deletion graphql_client_codegen/src/codegen/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>(
);
let normalization = options.normalization();

all_used_types.enums(query.schema).map(move |(_id, r#enum)| {
all_used_types.enums(query.schema)
.filter(move |(_id, r#enum)| !options.extern_enums().contains(&r#enum.name))
.map(move |(_id, r#enum)| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this just works because the generated modules already use super::*?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it very much relies on that. It's simple and it works. :) Unless you think use super::* might be removed in the future, I don't really see this as a problem. But happy to look into alternatives if you think there could be a cleaner solution.

let variant_names: Vec<TokenStream> = r#enum
.variants
.iter()
Expand Down
13 changes: 13 additions & 0 deletions graphql_client_codegen/src/codegen_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub struct GraphQLClientCodegenOptions {
normalization: Normalization,
/// Custom scalar definitions module path
custom_scalars_module: Option<syn::Path>,
/// List of externally defined enum types. Type names must match those used in the schema exactly.
extern_enums: Vec<String>,
}

impl GraphQLClientCodegenOptions {
Expand All @@ -59,6 +61,7 @@ impl GraphQLClientCodegenOptions {
schema_file: Default::default(),
normalization: Normalization::None,
custom_scalars_module: Default::default(),
extern_enums: Default::default(),
}
}

Expand Down Expand Up @@ -187,4 +190,14 @@ impl GraphQLClientCodegenOptions {
pub fn set_custom_scalars_module(&mut self, module: syn::Path) {
self.custom_scalars_module = Some(module)
}

/// Get the externally defined enums type names
pub fn extern_enums(&self) -> &[String] {
&self.extern_enums
}

/// Set the externally defined enums type names
pub fn set_extern_enums(&mut self, enums: Vec<String>) {
self.extern_enums = enums;
}
}
36 changes: 36 additions & 0 deletions graphql_query_derive/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, syn::E
Err(syn::Error::new_spanned(ast, "Attribute not found"))
}

/// Extract a list of configuration parameter values specified in the `graphql` attribute.
pub fn extract_attr_list(ast: &syn::DeriveInput, attr: &str) -> Result<Vec<String>, syn::Error> {
let attributes = &ast.attrs;
let graphql_path = path_to_match();
let attribute = attributes
.iter()
.find(|attr| attr.path == graphql_path)
.ok_or_else(|| syn::Error::new_spanned(ast, "The graphql attribute is missing"))?;
if let syn::Meta::List(items) = &attribute.parse_meta().expect("Attribute is well formatted") {
for item in items.nested.iter() {
if let syn::NestedMeta::Meta(syn::Meta::List(value_list)) = item {
if let Some(ident) = value_list.path.get_ident() {
if ident == attr {
return value_list
.nested
.iter()
.map(|lit| {
if let syn::NestedMeta::Lit(syn::Lit::Str(lit)) = lit {
Ok(lit.value())
} else {
Err(syn::Error::new_spanned(
lit,
"Attribute inside value list must be a literal",
))
}
})
.collect();
}
}
}
}
}

Err(syn::Error::new_spanned(ast, "Attribute not found"))
}

/// Get the deprecation from a struct attribute in the derive case.
pub fn extract_deprecation_strategy(
ast: &syn::DeriveInput,
Expand Down
6 changes: 6 additions & 0 deletions graphql_query_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fn build_graphql_client_derive_options(
let variables_derives = attributes::extract_attr(input, "variables_derives").ok();
let response_derives = attributes::extract_attr(input, "response_derives").ok();
let custom_scalars_module = attributes::extract_attr(input, "custom_scalars_module").ok();
let extern_enums = attributes::extract_attr_list(input, "extern_enums").ok();

let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Derive);
options.set_query_file(query_path);
Expand Down Expand Up @@ -105,6 +106,11 @@ fn build_graphql_client_derive_options(
options.set_custom_scalars_module(custom_scalars_module);
}

// The user can specify a list of enums types that are defined externally, rather than generated by this library
if let Some(extern_enums) = extern_enums {
options.set_extern_enums(extern_enums);
}

options.set_struct_ident(input.ident.clone());
options.set_module_visibility(input.vis.clone());
options.set_operation_name(input.ident.to_string());
Expand Down