Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
root = true

[*]
[*.rs]
end_of_line = lf
insert_final_newline = true
charset = utf-8
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/target
target/
node_modules/
**/*.rs.bk
Cargo.lock
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

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

- Basic support for fragments on interfaces. See #154 for what is not supported yet.

### Fixed

- 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!
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ categories = ["network-programming", "web-programming", "wasm"]
failure = "0.1"
graphql_query_derive = {path = "./graphql_query_derive", version = "0.4.0"}
itertools = "0.7"
serde = "1.0"
serde = "^1.0.78"
serde_derive = "1.0"
serde_json = "1.0"

Expand Down
2 changes: 1 addition & 1 deletion examples/call_from_js/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![feature(wasm_custom_section, wasm_import_module, use_extern_macros)]
#![feature(use_extern_macros)]

#[macro_use]
extern crate graphql_client;
Expand Down
2 changes: 1 addition & 1 deletion graphql_client_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ lazy_static = "1.0"
quote = "0.6"
syn = "0.15"
proc-macro2 = { version = "0.4", features = [] }
serde = "1.0"
serde = "^1.0.78"
serde_derive = "1.0"
serde_json = "1.0"
heck = "0.3"
Expand Down
1 change: 1 addition & 0 deletions graphql_client_codegen/src/attributes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use failure;
use syn;

/// Extract an configuration parameter specified in the `graphql` attribute.
pub fn extract_attr(ast: &syn::DeriveInput, attr: &str) -> Result<String, failure::Error> {
let attributes = &ast.attrs;
let attribute = attributes
Expand Down
8 changes: 8 additions & 0 deletions graphql_client_codegen/src/deprecation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ use syn;

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

/// Whether an item is deprecated, with context.
#[derive(Debug, PartialEq, Hash, Clone)]
pub enum DeprecationStatus {
/// Not deprecated
Current,
/// Deprecated
Deprecated(Option<String>),
}

/// The available deprecation startegies.
#[derive(Debug, PartialEq)]
pub enum DeprecationStrategy {
/// Allow use of deprecated items in queries, and say nothing.
Allow,
/// Fail compilation if a deprecated item is used.
Deny,
/// Allow use of deprecated items in queries, but warn about them (default).
Warn,
}

Expand All @@ -23,6 +30,7 @@ impl Default for DeprecationStrategy {
}
}

/// Get the deprecation from a struct attribute in the derive case.
pub fn extract_deprecation_strategy(
ast: &syn::DeriveInput,
) -> Result<DeprecationStrategy, failure::Error> {
Expand Down
22 changes: 18 additions & 4 deletions graphql_client_codegen/src/fragments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,43 @@ use query::QueryContext;
use selection::Selection;
use std::cell::Cell;

/// Represents a fragment extracted from a query document.
#[derive(Debug, PartialEq)]
pub struct GqlFragment {
/// The name of the fragment, matching one-to-one with the name in the GraphQL query document.
pub name: String,
/// The `on` clause of the fragment.
pub on: String,
/// The selected fields.
pub selection: Selection,
/// Whether the fragment
pub is_required: Cell<bool>,
}

impl GqlFragment {
/// Generate all the Rust code required by the fragment's selection.
pub(crate) fn to_rust(&self, context: &QueryContext) -> Result<TokenStream, ::failure::Error> {
let derives = context.response_derives();
let name_ident = Ident::new(&self.name, Span::call_site());
let opt_object = context.schema.objects.get(&self.on);
let object = if let Some(object) = opt_object {
object
let (field_impls, fields) = if let Some(object) = opt_object {
let field_impls =
object.field_impls_for_selection(context, &self.selection, &self.name)?;
let fields =
object.response_fields_for_selection(context, &self.selection, &self.name)?;
(field_impls, fields)
} else if let Some(iface) = context.schema.interfaces.get(&self.on) {
let field_impls =
iface.field_impls_for_selection(context, &self.selection, &self.name)?;
let fields =
iface.response_fields_for_selection(context, &self.selection, &self.name)?;
(field_impls, fields)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code isn't pretty, we could make it a trait. On the other hand we have to support type-refining fragments so it may not be the right time to abstract more.

} else {
panic!(
"fragment '{}' cannot operate on unknown type '{}'",
self.name, self.on
);
};
let field_impls = object.field_impls_for_selection(context, &self.selection, &self.name)?;
let fields = object.response_fields_for_selection(context, &self.selection, &self.name)?;

Ok(quote!{
#derives
Expand Down
79 changes: 56 additions & 23 deletions graphql_client_codegen/src/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,34 @@ use unions::union_variants;
#[derive(Debug, Clone, PartialEq)]
pub struct GqlInterface {
pub description: Option<String>,
/// The set of object types implementing this interface.
pub implemented_by: HashSet<String>,
/// The name of the interface. Should match 1-to-1 to its name in the GraphQL schema.
pub name: String,
/// The interface's fields. Analogous to object fields.
pub fields: Vec<GqlObjectField>,
pub is_required: Cell<bool>,
}

impl GqlInterface {
pub fn new(name: Cow<str>, description: Option<&str>) -> GqlInterface {
/// filters the selection to keep only the fields that refer to the interface's own.
fn object_selection(&self, selection: &Selection) -> Selection {
Selection(
selection
.0
.iter()
// Only keep what we can handle
.filter(|f| match f {
SelectionItem::Field(f) => f.name != "__typename",
SelectionItem::FragmentSpread(_) => true,
SelectionItem::InlineFragment(_) => false,
}).map(|a| (*a).clone())
.collect(),
)
}

/// Create an empty interface. This needs to be mutated before it is useful.
pub(crate) fn new(name: Cow<str>, description: Option<&str>) -> GqlInterface {
GqlInterface {
description: description.map(|d| d.to_owned()),
name: name.into_owned(),
Expand All @@ -29,6 +49,38 @@ impl GqlInterface {
}
}

/// The generated code for each of the selected field's types. See [shared::field_impls_for_selection].
pub(crate) fn field_impls_for_selection(
&self,
context: &QueryContext,
selection: &Selection,
prefix: &str,
) -> Result<Vec<TokenStream>, failure::Error> {
::shared::field_impls_for_selection(
&self.fields,
context,
&self.object_selection(selection),
prefix,
)
}

/// The code for the interface's corresponding struct's fields.
pub(crate) fn response_fields_for_selection(
&self,
context: &QueryContext,
selection: &Selection,
prefix: &str,
) -> Result<Vec<TokenStream>, failure::Error> {
response_fields_for_selection(
&self.name,
&self.fields,
context,
&self.object_selection(selection),
prefix,
)
}

/// Generate all the code for the interface.
pub(crate) fn response_for_selection(
&self,
query_context: &QueryContext,
Expand All @@ -42,19 +94,6 @@ impl GqlInterface {
.extract_typename()
.ok_or_else(|| format_err!("Missing __typename in selection for {}", prefix))?;

let object_selection = Selection(
selection
.0
.iter()
// Only keep what we can handle
.filter(|f| match f {
SelectionItem::Field(f) => f.name != "__typename",
SelectionItem::FragmentSpread(_) => true,
SelectionItem::InlineFragment(_) => false,
}).map(|a| (*a).clone())
.collect(),
);

let union_selection = Selection(
selection
.0
Expand All @@ -67,16 +106,10 @@ impl GqlInterface {
.collect(),
);

let object_fields = response_fields_for_selection(
&self.name,
&self.fields,
query_context,
&object_selection,
prefix,
)?;
let object_fields =
self.response_fields_for_selection(query_context, &selection, prefix)?;

let object_children =
field_impls_for_selection(&self.fields, query_context, &object_selection, prefix)?;
let object_children = self.field_impls_for_selection(query_context, &selection, prefix)?;
let (mut union_variants, union_children, used_variants) =
union_variants(&union_selection, query_context, prefix)?;

Expand Down
19 changes: 16 additions & 3 deletions graphql_client_codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
#![recursion_limit = "512"]
#![deny(missing_docs)]

//! Crate for internal use by other graphql-client crates, for code generation.
//!
//! It is not meant to be used directly by users of the library.

#[macro_use]
extern crate failure;
Expand All @@ -19,11 +24,14 @@ extern crate quote;

use proc_macro2::TokenStream;

/// Derive-related code. This will be moved into graphql_query_derive.
pub mod attributes;
pub mod codegen;
mod codegen;
/// Deprecation-related code
pub mod deprecation;
pub mod introspection_response;
pub mod query;
mod introspection_response;
mod query;
/// Contains the [Schema] type and its implementation.
pub mod schema;

mod constants;
Expand Down Expand Up @@ -55,9 +63,13 @@ lazy_static! {
CacheMap::default();
}

/// Used to configure code generation.
pub struct GraphQLClientDeriveOptions {
/// Name of the operation we want to generate code for. If it does not match, we default to the first one.
pub struct_name: String,
/// Comma-separated list of additional traits we want to derive.
pub additional_derives: Option<String>,
/// The deprecation strategy to adopt.
pub deprecation_strategy: Option<deprecation::DeprecationStrategy>,
}

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

/// Generates the code for a Rust module given a query, a schema and options.
pub fn generate_module_token_stream(
query_path: std::path::PathBuf,
schema_path: std::path::PathBuf,
Expand Down
25 changes: 13 additions & 12 deletions graphql_client_codegen/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,24 @@ use scalars::Scalar;
use std::collections::{BTreeMap, BTreeSet};
use unions::GqlUnion;

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

/// Intermediate representation for a parsed GraphQL schema used during code generation.
#[derive(Debug, Clone, PartialEq)]
pub struct Schema {
pub enums: BTreeMap<String, GqlEnum>,
pub inputs: BTreeMap<String, GqlInput>,
pub interfaces: BTreeMap<String, GqlInterface>,
pub objects: BTreeMap<String, GqlObject>,
pub scalars: BTreeMap<String, Scalar>,
pub unions: BTreeMap<String, GqlUnion>,
pub query_type: Option<String>,
pub mutation_type: Option<String>,
pub subscription_type: Option<String>,
pub(crate) enums: BTreeMap<String, GqlEnum>,
pub(crate) inputs: BTreeMap<String, GqlInput>,
pub(crate) interfaces: BTreeMap<String, GqlInterface>,
pub(crate) objects: BTreeMap<String, GqlObject>,
pub(crate) scalars: BTreeMap<String, Scalar>,
pub(crate) unions: BTreeMap<String, GqlUnion>,
pub(crate) query_type: Option<String>,
pub(crate) mutation_type: Option<String>,
pub(crate) subscription_type: Option<String>,
}

impl Schema {
pub fn new() -> Schema {
pub(crate) fn new() -> Schema {
Schema {
enums: BTreeMap::new(),
inputs: BTreeMap::new(),
Expand All @@ -40,7 +41,7 @@ impl Schema {
}
}

pub fn ingest_interface_implementations(
pub(crate) fn ingest_interface_implementations(
&mut self,
impls: BTreeMap<String, Vec<String>>,
) -> Result<(), failure::Error> {
Expand Down
Loading