Skip to content

Commit b688201

Browse files
committed
graphql-client-codegen can generate all queries token
1 parent 6bf0802 commit b688201

File tree

4 files changed

+187
-41
lines changed

4 files changed

+187
-41
lines changed

graphql_client_codegen/src/codegen.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,31 @@ use failure;
33
use fragments::GqlFragment;
44
use graphql_parser::query;
55
use operations::Operation;
6-
use proc_macro2::TokenStream;
6+
use proc_macro2::{Ident, Span, TokenStream};
77
use query::QueryContext;
88
use schema;
99
use selection::Selection;
1010

1111
/// Selects the first operation matching `struct_name` or the first one. Returns `None` when the query document defines no operation.
1212
pub(crate) fn select_operation(query: &query::Document, struct_name: &str) -> Option<Operation> {
13+
let operations = all_operations(query);
14+
15+
operations
16+
.iter()
17+
.find(|op| op.name == struct_name)
18+
.map(|i| i.to_owned())
19+
.or_else(|| operations.iter().next().map(|i| i.to_owned()))
20+
}
21+
22+
pub(crate) fn all_operations(query: &query::Document) -> Vec<Operation> {
1323
let mut operations: Vec<Operation> = Vec::new();
1424

1525
for definition in &query.definitions {
1626
if let query::Definition::Operation(op) = definition {
1727
operations.push(op.into());
1828
}
1929
}
20-
2130
operations
22-
.iter()
23-
.find(|op| op.name == struct_name)
24-
.map(|i| i.to_owned())
25-
.or_else(|| operations.iter().next().map(|i| i.to_owned()))
2631
}
2732

2833
/// The main code generation function.
@@ -32,6 +37,7 @@ pub fn response_for_query(
3237
operation: &Operation,
3338
additional_derives: Option<String>,
3439
deprecation_strategy: deprecation::DeprecationStrategy,
40+
multiple_operation: bool,
3541
) -> Result<TokenStream, failure::Error> {
3642
let mut context = QueryContext::new(schema, deprecation_strategy);
3743

@@ -112,7 +118,8 @@ pub fn response_for_query(
112118
}
113119
}).collect();
114120
let fragment_definitions = fragment_definitions?;
115-
let variables_struct = operation.expand_variables(&context);
121+
let variables_struct =
122+
operation.expand_variables(&context, &operation.name, multiple_operation);
116123

