Skip to content

Refactor schema representation and code generation #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 93 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
27a4a54
Start refactoring the schema to a normalized graph-like datastructure
tomhoule Jan 17, 2020
9f83c6d
Normalize fields
tomhoule Jan 27, 2020
c49ad84
Decide general workflow
tomhoule Jan 28, 2020
6db9d19
Make progress on query resolution
tomhoule Jan 28, 2020
a5ca329
Subscription resolution
tomhoule Feb 5, 2020
20fd6bb
Make progress on codegen
tomhoule Feb 5, 2020
e64bf25
Make progress on variables struct generation
tomhoule Feb 5, 2020
514f695
Insert parent information in Selection struct
tomhoule Feb 5, 2020
8218809
Implement non-lossy way to walk up a selection
tomhoule Feb 5, 2020
198510a
Finalize first version of variables struct rendering
tomhoule Feb 5, 2020
233a889
Start rendering query selection
tomhoule Feb 7, 2020
d414921
Reintroduce support for aliases
tomhoule Feb 7, 2020
45fe183
Make progress on selection rendering
tomhoule Feb 7, 2020
6d107ce
First attempt at recursing through selection
tomhoule Feb 7, 2020
48e41d0
Progress on selection rendering
tomhoule Feb 7, 2020
bba6682
Try reifying selections
tomhoule Feb 13, 2020
1beaed0
Go back to codegen
tomhoule Feb 13, 2020
a6c0ecf
Experiment with petgraph
tomhoule Feb 14, 2020
8cc4678
implement upgrade
tomhoule Feb 14, 2020
c2a5fe9
Simplify method on ResolvedQuery
tomhoule Feb 14, 2020
a859661
Make progress on paths
tomhoule Feb 14, 2020
2f831d8
Collect used types from variables too
tomhoule Feb 15, 2020
bb51d41
Ditch refs
tomhoule Feb 22, 2020
723b60d
Cleanup
tomhoule Feb 22, 2020
a27746d
More simplification
tomhoule Feb 22, 2020
e40ead2
Cache parsed schemas
tomhoule Feb 22, 2020
8d4b052
Expand fragment definitions
tomhoule Feb 22, 2020
328c4f8
Start reimplementing deprecation handling
tomhoule Feb 22, 2020
d48c63d
Make progress on input object handling
tomhoule Feb 22, 2020
efee0df
Address some of the test panics
tomhoule Feb 22, 2020
018b38b
Explore rendering of selections on unions
tomhoule Feb 23, 2020
60ec91d
Progress on unions codegen
tomhoule Feb 23, 2020
6380559
Work towards rendering fragments on unions
tomhoule Feb 23, 2020
1632d83
Expose a flaw in union type generation
tomhoule Feb 23, 2020
a0b21a0
Identify next steps for codegen
tomhoule Feb 24, 2020
4ec9674
Use selection ids to resolve selections
tomhoule Feb 24, 2020
053c34c
Test union codegen
tomhoule Feb 24, 2020
0c394bb
Refactor selection resolution for clarity
tomhoule Mar 1, 2020
ee864f9
Make progress in the selection codegen code
tomhoule Mar 1, 2020
324924b
Reimplement type-refining fragments and proper variants handling
tomhoule Mar 14, 2020
8ed1fd8
Finish interface and union selections rendering
tomhoule Mar 14, 2020
c9ad62b
Make union tests pass
tomhoule Mar 14, 2020
382d0ac
rustfmt
tomhoule Mar 14, 2020
cbc71cb
Light test debugging
tomhoule Mar 14, 2020
e2a5b46
Improve cryptic panic
tomhoule Mar 14, 2020
a554885
Make type-refining fragments work
tomhoule Mar 14, 2020
2d2a61c
Reimplement field deprecation annotations
tomhoule Mar 14, 2020
a2934cb
Generate structs for all used input types
tomhoule Mar 14, 2020
57dd097
Minor cleanup
tomhoule Mar 14, 2020
efd7dfa
Start work on rendering input field types
tomhoule Mar 15, 2020
32c89a3
Reimplement input field boxing
tomhoule Mar 15, 2020
3522fc5
Finish the reimplementation of input fields rendering
tomhoule Mar 15, 2020
8167564
Generate custom scalars in queries
tomhoule Mar 15, 2020
3ea2f01
Avoid infinite recursion on fragment resolution
tomhoule Mar 15, 2020
6f0eece
Delete commented-out code
tomhoule Mar 15, 2020
386b05f
Add back recursive fragment handling
tomhoule Mar 15, 2020
e91f6e6
Make more tests pass
tomhoule Mar 20, 2020
0bee2b0
Fix test expectation
tomhoule Mar 20, 2020
4b6aced
Fix last graphql_client e2e tests
tomhoule Mar 20, 2020
7c2dafd
Make graphql_client doctests pass
tomhoule Mar 20, 2020
2f6fc18
Reimplement normalization
tomhoule Mar 20, 2020
e47b615
Delete a bunch of dead code
tomhoule Mar 20, 2020
ec9fb52
Delete file committed by mistake
tomhoule Mar 20, 2020
0bed508
Delete more dead code
tomhoule Mar 20, 2020
6ab3846
Simplify normalization
tomhoule Mar 20, 2020
4657dcb
Polish json conversion
tomhoule Mar 20, 2020
5ebd4f1
Delete more dead code
tomhoule Mar 20, 2020
19cb078
Reuse previous helpful error messages
tomhoule Mar 20, 2020
f149078
Fix remaining warnings
tomhoule Mar 20, 2020
92c4403
Delete more dead code
tomhoule Mar 20, 2020
5327bcf
Move github tests
tomhoule Mar 20, 2020
fef8532
Remove old .gitignores
tomhoule Mar 20, 2020
078940f
Explore gql/json schema parsing mismatch
tomhoule Mar 21, 2020
e9db28c
Rename `resolution` module to `query`
tomhoule Mar 21, 2020
97f0694
Fix remaining problems with schema conversion test
tomhoule Mar 21, 2020
7ede92d
Make keywords tests pass
tomhoule Mar 21, 2020
1458083
Start splitting up query.rs
tomhoule Mar 22, 2020
197f4a9
Reorganize more modules
tomhoule Mar 22, 2020
c5f2886
Delete top-level operations module
tomhoule Mar 22, 2020
9e57406
Properly set variable derives on input type structs
tomhoule Mar 22, 2020
77a5490
Rename ResolvedQuery to Query
tomhoule Mar 24, 2020
e4bc878
Simplify schema and query navigation
tomhoule Mar 24, 2020
a7bd81e
Alias response structs containing a single fragment to just the fragment
tomhoule Mar 24, 2020
c61bab3
Minor error handling improvements
tomhoule Mar 25, 2020
8920d5a
Add validations for union and interface selections
tomhoule Mar 25, 2020
a769176
Split up query::resolve
tomhoule Mar 25, 2020
004aed6
Move validation into separate module
tomhoule Mar 25, 2020
c075361
Run prettier
tomhoule Mar 25, 2020
ac82fd8
Fix false-positives in deprecations
tomhoule Mar 25, 2020
e5b3916
Fix a bunch of clippy warnings
tomhoule Mar 25, 2020
c839414
Make clippy pass
tomhoule Mar 26, 2020
ca2ad8a
Rebase conflicts
tomhoule Mar 29, 2020
c902970
Replace mem::replace with mem::take where possible
tomhoule Apr 4, 2020
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@ There are a number of breaking changes due to the new features, read the `Added`

