Skip to content

Commit ef53897

Browse files
authored
Support codegen from query string (#470)
* Support query_string macro attribute Add test for query string attribute Handle non-utf8 paths and files without extensions Co-authored-by: Surma <surma@surma.dev> Update crate doc comment Use options: GraphQLClientCodegenOptions * Prettier fixes
1 parent a7283cb commit ef53897

File tree

4 files changed

+176
-186
lines changed

4 files changed

+176
-186
lines changed

examples/github/examples/schema.graphql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2410,7 +2410,7 @@ type IssueTimelineConnection {
24102410

24112411
"An item in an issue timeline"
24122412
union IssueTimelineItem =
2413-
AssignedEvent
2413+
| AssignedEvent
24142414
| ClosedEvent
24152415
| Commit
24162416
| CrossReferencedEvent
@@ -5009,7 +5009,7 @@ type PullRequestTimelineConnection {
50095009

50105010
"An item in an pull request timeline"
50115011
union PullRequestTimelineItem =
5012-
AssignedEvent
5012+
| AssignedEvent
50135013
| BaseRefForcePushedEvent
50145014
| ClosedEvent
50155015
| Commit
@@ -6940,7 +6940,7 @@ type ReviewRequestedEvent implements Node {
69406940

69416941
"The results of a search."
69426942
union SearchResultItem =
6943-
Issue
6943+
| Issue
69446944
| MarketplaceListing
69456945
| Organization
69466946
| PullRequest

graphql_client_codegen/src/lib.rs

Lines changed: 78 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22
#![warn(rust_2018_idioms)]
33
#![allow(clippy::option_option)]
44

5-
//! Crate for internal use by other graphql-client crates, for code generation.
6-
//!
7-
//! It is not meant to be used directly by users of the library.
5+
//! Crate for Rust code generation from a GraphQL query, schema, and options.
86
97
use lazy_static::*;
108
use proc_macro2::TokenStream;
119
use quote::*;
10+
use schema::Schema;
1211

1312
mod codegen;
1413
mod codegen_options;
@@ -44,67 +43,92 @@ impl std::error::Error for GeneralError {}
4443

4544
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
4645
type CacheMap<T> = std::sync::Mutex<BTreeMap<std::path::PathBuf, T>>;
46+
type QueryDocument = graphql_parser::query::Document<'static, String>;
4747

4848
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-
CacheMap::default();
49+
static ref SCHEMA_CACHE: CacheMap<Schema> = CacheMap::default();
50+
static ref QUERY_CACHE: CacheMap<(String, QueryDocument)> = CacheMap::default();
5251
}
5352

54-
/// Generates Rust code given a query document, a schema and options.
53+
fn get_set_cached<T: Clone>(
54+
cache: &CacheMap<T>,
55+
key: &std::path::Path,
56+
value_func: impl FnOnce() -> T,
57+
) -> T {
58+
let mut lock = cache.lock().expect("cache is poisoned");
59+
lock.entry(key.into()).or_insert_with(value_func).clone()
60+
}
61+
62+
fn query_document(query_string: &str) -> Result<QueryDocument, BoxError> {
63+
let document = graphql_parser::parse_query(query_string)
64+
.map_err(|err| GeneralError(format!("Query parser error: {}", err)))?
65+
.into_static();
66+
Ok(document)
67+
}
68+
69+
fn get_set_query_from_file(query_path: &std::path::Path) -> (String, QueryDocument) {
70+
get_set_cached(&QUERY_CACHE, query_path, || {
71+
let query_string = read_file(query_path).unwrap();
72+
let query_document = query_document(&query_string).unwrap();
73+
(query_string, query_document)
74+
})
75+
}
76+
77+
fn get_set_schema_from_file(schema_path: &std::path::Path) -> Schema {
78+
get_set_cached(&SCHEMA_CACHE, schema_path, || {
79+
let schema_extension = schema_path
80+
.extension()
81+
.map(|ext| ext.to_str().expect("Path must be valid UTF-8"))
82+
.unwrap_or("<no extension>");
83+
let schema_string = read_file(schema_path).unwrap();
84+
match schema_extension {
85+
"graphql" | "gql" => {
86+
let s = graphql_parser::schema::parse_schema::<&str>(&schema_string).map_err(|parser_error| GeneralError(format!("Parser error: {}", parser_error))).unwrap();
87+
Schema::from(s)
88+
}
89+
"json" => {
90+
let parsed: graphql_introspection_query::introspection_response::IntrospectionResponse = serde_json::from_str(&schema_string).unwrap();
91+
Schema::from(parsed)
92+
}
93+
extension => panic!("Unsupported extension for the GraphQL schema: {} (only .json and .graphql are supported)", extension)
94+
}
95+
})
96+
}
97+
98+
/// Generates Rust code given a path to a query file, a path to a schema file, and options.
5599
pub fn generate_module_token_stream(
56100
query_path: std::path::PathBuf,
57101
schema_path: &std::path::Path,
58102
options: GraphQLClientCodegenOptions,
59103
) -> Result<TokenStream, BoxError> {
60-
use std::collections::btree_map;
61-
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()) {
72-
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-
}
89-
}
90-
};
104+
let query = get_set_query_from_file(query_path.as_path());
105+
let schema = get_set_schema_from_file(schema_path);
91106

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()
103-
}
104-
}
105-
};
107+
generate_module_token_stream_inner(&query, &schema, options)
108+
}
109+
110+
/// Generates Rust code given a query string, a path to a schema file, and options.
111+
pub fn generate_module_token_stream_from_string(
112+
query_string: &str,
113+
schema_path: &std::path::Path,
114+
options: GraphQLClientCodegenOptions,
115+
) -> Result<TokenStream, BoxError> {
116+
let query = (query_string.to_string(), query_document(query_string)?);
117+
let schema = get_set_schema_from_file(schema_path);
118+
119+
generate_module_token_stream_inner(&query, &schema, options)
120+
}
106121

