Skip to content

Commit 3e169d6

Browse files
committed
WIP Support query_string macro attribute
1 parent c52e89e commit 3e169d6

File tree

2 files changed

+130
-75
lines changed
  • graphql_client_codegen/src
  • graphql_query_derive/src

2 files changed

+130
-75
lines changed

graphql_client_codegen/src/lib.rs

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use lazy_static::*;
1010
use proc_macro2::TokenStream;
1111
use quote::*;
12+
use schema::Schema;
1213

1314
mod codegen;
1415
mod codegen_options;
@@ -43,83 +44,122 @@ impl Display for GeneralError {
4344
impl std::error::Error for GeneralError {}
4445

4546
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
46-
type CacheMap<T> = std::sync::Mutex<BTreeMap<std::path::PathBuf, T>>;
47+
type CacheMap<K, V> = std::sync::Mutex<BTreeMap<K, V>>;
48+
type QueryDocument = graphql_parser::query::Document<'static, String>;
4749

4850
lazy_static! {
49-
static ref SCHEMA_CACHE: CacheMap<schema::Schema> = CacheMap::default();
50-
static ref QUERY_CACHE: CacheMap<(String, graphql_parser::query::Document<'static, String>)> =
51+
static ref SCHEMA_CACHE: CacheMap<std::path::PathBuf, Schema> = CacheMap::default();
52+
static ref QUERY_CACHE: CacheMap<std::path::PathBuf, (String, QueryDocument)> =
5153
CacheMap::default();
5254
}
5355

54-
/// Generates Rust code given a query document, a schema and options.
55-
pub fn generate_module_token_stream(
56-
query_path: std::path::PathBuf,
57-
schema_path: &std::path::Path,
58-
options: GraphQLClientCodegenOptions,
59-
) -> Result<TokenStream, BoxError> {
56+
fn get_set_cached<K: std::cmp::Ord, V: Clone>(
57+
cache: &CacheMap<K, V>,
58+
key: K,
59+
value: V,
60+
) -> Result<V, BoxError> {
6061
use std::collections::btree_map;
6162

62-
let schema_extension = schema_path
63-
.extension()
64-
.and_then(std::ffi::OsStr::to_str)
65-
.unwrap_or("INVALID");
66-
let schema_string;
67-
68-
// Check the schema cache.
69-
let schema: schema::Schema = {
70-
let mut lock = SCHEMA_CACHE.lock().expect("schema cache is poisoned");
71-
match lock.entry(schema_path.to_path_buf()) {
63+
let cached: V = {
64+
let mut lock = cache.lock().expect("cache is poisoned");
65+
match lock.entry(key) {
7266
btree_map::Entry::Occupied(o) => o.get().clone(),
73-
btree_map::Entry::Vacant(v) => {
74-
schema_string = read_file(v.key())?;
75-
let schema = match schema_extension {
76-
"graphql" | "gql" => {
77-
let s = graphql_parser::schema::parse_schema::<&str>(&schema_string).map_err(|parser_error| GeneralError(format!("Parser error: {}", parser_error)))?;
78-
schema::Schema::from(s)
79-
}
80-
"json" => {
81-
let parsed: graphql_introspection_query::introspection_response::IntrospectionResponse = serde_json::from_str(&schema_string)?;
82-
schema::Schema::from(parsed)
83-
}
84-
extension => return Err(GeneralError(format!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension)).into())
85-
};
86-
87-
v.insert(schema).clone()
88-
}
67+
btree_map::Entry::Vacant(v) => v.insert(value).clone(),
8968
}
9069
};
9170

92-
// We need to qualify the query with the path to the crate it is part of
93-
let (query_string, query) = {
94-
let mut lock = QUERY_CACHE.lock().expect("query cache is poisoned");
95-
match lock.entry(query_path) {
96-
btree_map::Entry::Occupied(o) => o.get().clone(),
97-
btree_map::Entry::Vacant(v) => {
98-
let query_string = read_file(v.key())?;
99-
let query = graphql_parser::parse_query(&query_string)
100-
.map_err(|err| GeneralError(format!("Query parser error: {}", err)))?
101-
.into_static();
102-
v.insert((query_string, query)).clone()
71+
Ok(cached)
72+
}
73+
74+
fn query_document(query_string: &str) -> Result<QueryDocument, BoxError> {
75+
let document = graphql_parser::parse_query(query_string)
76+
.map_err(|err| GeneralError(format!("Query parser error: {}", err)))?
77+
.into_static();
78+
Ok(document)
79+
}
80+
81+
fn get_set_query_from_file(
82+
query_path: &std::path::Path,
83+
) -> Result<(String, QueryDocument), BoxError> {
84+
get_set_cached(&QUERY_CACHE, query_path.to_path_buf(), {
85+
let query_string = read_file(query_path)?;
86+
let query_document = query_document(&query_string)?;
87+
(query_string, query_document)
88+
})
89+
}
90+
91+
fn get_set_schema_from_file(schema_path: &std::path::Path) -> Result<Schema, BoxError> {
92+
get_set_cached(&SCHEMA_CACHE, schema_path.to_path_buf(), {
93+
let schema_extension = schema_path
94+
.extension()
95+
.and_then(std::ffi::OsStr::to_str)
96+
.unwrap_or("INVALID");
97+
let schema_string = read_file(schema_path)?;
98+
match schema_extension {
99+
"graphql" | "gql" => {
100+
let s = graphql_parser::schema::parse_schema::<&str>(&schema_string).map_err(|parser_error| GeneralError(format!("Parser error: {}", parser_error)))?;
101+
Schema::from(s)
102+
}
103+
"json" => {
104+
let parsed: graphql_introspection_query::introspection_response::IntrospectionResponse = serde_json::from_str(&schema_string)?;
105+
Schema::from(parsed)
103106
}
107+
extension => return Err(GeneralError(format!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension)).into())
104108
}
105-
};
109+
})
110+
}
106111

107-
let query = crate::query::resolve(&schema, &query)?;
112+
/// Generates Rust code given a path to a query file, a path to a schema file, and options.
113+
pub fn generate_module_token_stream(
114+
query_path: std::path::PathBuf,
115+
schema_path: &std::path::Path,
116+
options: GraphQLClientCodegenOptions,
117+
) -> Result<TokenStream, BoxError> {
118+
let query = get_set_query_from_file(query_path.as_path())?;
119+
let schema = get_set_schema_from_file(schema_path)?;
120+
121+
generate_module_token_stream_inner(&query, &schema, options)
122+
}
123+
124+
/// Generates Rust code given a query string, a path to a schema file, and options.
125+
pub fn generate_module_token_stream_from_string(
126+
query_string: &str,
127+
schema_path: &std::path::Path,
128+
options: GraphQLClientCodegenOptions,
129+
) -> Result<TokenStream, BoxError> {
130+
let query = (query_string.to_string(), query_document(query_string)?);
131+
let schema = get_set_schema_from_file(schema_path)?;
132+
133+
generate_module_token_stream_inner(&query, &schema, options)
134+
}
135+
136+
/// Generates Rust code given a query string and query document, a schema, and options.
137+
fn generate_module_token_stream_inner(
138+
query: &(String, QueryDocument),
139+
schema: &Schema,
140+
options: GraphQLClientCodegenOptions,
141+
) -> Result<TokenStream, BoxError> {
142+
let (query_string, query_document) = query;
143+
144+
// We need to qualify the query with the path to the crate it is part of
145+
let generated_query = crate::query::resolve(schema, query_document)?;
108146

109147
// Determine which operation we are generating code for. This will be used in operationName.
110148
let operations = options
111149
.operation_name
112150
.as_ref()
113-
.and_then(|operation_name| query.select_operation(operation_name, *options.normalization()))
151+
.and_then(|operation_name| {
152+
generated_query.select_operation(operation_name, *options.normalization())
153+
})
114154
.map(|op| vec![op]);
115155

116156
let operations = match (operations, &options.mode) {
117157
(Some(ops), _) => ops,
118-
(None, &CodegenMode::Cli) => query.operations().collect(),
158+
(None, &CodegenMode::Cli) => generated_query.operations().collect(),
119159
(None, &CodegenMode::Derive) => {
120160
return Err(GeneralError(derive_operation_not_found_error(
121161
options.struct_ident(),
122-
&query,
162+
&generated_query,
123163
))
124164
.into());
125165
}
@@ -131,8 +171,8 @@ pub fn generate_module_token_stream(
131171
for operation in &operations {
132172
let generated = generated_module::GeneratedModule {
133173
query_string: query_string.as_str(),
134-
schema: &schema,
135-
resolved_query: &query,
174+
schema,
175+
resolved_query: &generated_query,
136176
operation: &operation.1.name,
137177
options: &options,
138178
}

graphql_query_derive/src/lib.rs

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ extern crate proc_macro;
44
mod attributes;
55

66
use graphql_client_codegen::{
7-
generate_module_token_stream, CodegenMode, GraphQLClientCodegenOptions,
7+
generate_module_token_stream, generate_module_token_stream_from_string, CodegenMode,
8+
GraphQLClientCodegenOptions,
89
};
910
use std::{
1011
env,
@@ -26,38 +27,48 @@ fn graphql_query_derive_inner(
2627
) -> Result<proc_macro::TokenStream, syn::Error> {
2728
let input = TokenStream::from(input);
2829
let ast = syn::parse2(input)?;
29-
let (query_path, schema_path) = build_query_and_schema_path(&ast)?;
30-
let options = build_graphql_client_derive_options(&ast, query_path.clone())?;
31-
32-
generate_module_token_stream(query_path, &schema_path, options)
33-
.map(Into::into)
34-
.map_err(|err| {
35-
syn::Error::new_spanned(
36-
ast,
37-
format!("Failed to generate GraphQLQuery impl: {}", err),
38-
)
39-
})
40-
}
4130

42-
fn build_query_and_schema_path(input: &syn::DeriveInput) -> Result<(PathBuf, PathBuf), syn::Error> {
4331
let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_err| {
4432
syn::Error::new_spanned(
45-
input,
33+
&ast,
4634
"Error checking that the CARGO_MANIFEST_DIR env variable is defined.",
4735
)
4836
})?;
4937

50-
let query_path = attributes::extract_attr(input, "query_path")?;
51-
let query_path = format!("{}/{}", cargo_manifest_dir, query_path);
52-
let query_path = Path::new(&query_path).to_path_buf();
53-
let schema_path = attributes::extract_attr(input, "schema_path")?;
38+
let schema_path = attributes::extract_attr(&ast, "schema_path")?;
5439
let schema_path = Path::new(&cargo_manifest_dir).join(schema_path);
55-
Ok((query_path, schema_path))
40+
41+
let query_string = attributes::extract_attr(&ast, "query_string").ok();
42+
let module_token_stream = match query_string {
43+
Some(query_string) => {
44+
let options = build_graphql_client_derive_options(&ast, None)?;
45+
generate_module_token_stream_from_string(
46+
query_string.as_str(),
47+
schema_path.as_path(),
48+
options,
49+
)
50+
}
51+
None => {
52+
let query_path = attributes::extract_attr(&ast, "query_path")?;
53+
let query_path = format!("{}/{}", cargo_manifest_dir, query_path);
54+
let query_path = Path::new(&query_path).to_path_buf();
55+
let options = build_graphql_client_derive_options(&ast, Some(query_path.clone()))?;
56+
57+
generate_module_token_stream(query_path, &schema_path, options)
58+
}
59+
};
60+
61+
module_token_stream.map(Into::into).map_err(|err| {
62+
syn::Error::new_spanned(
63+
ast,
64+
format!("Failed to generate GraphQLQuery impl: {}", err),
65+
)
66+
})
5667
}
5768

5869
fn build_graphql_client_derive_options(
5970
input: &syn::DeriveInput,
60-
query_path: PathBuf,
71+
query_path: Option<PathBuf>,
6172
) -> Result<GraphQLClientCodegenOptions, syn::Error> {
6273
let variables_derives = attributes::extract_attr(input, "variables_derives").ok();
6374
let response_derives = attributes::extract_attr(input, "response_derives").ok();
@@ -67,7 +78,11 @@ fn build_graphql_client_derive_options(
6778
let skip_serializing_none: bool = attributes::extract_skip_serializing_none(input);
6879

6980
let mut options = GraphQLClientCodegenOptions::new(CodegenMode::Derive);
70-
options.set_query_file(query_path);
81+
82+
if let Some(query_path) = query_path {
83+
options.set_query_file(query_path);
84+
}
85+
7186
options.set_fragments_other_variant(fragments_other_variant);
7287
options.set_skip_serializing_none(skip_serializing_none);
7388

0 commit comments

Comments
 (0)