### Added

- Copy documentation from the GraphQL schema to the generated types (including their fields) as normal Rust documentation. Documentation will show up in the generated docs as well as IDEs that support expanding derive macros (which does not include the RLS yet).
- Copy documentation from the GraphQL schema to the generated types (including
their fields) as normal Rust documentation. Documentation will show up in the
generated docs as well as IDEs that support expanding derive macros (which
does not include the RLS yet).
- Implement and test deserializing subscription responses. We also try to provide helpful error messages when a subscription query is not valid (i.e. when it has more than one top-level field).
- Support the [new top-level errors shape from the June 2018 spec](https://github.com/facebook/graphql/blob/master/spec/Section%207%20--%20Response.md), except for the `extensions` field (see issue #64).

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ A typed GraphQL client library for Rust.

```rust
use graphql_client::{GraphQLQuery, Response};
use std::error::Error;

#[derive(GraphQLQuery)]
#[graphql(
Expand All @@ -68,7 +69,7 @@ A typed GraphQL client library for Rust.
)]
pub struct UnionQuery;

fn perform_my_query(variables: union_query::Variables) -> Result<(), anyhow::Error> {
fn perform_my_query(variables: union_query::Variables) -> Result<(), Box<dyn Error>> {

// this is the important line
let request_body = UnionQuery::build_query(variables);
Expand Down
2 changes: 1 addition & 1 deletion examples/github/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Tom Houlé <[email protected]>"]
edition = "2018"

[dev-dependencies]
anyhow = "*"
anyhow = "1.0"
graphql_client = { path = "../../graphql_client" }
serde = "^1.0"
reqwest = "^0.9"
Expand Down
2 changes: 1 addition & 1 deletion examples/hasura/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["Mark Catley <[email protected]>"]
edition = "2018"

[dev-dependencies]
anyhow = "*"
anyhow = "1.0"
graphql_client = { path = "../../graphql_client" }
serde = "1.0"
serde_derive = "1.0"
Expand Down
23 changes: 9 additions & 14 deletions graphql-introspection-query/src/introspection_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl<'de> Deserialize<'de> for __DirectiveLocation {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum __TypeKind {
SCALAR,
OBJECT,
Expand Down Expand Up @@ -130,11 +130,11 @@ pub struct FullType {
pub kind: Option<__TypeKind>,
pub name: Option<String>,
pub description: Option<String>,
pub fields: Option<Vec<Option<FullTypeFields>>>,
pub input_fields: Option<Vec<Option<FullTypeInputFields>>>,
pub interfaces: Option<Vec<Option<FullTypeInterfaces>>>,
pub enum_values: Option<Vec<Option<FullTypeEnumValues>>>,
pub possible_types: Option<Vec<Option<FullTypePossibleTypes>>>,
pub fields: Option<Vec<FullTypeFields>>,
pub input_fields: Option<Vec<FullTypeInputFields>>,
pub interfaces: Option<Vec<FullTypeInterfaces>>,
pub enum_values: Option<Vec<FullTypeEnumValues>>,
pub possible_types: Option<Vec<FullTypePossibleTypes>>,
}

#[derive(Clone, Debug, Deserialize)]
Expand Down Expand Up @@ -196,19 +196,14 @@ pub struct FullTypePossibleTypes {
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InputValue {
pub name: Option<String>,
pub name: String,
pub description: Option<String>,
#[serde(rename = "type")]
pub type_: Option<InputValueType>,
pub type_: InputValueType,
pub default_value: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InputValueType {
#[serde(flatten)]
pub type_ref: TypeRef,
}
type InputValueType = TypeRef;

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down
9 changes: 6 additions & 3 deletions graphql_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ doc_comment::doctest!("../../README.md");
/// ```
/// use graphql_client::*;
/// use serde_json::json;
/// use std::error::Error;
///
/// #[derive(GraphQLQuery)]
/// #[graphql(
Expand All @@ -39,7 +40,7 @@ doc_comment::doctest!("../../README.md");
/// )]
/// struct StarWarsQuery;
///
/// fn main() -> Result<(), anyhow::Error> {
/// fn main() -> Result<(), Box<dyn Error>> {
/// use graphql_client::GraphQLQuery;
///
/// let variables = star_wars_query::Variables {
Expand Down Expand Up @@ -124,13 +125,14 @@ impl Display for PathFragment {
/// # use serde_json::json;
/// # use serde::Deserialize;
/// # use graphql_client::GraphQLQuery;
/// # use std::error::Error;
/// #
/// # #[derive(Debug, Deserialize, PartialEq)]
/// # struct ResponseData {
/// # something: i32
/// # }
/// #
/// # fn main() -> Result<(), anyhow::Error> {
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use graphql_client::*;
///
/// let body: Response<ResponseData> = serde_json::from_value(json!({
Expand Down Expand Up @@ -230,6 +232,7 @@ impl Display for Error {
/// # use serde_json::json;
/// # use serde::Deserialize;
/// # use graphql_client::GraphQLQuery;
/// # use std::error::Error;
/// #
/// # #[derive(Debug, Deserialize, PartialEq)]
/// # struct User {
Expand All @@ -247,7 +250,7 @@ impl Display for Error {
/// # dogs: Vec<Dog>,
/// # }
/// #
/// # fn main() -> Result<(), anyhow::Error> {
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use graphql_client::Response;
///
/// let body: Response<ResponseData> = serde_json::from_value(json!({
Expand Down
26 changes: 6 additions & 20 deletions graphql_client/tests/fragments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,7 @@ fn fragment_reference() {
let valid_fragment_reference =
serde_json::from_value::<fragment_reference::ResponseData>(valid_response).unwrap();

assert_eq!(
valid_fragment_reference
.fragment_reference
.in_fragment
.unwrap(),
"value"
);
assert_eq!(valid_fragment_reference.in_fragment.unwrap(), "value");
}

#[test]
Expand All @@ -42,13 +36,7 @@ fn fragments_with_snake_case_name() {
let valid_fragment_reference =
serde_json::from_value::<snake_case_fragment::ResponseData>(valid_response).unwrap();

assert_eq!(
valid_fragment_reference
.snake_case_fragment
.in_fragment
.unwrap(),
"value"
);
assert_eq!(valid_fragment_reference.in_fragment.unwrap(), "value");
}

#[derive(GraphQLQuery)]
Expand All @@ -64,11 +52,9 @@ fn recursive_fragment() {

let _ = RecursiveFragment {
head: Some("ABCD".to_string()),
tail: Some(RecursiveFragmentTail {
recursive_fragment: Box::new(RecursiveFragment {
head: Some("EFGH".to_string()),
tail: None,
}),
}),
tail: Some(Box::new(RecursiveFragment {
head: Some("EFGH".to_string()),
tail: None,
})),
};
}
11 changes: 6 additions & 5 deletions graphql_client/tests/input_object_variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ fn input_object_variables_default() {
msg: default_input_object_variables_query::Variables::default_msg(),
};

let out = serde_json::to_string(&variables).unwrap();
let out = serde_json::to_value(&variables).unwrap();

assert_eq!(
out,
r#"{"msg":{"content":null,"to":{"category":null,"email":"[email protected]","name":null}}}"#,
);
let expected_default = serde_json::json!({
"msg":{"content":null,"to":{"category":null,"email":"[email protected]","name":null}}
});

assert_eq!(out, expected_default);
}

#[derive(GraphQLQuery)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
fragment Birthday on Person {
fragment BirthdayFragment on Person {
birthday
}

Expand All @@ -9,7 +9,7 @@ query QueryOnInterface {
... on Dog {
isGoodDog
}
...Birthday
...BirthdayFragment
... on Organization {
industry
}
Expand Down
80 changes: 63 additions & 17 deletions graphql_client/tests/union_query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use graphql_client::*;

const RESPONSE: &str = include_str!("unions/union_query_response.json");
const FRAGMENT_AND_MORE_RESPONSE: &str = include_str!("unions/fragment_and_more_response.json");

#[derive(GraphQLQuery)]
#[graphql(
Expand All @@ -18,6 +19,14 @@ pub struct UnionQuery;
)]
pub struct FragmentOnUnion;

#[derive(GraphQLQuery)]
#[graphql(
query_path = "tests/unions/union_query.graphql",
schema_path = "tests/unions/union_schema.graphql",
response_derives = "PartialEq, Debug"
)]
pub struct FragmentAndMoreOnUnion;

#[test]
fn union_query_deserialization() {
let response_data: union_query::ResponseData = serde_json::from_str(RESPONSE).unwrap();
Expand Down Expand Up @@ -53,26 +62,63 @@ fn fragment_on_union() {

let expected = fragment_on_union::ResponseData {
names: Some(vec![
fragment_on_union::FragmentOnUnionNames::Person(
fragment_on_union::FragmentOnUnionNamesOnPerson {
first_name: "Audrey".to_string(),
},
),
fragment_on_union::FragmentOnUnionNames::Dog(
fragment_on_union::FragmentOnUnionNamesOnDog {
name: "Laïka".to_string(),
},
),
fragment_on_union::FragmentOnUnionNames::Organization(
fragment_on_union::FragmentOnUnionNamesOnOrganization {
fragment_on_union::NamesFragment::Person(fragment_on_union::NamesFragmentOnPerson {
first_name: "Audrey".to_string(),
}),
fragment_on_union::NamesFragment::Dog(fragment_on_union::NamesFragmentOnDog {
name: "Laïka".to_string(),
}),
fragment_on_union::NamesFragment::Organization(
fragment_on_union::NamesFragmentOnOrganization {
title: "Mozilla".to_string(),
},
),
fragment_on_union::FragmentOnUnionNames::Dog(
fragment_on_union::FragmentOnUnionNamesOnDog {
name: "Norbert".to_string(),
},
),
fragment_on_union::NamesFragment::Dog(fragment_on_union::NamesFragmentOnDog {
name: "Norbert".to_string(),
}),
]),
};

assert_eq!(response_data, expected);
}

#[test]
fn fragment_and_more_on_union() {
use fragment_and_more_on_union::*;

let response_data: fragment_and_more_on_union::ResponseData =
serde_json::from_str(FRAGMENT_AND_MORE_RESPONSE).unwrap();

let expected = fragment_and_more_on_union::ResponseData {
names: Some(vec![
FragmentAndMoreOnUnionNames {
names_fragment: NamesFragment::Person(NamesFragmentOnPerson {
first_name: "Larry".into(),
}),
on: FragmentAndMoreOnUnionNamesOn::Person,
},
FragmentAndMoreOnUnionNames {
names_fragment: NamesFragment::Dog(NamesFragmentOnDog {
name: "Laïka".into(),
}),
on: FragmentAndMoreOnUnionNamesOn::Dog(FragmentAndMoreOnUnionNamesOnDog {
is_good_dog: true,
}),
},
FragmentAndMoreOnUnionNames {
names_fragment: NamesFragment::Organization(NamesFragmentOnOrganization {
title: "Mozilla".into(),
}),
on: FragmentAndMoreOnUnionNamesOn::Organization,
},
FragmentAndMoreOnUnionNames {
names_fragment: NamesFragment::Dog(NamesFragmentOnDog {
name: "Norbert".into(),
}),
on: FragmentAndMoreOnUnionNamesOn::Dog(FragmentAndMoreOnUnionNamesOnDog {
is_good_dog: true,
}),
},
]),
};

Expand Down
22 changes: 22 additions & 0 deletions graphql_client/tests/unions/fragment_and_more_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"names": [
{
"__typename": "Person",
"firstName": "Larry"
},
{
"__typename": "Dog",
"name": "Laïka",
"isGoodDog": true
},
{
"__typename": "Organization",
"title": "Mozilla"
},
{
"__typename": "Dog",
"name": "Norbert",
"isGoodDog": true
}
]
}
9 changes: 9 additions & 0 deletions graphql_client/tests/unions/union_query.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ query FragmentOnUnion {
...NamesFragment
}
}

query FragmentAndMoreOnUnion {
names {
...NamesFragment
... on Dog {
isGoodDog
}
}
}
2 changes: 1 addition & 1 deletion graphql_client_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ path = "src/main.rs"
[dependencies]
anyhow = "1.0"
reqwest = "^0.9"
graphql_client = { version = "0.9.0", path = "../graphql_client" }
graphql_client = { version = "0.9.0", path = "../graphql_client", features = [] }
graphql_client_codegen = { path = "../graphql_client_codegen/", version = "0.9.0" }
structopt = "0.3"
serde = { version = "^1.0", features = ["derive"] }
Expand Down
4 changes: 2 additions & 2 deletions graphql_client_cli/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> Result<()> {
options.set_deprecation_strategy(deprecation_strategy);
}

let gen = generate_module_token_stream(query_path.clone(), &schema_path, options).map_err(|fail| fail.compat())?;
let gen = generate_module_token_stream(query_path.clone(), &schema_path, options)?;

let generated_code = gen.to_string();
let generated_code = if cfg!(feature = "rustfmt") && !no_formatting {
Expand Down Expand Up @@ -103,5 +103,5 @@ fn format(codes: &str) -> String {
return String::from_utf8(out).unwrap();
}
#[cfg(not(feature = "rustfmt"))]
unreachable!()
unreachable!("called format() without the rustfmt feature")
}
Loading