107-
let query = crate::query::resolve(&schema, &query)?;
122+
/// Generates Rust code given a query string and query document, a schema, and options.
123+
fn generate_module_token_stream_inner(
124+
query: &(String, QueryDocument),
125+
schema: &Schema,
126+
options: GraphQLClientCodegenOptions,
127+
) -> Result<TokenStream, BoxError> {
128+
let (query_string, query_document) = query;
129+
130+
// We need to qualify the query with the path to the crate it is part of
131+
let query = crate::query::resolve(schema, query_document)?;
108132

109133
// Determine which operation we are generating code for. This will be used in operationName.
110134
let operations = options
@@ -131,7 +155,7 @@ pub fn generate_module_token_stream(
131155
for operation in &operations {
132156
let generated = generated_module::GeneratedModule {
133157
query_string: query_string.as_str(),
134-
schema: &schema,
158+
schema,
135159
resolved_query: &query,
136160
operation: &operation.1.name,
137161
options: &options,

graphql_client_codegen/src/schema/tests/github_schema.graphql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2409,7 +2409,7 @@ type IssueTimelineConnection {
24092409

24102410
"An item in an issue timeline"
24112411
union IssueTimelineItem =
2412-
AssignedEvent
2412+
| AssignedEvent
24132413
| ClosedEvent
24142414
| Commit
24152415
| CrossReferencedEvent
@@ -5008,7 +5008,7 @@ type PullRequestTimelineConnection {
50085008

50095009
"An item in an pull request timeline"
50105010
union PullRequestTimelineItem =
5011-
AssignedEvent
5011+
| AssignedEvent
50125012
| BaseRefForcePushedEvent
50135013
| ClosedEvent
50145014
| Commit
@@ -6939,7 +6939,7 @@ type ReviewRequestedEvent implements Node {
69396939

69406940
"The results of a search."
69416941
union SearchResultItem =
6942-
Issue
6942+
| Issue
69436943
| MarketplaceListing
69446944
| Organization
69456945
| PullRequest

0 commit comments

Comments
 (0)