Skip to content

Commit 09e32d0

Browse files
authored
Merge pull request #153 from tomhoule/fragments-in-interfaces
Support fragments in interfaces
2 parents a6c9af4 + a05f0b5 commit 09e32d0

16 files changed

+235
-62
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
root = true
22

3-
[*]
3+
[*.rs]
44
end_of_line = lf
55
insert_final_newline = true
66
charset = utf-8

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/target
1+
target/
22
node_modules/
33
**/*.rs.bk
44
Cargo.lock

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
5252

5353
- `GraphQLError` now implements the `Display` trait.
5454

55+
- Basic support for fragments on interfaces. See #154 for what is not supported yet.
56+
5557
### Fixed
5658

5759
- Handle all Rust keywords as field names in codegen by appending `_` to the generated names, so a field called `type` in a GraphQL query will become a `type_` field in the generated struct. Thanks to @scrogson!

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ categories = ["network-programming", "web-programming", "wasm"]
1212
failure = "0.1"
1313
graphql_query_derive = {path = "./graphql_query_derive", version = "0.4.0"}
1414
itertools = "0.7"
15-
serde = "1.0"
15+
serde = "^1.0.78"
1616
serde_derive = "1.0"
1717
serde_json = "1.0"
1818

examples/call_from_js/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![feature(wasm_custom_section, wasm_import_module, use_extern_macros)]
1+
#![feature(use_extern_macros)]
22

33
#[macro_use]
44
extern crate graphql_client;

graphql_client_codegen/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ lazy_static = "1.0"
1313
quote = "0.6"
1414
syn = "0.15"
1515
proc-macro2 = { version = "0.4", features = [] }
16-
serde = "1.0"
16+
serde = "^1.0.78"
1717
serde_derive = "1.0"
1818
serde_json = "1.0"
1919
heck = "0.3"

graphql_client_codegen/src/attributes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use failure;
22
use syn;
33

4+
/// Extract an configuration parameter specified in the `graphql` attribute.
45
pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, failure::Error> {
56
let attributes = &ast.attrs;
67
let attribute = attributes

graphql_client_codegen/src/deprecation.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,23 @@ use syn;
44

55
static DEPRECATION_ERROR: &'static str = "deprecated must be one of 'allow', 'deny', or 'warn'";
66

7+
/// Whether an item is deprecated, with context.
78
#[derive(Debug, PartialEq, Hash, Clone)]
89
pub enum DeprecationStatus {
10+
/// Not deprecated
911
Current,
12+
/// Deprecated
1013
Deprecated(Option<String>),
1114
}
1215

16+
/// The available deprecation startegies.
1317
#[derive(Debug, PartialEq)]
1418
pub enum DeprecationStrategy {
19+
/// Allow use of deprecated items in queries, and say nothing.
1520
Allow,
21+
/// Fail compilation if a deprecated item is used.
1622
Deny,
23+
/// Allow use of deprecated items in queries, but warn about them (default).
1724
Warn,
1825
}
1926

@@ -23,6 +30,7 @@ impl Default for DeprecationStrategy {
2330
}
2431
}
2532

33+
/// Get the deprecation from a struct attribute in the derive case.
2634
pub fn extract_deprecation_strategy(
2735
ast: &syn::DeriveInput,
2836
) -> Result<DeprecationStrategy, failure::Error> {

graphql_client_codegen/src/fragments.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,43 @@ use query::QueryContext;
33
use selection::Selection;
44
use std::cell::Cell;
55

6+
/// Represents a fragment extracted from a query document.
67
#[derive(Debug, PartialEq)]
78
pub struct GqlFragment {
9+
/// The name of the fragment, matching one-to-one with the name in the GraphQL query document.
810
pub name: String,
11+
/// The `on` clause of the fragment.
912
pub on: String,
13+
/// The selected fields.
1014
pub selection: Selection,
15+
/// Whether the fragment
1116
pub is_required: Cell<bool>,
1217
}
1318

1419
impl GqlFragment {
20+
/// Generate all the Rust code required by the fragment's selection.
1521
pub(crate) fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
1622
let derives = context.response_derives();
1723
let name_ident = Ident::new(&self.name, Span::call_site());
1824
let opt_object = context.schema.objects.get(&self.on);
19-
let object = if let Some(object) = opt_object {
20-
object
25+
let (field_impls, fields) = if let Some(object) = opt_object {
26+
let field_impls =
27+
object.field_impls_for_selection(context, &self.selection, &self.name)?;
28+
let fields =
29+
object.response_fields_for_selection(context, &self.selection, &self.name)?;
30+
(field_impls, fields)
31+
} else if let Some(iface) = context.schema.interfaces.get(&self.on) {
32+
let field_impls =
33+
iface.field_impls_for_selection(context, &self.selection, &self.name)?;
34+
let fields =
35+
iface.response_fields_for_selection(context, &self.selection, &self.name)?;
36+
(field_impls, fields)
2137
} else {
2238
panic!(
2339
"fragment '{}' cannot operate on unknown type '{}'",
2440
self.name, self.on
2541
);
2642
};
27-
let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?;
28-
let fields = object.response_fields_for_selection(context, &self.selection, &self.name)?;
2943

3044
Ok(quote!{
3145
#derives

graphql_client_codegen/src/interfaces.rs

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,34 @@ use unions::union_variants;
1212
#[derive(Debug, Clone, PartialEq)]
1313
pub struct GqlInterface {
1414
pub description: Option<String>,
15+
/// The set of object types implementing this interface.
1516
pub implemented_by: HashSet<String>,
17+
/// The name of the interface. Should match 1-to-1 to its name in the GraphQL schema.
1618
pub name: String,
19+
/// The interface's fields. Analogous to object fields.
1720
pub fields: Vec<GqlObjectField>,
1821
pub is_required: Cell<bool>,
1922
}
2023

2124
impl GqlInterface {
22-
pub fn new(name: Cow<str>, description: Option<&str>) -> GqlInterface {
25+
/// filters the selection to keep only the fields that refer to the interface's own.
26+
fn object_selection(&self, selection: &Selection) -> Selection {
27+
Selection(
28+
selection
29+
.0
30+
.iter()
31+
// Only keep what we can handle
32+
.filter(|f| match f {
33+
SelectionItem::Field(f) => f.name != "__typename",
34+
SelectionItem::FragmentSpread(_) => true,
35+
SelectionItem::InlineFragment(_) => false,
36+
}).map(|a| (*a).clone())
37+
.collect(),
38+
)
39+
}
40+
41+
/// Create an empty interface. This needs to be mutated before it is useful.
42+
pub(crate) fn new(name: Cow<str>, description: Option<&str>) -> GqlInterface {
2343
GqlInterface {
2444
description: description.map(|d| d.to_owned()),
2545
name: name.into_owned(),
@@ -29,6 +49,38 @@ impl GqlInterface {
2949
}
3050
}
3151

52+
/// The generated code for each of the selected field's types. See [shared::field_impls_for_selection].
53+
pub(crate) fn field_impls_for_selection(
54+
&self,
55+
context: &QueryContext,
56+
selection: &Selection,
57+
prefix: &str,
58+
) -> Result<Vec<TokenStream>, failure::Error> {
59+
::shared::field_impls_for_selection(
60+
&self.fields,
61+
context,
62+
&self.object_selection(selection),
63+
prefix,
64+
)
65+
}
66+
67+
/// The code for the interface's corresponding struct's fields.
68+
pub(crate) fn response_fields_for_selection(
69+
&self,
70+
context: &QueryContext,
71+
selection: &Selection,
72+
prefix: &str,
73+
) -> Result<Vec<TokenStream>, failure::Error> {
74+
response_fields_for_selection(
75+
&self.name,
76+
&self.fields,
77+
context,
78+
&self.object_selection(selection),
79+
prefix,
80+
)
81+
}
82+
83+
/// Generate all the code for the interface.
3284
pub(crate) fn response_for_selection(
3385
&self,
3486
query_context: &QueryContext,
@@ -42,19 +94,6 @@ impl GqlInterface {
4294
.extract_typename()
4395
.ok_or_else(|| format_err!("Missing __typename in selection for {}", prefix))?;
4496

45-
let object_selection = Selection(
46-
selection
47-
.0
48-
.iter()
49-
// Only keep what we can handle
50-
.filter(|f| match f {
51-
SelectionItem::Field(f) => f.name != "__typename",
52-
SelectionItem::FragmentSpread(_) => true,
53-
SelectionItem::InlineFragment(_) => false,
54-
}).map(|a| (*a).clone())
55-
.collect(),
56-
);
57-
5897
let union_selection = Selection(
5998
selection
6099
.0
@@ -67,16 +106,10 @@ impl GqlInterface {
67106
.collect(),
68107
);
69108

70-
let object_fields = response_fields_for_selection(
71-
&self.name,
72-
&self.fields,
73-
query_context,
74-
&object_selection,
75-
prefix,
76-
)?;
109+
let object_fields =
110+
self.response_fields_for_selection(query_context, &selection, prefix)?;
77111

78-
let object_children =
79-
field_impls_for_selection(&self.fields, query_context, &object_selection, prefix)?;
112+
let object_children = self.field_impls_for_selection(query_context, &selection, prefix)?;
80113
let (mut union_variants, union_children, used_variants) =
81114
union_variants(&union_selection, query_context, prefix)?;
82115

graphql_client_codegen/src/lib.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
#![recursion_limit = "512"]
2+
#![deny(missing_docs)]
3+
4+
//! Crate for internal use by other graphql-client crates, for code generation.
5+
//!
6+
//! It is not meant to be used directly by users of the library.
27
38
#[macro_use]
49
extern crate failure;
@@ -19,11 +24,14 @@ extern crate quote;
1924

2025
use proc_macro2::TokenStream;
2126

27+
/// Derive-related code. This will be moved into graphql_query_derive.
2228
pub mod attributes;
23-
pub mod codegen;
29+
mod codegen;
30+
/// Deprecation-related code
2431
pub mod deprecation;
25-
pub mod introspection_response;
26-
pub mod query;
32+
mod introspection_response;
33+
mod query;
34+
/// Contains the [Schema] type and its implementation.
2735
pub mod schema;
2836

2937
mod constants;
@@ -55,9 +63,13 @@ lazy_static! {
5563
CacheMap::default();
5664
}
5765

66+
/// Used to configure code generation.
5867
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.
5969
pub struct_name: String,
70+
/// Comma-separated list of additional traits we want to derive.
6071
pub additional_derives: Option<String>,
72+
/// The deprecation strategy to adopt.
6173
pub deprecation_strategy: Option<deprecation::DeprecationStrategy>,
6274
}
6375

@@ -66,6 +78,7 @@ pub(crate) struct FullResponse<T> {
6678
data: T,
6779
}
6880

81+
/// Generates the code for a Rust module given a query, a schema and options.
6982
pub fn generate_module_token_stream(
7083
query_path: std::path::PathBuf,
7184
schema_path: std::path::PathBuf,

graphql_client_codegen/src/schema.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,24 @@ use scalars::Scalar;
1010
use std::collections::{BTreeMap, BTreeSet};
1111
use unions::GqlUnion;
1212

13-
pub const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"];
13+
pub(crate) const DEFAULT_SCALARS: &[&str] = &["ID", "String", "Int", "Float", "Boolean"];
1414

15+
/// Intermediate representation for a parsed GraphQL schema used during code generation.
1516
#[derive(Debug, Clone, PartialEq)]
1617
pub struct Schema {
17-
pub enums: BTreeMap<String, GqlEnum>,
18-
pub inputs: BTreeMap<String, GqlInput>,
19-
pub interfaces: BTreeMap<String, GqlInterface>,
20-
pub objects: BTreeMap<String, GqlObject>,
21-
pub scalars: BTreeMap<String, Scalar>,
22-
pub unions: BTreeMap<String, GqlUnion>,
23-
pub query_type: Option<String>,
24-
pub mutation_type: Option<String>,
25-
pub subscription_type: Option<String>,
18+
pub(crate) enums: BTreeMap<String, GqlEnum>,
19+
pub(crate) inputs: BTreeMap<String, GqlInput>,
20+
pub(crate) interfaces: BTreeMap<String, GqlInterface>,
21+
pub(crate) objects: BTreeMap<String, GqlObject>,
22+
pub(crate) scalars: BTreeMap<String, Scalar>,
23+
pub(crate) unions: BTreeMap<String, GqlUnion>,
24+
pub(crate) query_type: Option<String>,
25+
pub(crate) mutation_type: Option<String>,
26+
pub(crate) subscription_type: Option<String>,
2627
}
2728

2829
impl Schema {
29-
pub fn new() -> Schema {
30+
pub(crate) fn new() -> Schema {
3031
Schema {
3132
enums: BTreeMap::new(),
3233
inputs: BTreeMap::new(),
@@ -40,7 +41,7 @@ impl Schema {
4041
}
4142
}
4243

43-
pub fn ingest_interface_implementations(
44+
pub(crate) fn ingest_interface_implementations(
4445
&mut self,
4546
impls: BTreeMap<String, Vec<String>>,
4647
) -> Result<(), failure::Error> {

0 commit comments

Comments
 (0)