117124
let input_object_definitions: Result<Vec<TokenStream>, _> = context
118125
.schema
@@ -141,7 +148,18 @@ pub fn response_for_query(
141148

142149
let response_derives = context.response_derives();
143150

151+
let respons_data_struct_name = if multiple_operation {
152+
Ident::new(
153+
format!("{}ResponseData", operation.name).as_str(),
154+
Span::call_site(),
155+
)
156+
} else {
157+
Ident::new("ResponseData", Span::call_site())
158+
};
159+
144160
Ok(quote! {
161+
use serde_derive::*;
162+
145163
#[allow(dead_code)]
146164
type Boolean = bool;
147165
#[allow(dead_code)]
@@ -164,7 +182,7 @@ pub fn response_for_query(
164182
#variables_struct
165183

166184
#response_derives
167-
pub struct ResponseData {
185+
pub struct #respons_data_struct_name {
168186
#(#response_data_fields,)*
169187
}
170188

graphql_client_codegen/src/deprecation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub enum DeprecationStatus {
1414
}
1515

1616
/// The available deprecation startegies.
17-
#[derive(Debug, PartialEq)]
17+
#[derive(Debug, PartialEq, Clone)]
1818
pub enum DeprecationStrategy {
1919
/// Allow use of deprecated items in queries, and say nothing.
2020
Allow,

graphql_client_codegen/src/lib.rs

Lines changed: 143 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,14 @@ lazy_static! {
6464
}
6565

6666
/// Used to configure code generation.
67+
#[derive(Clone)]
6768
pub struct GraphQLClientDeriveOptions {
68-
/// Name of the operation we want to generate code for. If it does not match, we default to the first one.
69-
pub struct_name: String,
69+
/// Name of the operation we want to generate code for. If it does not match, we use all queries.
70+
pub operation_name: Option<String>,
71+
/// The name of implemention target struct.
72+
pub struct_name: Option<String>,
73+
/// The module that contains queries.
74+
pub module_name: Option<String>,
7075
/// Comma-separated list of additional traits we want to derive.
7176
pub additional_derives: Option<String>,
7277
/// The deprecation strategy to adopt.
@@ -81,10 +86,10 @@ pub fn generate_module_token_stream(
8186
) -> Result<TokenStream, failure::Error> {
8287
let options = options.unwrap();
8388

84-
let response_derives = options.additional_derives;
89+
let response_derives = options.additional_derives.clone();
8590

8691
// The user can determine what to do about deprecations.
87-
let deprecation_strategy = options.deprecation_strategy.unwrap_or_default();
92+
let deprecation_strategy = options.deprecation_strategy.clone().unwrap_or_default();
8893

8994
// We need to qualify the query with the path to the crate it is part of
9095
let (query_string, query) = {
@@ -100,15 +105,17 @@ pub fn generate_module_token_stream(
100105
};
101106

102107
// Determine which operation we are generating code for. This will be used in operationName.
103-
104-
let operation = if let Some(op) = codegen::select_operation(&query, &options.struct_name) {
105-
op
108+
let operations = if options.operation_name.is_some() {
109+
let op = codegen::select_operation(&query, &(options.operation_name.clone().unwrap()));
110+
if op.is_some() {
111+
vec![op.unwrap()]
112+
} else {
113+
codegen::all_operations(&query)
114+
}
106115
} else {
107-
panic!("Query document defines no operation.")
116+
codegen::all_operations(&query)
108117
};
109118

110-
let operation_name_literal = &operation.name;
111-
112119
// Check the schema cache.
113120
let schema = {
114121
let mut lock = SCHEMA_CACHE.lock().expect("schema cache is poisoned");
@@ -141,20 +148,91 @@ pub fn generate_module_token_stream(
141148
}
142149
};
143150

151+
let struct_name = if options.struct_name.is_some() {
152+
Some(Ident::new(
153+
options.struct_name.clone().unwrap().as_str(),
154+
Span::call_site(),
155+
))
156+
} else {
157+
None
158+
};
159+
144160
let module_name = Ident::new(
145-
options.struct_name.to_snake_case().as_str(),
161+
options
162+
.module_name
163+
.clone()
164+
.unwrap_or_else(|| options.operation_name.clone().unwrap())
165+
.to_snake_case()
166+
.as_str(),
146167
Span::call_site(),
147168
);
148-
let struct_name = Ident::new(options.struct_name.as_str(), Span::call_site());
149-
let schema_output = codegen::response_for_query(
150-
schema,
151-
query,
152-
&operation,
153-
response_derives,
154-
deprecation_strategy,
155-
)?;
156-
157-
let result = quote!(
169+
170+
let operation_count = operations.len();
171+
172+
let mulutiple_operation = operation_count > 1;
173+
174+
let mut schema_and_operations = Vec::with_capacity(operation_count);
175+
176+
for operation in &operations {
177+
let schema_output = codegen::response_for_query(
178+
schema.clone(),
179+
query.clone(),
180+
&operation,
181+
response_derives.clone(),
182+
deprecation_strategy.clone(),
183+
mulutiple_operation,
184+
)?;
185+
let operation_name = Ident::new(operation.name.as_str(), Span::call_site());
186+
schema_and_operations.push((schema_output, operation_name, operation.name.as_str()));
187+
}
188+
189+
let result = build_module_token_stream(
190+
&module_name,
191+
&struct_name,
192+
&query_string,
193+
schema_and_operations,
194+
);
195+
196+
Ok(result)
197+
}
198+
199+
fn build_module_token_stream(
200+
module_name: &Ident,
201+
struct_name: &Option<Ident>,
202+
query_string: &str,
203+
schema_and_operations: Vec<(TokenStream, Ident, &str)>,
204+
) -> TokenStream {
205+
let mut schema_token_streams = vec![];
206+
let mut trait_token_streams = vec![];
207+
let mulutiple_operation = schema_and_operations.len() > 1;
208+
for (schema_output, operation_name, operation_name_literal) in schema_and_operations {
209+
let (schema_token_stream, trait_token_stream) = build_query_struct_token_stream(
210+
&module_name,
211+
struct_name.clone(),
212+
&schema_output,
213+
&operation_name,
214+
operation_name_literal,
215+
mulutiple_operation,
216+
);
217+
schema_token_streams.push(schema_token_stream);
218+
trait_token_streams.push(trait_token_stream);
219+
}
220+
221+
merge_with_common_token_stream(
222+
&module_name,
223+
query_string,
224+
schema_token_streams,
225+
trait_token_streams,
226+
)
227+
}
228+
229+
fn merge_with_common_token_stream(
230+
module_name: &Ident,
231+
query_string: &str,
232+
schema_token_streams: Vec<TokenStream>,
233+
trait_token_streams: Vec<TokenStream>,
234+
) -> TokenStream {
235+
quote!(
158236
pub mod #module_name {
159237
#![allow(non_camel_case_types)]
160238
#![allow(non_snake_case)]
@@ -163,14 +241,52 @@ pub fn generate_module_token_stream(
163241
use serde;
164242

165243
pub const QUERY: &'static str = #query_string;
166-
pub const OPERATION_NAME: &'static str = #operation_name_literal;
167-
168-
#schema_output
244+
#(#schema_token_streams)*
169245
}
246+
#(#trait_token_streams)*
247+
)
248+
}
170249

250+
fn build_query_struct_token_stream(
251+
module_name: &Ident,
252+
struct_name: Option<Ident>,
253+
schema_output: &TokenStream,
254+
operation_name: &Ident,
255+
operation_name_literal: &str,
256+
mulutiple_operation: bool,
257+
) -> (TokenStream, TokenStream) {
258+
let struct_name = if struct_name.is_some() {
259+
struct_name.unwrap()
260+
} else {
261+
operation_name.clone()
262+
};
263+
264+
let (respons_data_struct_name, variables_struct_name) = if mulutiple_operation {
265+
(
266+
Ident::new(
267+
format!("{}ResponseData", operation_name_literal).as_str(),
268+
Span::call_site(),
269+
),
270+
Ident::new(
271+
format!("{}Variables", operation_name).as_str(),
272+
Span::call_site(),
273+
),
274+
)
275+
} else {
276+
(
277+
Ident::new("ResponseData", Span::call_site()),
278+
Ident::new("Variables", Span::call_site()),
279+
)
280+
};
281+
282+
let schema_token = quote!(
283+
pub const OPERATION_NAME: &'static str = #operation_name_literal;
284+
#schema_output
285+
);
286+
let trait_token = quote!(
171287
impl ::graphql_client::GraphQLQuery for #struct_name {
172-
type Variables = #module_name::Variables;
173-
type ResponseData = #module_name::ResponseData;
288+
type Variables = #module_name::#variables_struct_name;
289+
type ResponseData = #module_name::#respons_data_struct_name;
174290

175291
fn build_query(variables: Self::Variables) -> ::graphql_client::QueryBody<Self::Variables> {
176292
::graphql_client::QueryBody {
@@ -182,8 +298,7 @@ pub fn generate_module_token_stream(
182298
}
183299
}
184300
);
185-
186-
Ok(result)
301+
(schema_token, trait_token)
187302
}
188303

189304
fn read_file(path: &::std::path::Path) -> Result<String, failure::Error> {

graphql_client_codegen/src/operations.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,27 @@ impl Operation {
3939
}
4040

4141
/// Generate the Variables struct and all the necessary supporting code.
42-
pub(crate) fn expand_variables(&self, context: &QueryContext) -> TokenStream {
42+
pub(crate) fn expand_variables(
43+
&self,
44+
context: &QueryContext,
45+
operation_name: &str,
46+
mulutiple_operation: bool,
47+
) -> TokenStream {
4348
let variables = &self.variables;
49+
let variables_struct_name = if mulutiple_operation {
50+
Ident::new(
51+
format!("{}Variables", operation_name).as_str(),
52+
Span::call_site(),
53+
)
54+
} else {
55+
Ident::new("Variables", Span::call_site())
56+
};
4457

4558
let variables_derives = context.variables_derives();
4659

4760
if variables.is_empty() {
4861
return quote!(#variables_derives
49-
pub struct Variables;);
62+
pub struct #variables_struct_name;);
5063
}
5164

5265
let fields = variables.iter().map(|variable| {
@@ -65,11 +78,11 @@ impl Operation {
6578

6679
quote! {
6780
#variables_derives
68-
pub struct Variables {
81+
pub struct #variables_struct_name {
6982
#(#fields,)*
7083
}
7184

72-
impl Variables {
85+
impl #variables_struct_name {
7386
#(#default_constructors)*
7487
}
7588
}

0 commit comments

Comments
 (0)