Skip to content

Commit 8a36e15

Browse files
committed
Fix wrong operationName when no operation is selected (fixes #149)
For example if we have this: query SelectName { person { name } } query SelectAge { person { age } } And our code looks like this: query_path = "..", schema_path = "...", )] struct SomeQuery; then the operation we generate code for will default to the first one, and operationName should be SelectName.
1 parent c232d91 commit 8a36e15

File tree

7 files changed

+140
-41
lines changed

7 files changed

+140
-41
lines changed

graphql_client_cli/src/generate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub fn generate_code(
2121
};
2222

2323
let options = GraphQLClientDeriveOptions {
24-
selected_operation,
24+
struct_name: selected_operation,
2525
additional_derives,
2626
deprecation_strategy,
2727
};

graphql_client_codegen/src/codegen.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,31 @@ use query::QueryContext;
88
use schema;
99
use selection::Selection;
1010

11+
/// Selects the first operation matching `struct_name` or the first one. Returns `None` when the query document defines no operation.
12+
pub(crate) fn select_operation(query: &query::Document, struct_name: &str) -> Option<Operation> {
13+
let mut operations: Vec<Operation> = Vec::new();
14+
15+
for definition in query.definitions.iter() {
16+
match definition {
17+
query::Definition::Operation(op) => {
18+
operations.push(op.into());
19+
}
20+
_ => (),
21+
}
22+
}
23+
24+
operations
25+
.iter()
26+
.find(|op| op.name == struct_name)
27+
.map(|i| i.to_owned())
28+
.or_else(|| operations.iter().next().map(|i| i.to_owned()))
29+
}
30+
31+
/// The main code generation function.
1132
pub fn response_for_query(
1233
schema: schema::Schema,
1334
query: query::Document,
14-
selected_operation: String,
35+
operation: &Operation,
1536
additional_derives: Option<String>,
1637
deprecation_strategy: deprecation::DeprecationStrategy,
1738
) -> Result<TokenStream, failure::Error> {
@@ -22,13 +43,10 @@ pub fn response_for_query(
2243
}
2344

2445
let mut definitions = Vec::new();
25-
let mut operations: Vec<Operation> = Vec::new();
2646

2747
for definition in query.definitions {
2848
match definition {
29-
query::Definition::Operation(op) => {
30-
operations.push(op.into());
31-
}
49+
query::Definition::Operation(_op) => (),
3250
query::Definition::Fragment(fragment) => {
3351
let query::TypeCondition::On(on) = fragment.type_condition;
3452
context.fragments.insert(
@@ -44,21 +62,6 @@ pub fn response_for_query(
4462
}
4563
}
4664

47-
context.selected_operation = operations
48-
.iter()
49-
.find(|op| op.name == selected_operation)
50-
.map(|i| i.to_owned());
51-
52-
let opt_operation = context
53-
.selected_operation
54-
.clone()
55-
.or_else(|| operations.iter().next().map(|i| i.to_owned()));
56-
let operation = if let Some(operation) = opt_operation {
57-
operation
58-
} else {
59-
panic!("no operation '{}' in query document", selected_operation);
60-
};
61-
6265
let response_data_fields = {
6366
let opt_root_name = operation.root_name(&context.schema);
6467
let root_name: String = if let Some(root_name) = opt_root_name {

graphql_client_codegen/src/lib.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ lazy_static! {
5656
}
5757

5858
pub struct GraphQLClientDeriveOptions {
59-
pub selected_operation: String,
59+
pub struct_name: String,
6060
pub additional_derives: Option<String>,
6161
pub deprecation_strategy: Option<deprecation::DeprecationStrategy>,
6262
}
@@ -93,6 +93,16 @@ pub fn generate_module_token_stream(
9393
}
9494
};
9595

96+
// Determine which operation we are generating code for. This will be used in operationName.
97+
98+
let operation = if let Some(op) = codegen::select_operation(&query, &options.struct_name) {
99+
op
100+
} else {
101+
panic!("Query document defines no operation.")
102+
};
103+
104+
let operation_name_literal = &operation.name;
105+
96106
// Check the schema cache.
97107
let schema = {
98108
let mut lock = SCHEMA_CACHE.lock().expect("schema cache is poisoned");
@@ -125,16 +135,15 @@ pub fn generate_module_token_stream(
125135
}
126136
};
127137

128-
let operation_string = options.selected_operation.clone();
129138
let module_name = Ident::new(
130-
options.selected_operation.to_snake_case().as_str(),
139+
options.struct_name.to_snake_case().as_str(),
131140
Span::call_site(),
132141
);
133-
let struct_name = Ident::new(options.selected_operation.as_str(), Span::call_site());
142+
let struct_name = Ident::new(options.struct_name.as_str(), Span::call_site());
134143
let schema_output = codegen::response_for_query(
135144
schema,
136145
query,
137-
options.selected_operation.clone(),
146+
&operation,
138147
response_derives,
139148
deprecation_strategy,
140149
)?;
@@ -148,7 +157,7 @@ pub fn generate_module_token_stream(
148157
use serde;
149158

150159
pub const QUERY: &'static str = #query_string;
151-
pub const OPERATION_NAME: &'static str = #operation_string;
160+
pub const OPERATION_NAME: &'static str = #operation_name_literal;
152161

153162
#schema_output
154163
}

graphql_client_codegen/src/operations.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ use syn::Ident;
88
use variables::Variable;
99

1010
#[derive(Debug, Clone)]
11-
pub(crate) enum OperationType {
11+
pub enum OperationType {
1212
Query,
1313
Mutation,
1414
Subscription,
1515
}
1616

1717
#[derive(Debug, Clone)]
18-
pub(crate) struct Operation {
18+
pub struct Operation {
1919
pub name: String,
2020
pub operation_type: OperationType,
2121
pub variables: Vec<Variable>,
@@ -113,3 +113,41 @@ impl ::std::convert::From<OperationDefinition> for Operation {
113113
}
114114
}
115115
}
116+
117+
impl<'a> ::std::convert::From<&'a OperationDefinition> for Operation {
118+
fn from(definition: &OperationDefinition) -> Operation {
119+
match *definition {
120+
OperationDefinition::Query(ref q) => Operation {
121+
name: q.name.clone().expect("unnamed operation"),
122+
operation_type: OperationType::Query,
123+
variables: q
124+
.variable_definitions
125+
.iter()
126+
.map(|v| v.clone().into())
127+
.collect(),
128+
selection: (&q.selection_set).into(),
129+
},
130+
OperationDefinition::Mutation(ref m) => Operation {
131+
name: m.name.clone().expect("unnamed operation"),
132+
operation_type: OperationType::Mutation,
133+
variables: m
134+
.variable_definitions
135+
.iter()
136+
.map(|v| v.clone().into())
137+
.collect(),
138+
selection: (&m.selection_set).into(),
139+
},
140+
OperationDefinition::Subscription(ref s) => Operation {
141+
name: s.name.clone().expect("unnamed operation"),
142+
operation_type: OperationType::Subscription,
143+
variables: s
144+
.variable_definitions
145+
.iter()
146+
.map(|v| v.clone().into())
147+
.collect(),
148+
selection: (&s.selection_set).into(),
149+
},
150+
OperationDefinition::SelectionSet(_) => panic!(SELECTION_SET_AT_ROOT),
151+
}
152+
}
153+
}

graphql_client_codegen/src/query.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use deprecation::DeprecationStrategy;
22
use failure;
33
use fragments::GqlFragment;
44
use itertools::Itertools;
5-
use operations::Operation;
65
use proc_macro2::Span;
76
use proc_macro2::TokenStream;
87
use schema::Schema;
@@ -14,7 +13,6 @@ use syn::Ident;
1413
pub(crate) struct QueryContext {
1514
pub fragments: BTreeMap<String, GqlFragment>,
1615
pub schema: Schema,
17-
pub selected_operation: Option<Operation>,
1816
pub deprecation_strategy: DeprecationStrategy,
1917
variables_derives: Vec<Ident>,
2018
response_derives: Vec<Ident>,
@@ -26,7 +24,6 @@ impl QueryContext {
2624
QueryContext {
2725
fragments: BTreeMap::new(),
2826
schema,
29-
selected_operation: None,
3027
deprecation_strategy,
3128
variables_derives: vec![Ident::new("Serialize", Span::call_site())],
3229
response_derives: vec![Ident::new("Deserialize", Span::call_site())],
@@ -45,7 +42,6 @@ impl QueryContext {
4542
QueryContext {
4643
fragments: BTreeMap::new(),
4744
schema: Schema::new(),
48-
selected_operation: None,
4945
deprecation_strategy: DeprecationStrategy::Allow,
5046
variables_derives: vec![Ident::new("Serialize", Span::call_site())],
5147
response_derives: vec![Ident::new("Deserialize", Span::call_site())],

graphql_query_derive/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn build_graphql_client_derive_options(input: &syn::DeriveInput) -> GraphQLClien
3737
.unwrap_or(deprecation::DeprecationStrategy::default());
3838

3939
GraphQLClientDeriveOptions {
40-
selected_operation: input.ident.to_string(),
40+
struct_name: input.ident.to_string(),
4141
additional_derives: response_derives,
4242
deprecation_strategy: Some(deprecation_strategy),
4343
}

tests/operation_selection.rs

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,42 @@ extern crate serde_derive;
55
extern crate serde;
66
extern crate serde_json;
77

8+
use graphql_client::GraphQLQuery;
9+
810
#[derive(GraphQLQuery)]
911
#[graphql(
1012
query_path = "tests/operation_selection/queries.graphql",
1113
schema_path = "tests/operation_selection/schema.graphql",
12-
response_derives = "Debug",
14+
response_derives = "Debug,PartialEq",
1315
)]
1416
pub struct Heights;
1517

1618
#[derive(GraphQLQuery)]
1719
#[graphql(
1820
query_path = "tests/operation_selection/queries.graphql",
1921
schema_path = "tests/operation_selection/schema.graphql",
20-
response_derives = "Debug",
22+
response_derives = "Debug,PartialEq",
2123
)]
2224
pub struct Echo;
2325

26+
// The default is the first operation so this should be the same as Heights
27+
#[derive(GraphQLQuery)]
28+
#[graphql(
29+
query_path = "tests/operation_selection/queries.graphql",
30+
schema_path = "tests/operation_selection/schema.graphql",
31+
response_derives = "Debug,PartialEq",
32+
)]
33+
pub struct Unrelated;
34+
2435
const HEIGHTS_RESPONSE: &'static str = r##"{"mountainHeight": 224, "buildingHeight": 12}"##;
2536
const ECHO_RESPONSE: &'static str = r##"{"echo": "tiramisù"}"##;
2637

2738
#[test]
2839
fn operation_selection_works() {
2940
let heights_response_data: heights::ResponseData =
3041
serde_json::from_str(HEIGHTS_RESPONSE).unwrap();
42+
let heights_unrelated_response_data: unrelated::ResponseData =
43+
serde_json::from_str(HEIGHTS_RESPONSE).unwrap();
3144
let echo_response_data: echo::ResponseData = serde_json::from_str(ECHO_RESPONSE).unwrap();
3245

3346
let _echo_variables = echo::Variables {
@@ -38,11 +51,51 @@ fn operation_selection_works() {
3851
building_id: "12".to_string(),
3952
mountain_name: Some("canigou".to_string()),
4053
};
54+
let _unrelated_variables = unrelated::Variables {
55+
building_id: "12".to_string(),
56+
mountain_name: Some("canigou".to_string()),
57+
};
58+
59+
let expected_echo = echo::ResponseData {
60+
echo: Some("tiramisù".to_string()),
61+
};
62+
let expected_heights = heights::ResponseData {
63+
mountain_height: Some(224),
64+
building_height: Some(12),
65+
};
4166

42-
let expected_echo = r##"ResponseData { echo: Some("tiramisù") }"##;
43-
let expected_heights =
44-
r##"ResponseData { mountain_height: Some(224), building_height: Some(12) }"##;
67+
let expected_heights_unrelated = unrelated::ResponseData {
68+
mountain_height: Some(224),
69+
building_height: Some(12),
70+
};
71+
72+
assert_eq!(expected_echo, echo_response_data);
73+
assert_eq!(expected_heights, heights_response_data);
74+
assert_eq!(expected_heights_unrelated, heights_unrelated_response_data);
75+
}
76+
77+
#[test]
78+
fn operation_name_is_correct() {
79+
let echo_variables = echo::Variables {
80+
msg: Some("hi".to_string()),
81+
};
82+
83+
let height_variables = heights::Variables {
84+
building_id: "12".to_string(),
85+
mountain_name: Some("canigou".to_string()),
86+
};
87+
let unrelated_variables = unrelated::Variables {
88+
building_id: "12".to_string(),
89+
mountain_name: Some("canigou".to_string()),
90+
};
4591

46-
assert_eq!(expected_echo, format!("{:?}", echo_response_data));
47-
assert_eq!(expected_heights, format!("{:?}", heights_response_data));
92+
assert_eq!(Echo::build_query(echo_variables).operation_name, "Echo");
93+
assert_eq!(
94+
Heights::build_query(height_variables).operation_name,
95+
"Heights"
96+
);
97+
assert_eq!(
98+
Unrelated::build_query(unrelated_variables).operation_name,
99+
"Heights"
100+
);
48101
}

0 commit comments

Comments
 (0)