Skip to content

Add doctests on GraphQLResponse and GraphQLError #69

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 3 commits into from
Jul 24, 2018
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Unreleased

## [0.3.0] - 2018-07-24

### Added

- Implemented support for the `extensions` field on errors from the June 2018 spec (#64).
- Improved documentation crate docs, added doctests and examples

### Fixed

- `Location` fields on errors were not public.
- The field names on input objects were not properly converted between snake and camel case.

### Changed

Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql_client"
version = "0.2.0"
version = "0.3.0"
authors = ["Tom Houlé <[email protected]>"]
description = "Typed GraphQL requests and responses"
repository = "https://github.com/tomhoule/graphql-client"
Expand All @@ -11,7 +11,7 @@ categories = ["network-programming", "web-programming", "wasm"]
[dependencies]
failure = "0.1"
quote = "0.3"
graphql_query_derive = {path = "./graphql_query_derive", version = "0.2.0"}
graphql_query_derive = {path = "./graphql_query_derive", version = "0.3.0"}
graphql-parser = "0.2.0"
serde = "1.0"
serde_derive = "1.0"
Expand Down
4 changes: 2 additions & 2 deletions graphql_client_cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "graphql_client_cli"
description = "The CLI for graphql-client (WIP)"
version = "0.2.0"
version = "0.3.0"
authors = ["Tom Houlé <[email protected]>"]
license = "Apache-2.0 OR MIT"

Expand All @@ -12,7 +12,7 @@ path = "src/main.rs"
[dependencies]
failure = "0.1"
reqwest = "0.8"
graphql_client = { version = "0.2.0", path = ".." }
graphql_client = { version = "0.3.0", path = ".." }
structopt = "0.2"
serde = "1.0"
serde_derive = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion graphql_query_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graphql_query_derive"
version = "0.2.0"
version = "0.3.0"
authors = ["Tom Houlé <[email protected]>"]
description = "Utility crate for graphql_client"
license = "Apache-2.0 OR MIT"
Expand Down
5 changes: 3 additions & 2 deletions graphql_query_derive/src/inputs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use failure;
use graphql_parser;
use heck::SnakeCase;
use introspection_response;
use objects::GqlObjectField;
use proc_macro2::{Ident, Span, TokenStream};
Expand All @@ -21,7 +22,7 @@ impl GqlInput {
fields.sort_unstable_by(|a, b| a.name.cmp(&b.name));
let fields = fields.iter().map(|field| {
let ty = field.type_.to_rust(&context, "");
let name = Ident::new(&field.name, Span::call_site());
let name = Ident::new(&field.name.to_snake_case(), Span::call_site());
quote!(pub #name: #ty)
});

Expand Down Expand Up @@ -136,7 +137,7 @@ mod tests {
"# [ serde ( rename_all = \"camelCase\" ) ] ",
"pub struct Cat { ",
"pub offsprings : Vec < Cat > , ",
"pub pawsCount : Float , ",
"pub paws_count : Float , ",
"pub requirements : Option < CatRequirements > , ",
"}",
].into_iter()
Expand Down
4 changes: 3 additions & 1 deletion graphql_query_derive/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use failure;
use fragments::GqlFragment;
use graphql_parser;
use heck::SnakeCase;
use proc_macro2::{Ident, Span, TokenStream};
use schema::Schema;
use selection::Selection;
Expand Down Expand Up @@ -43,7 +44,7 @@ impl QueryContext {
let fields = self.variables.iter().map(|variable| {
let name = &variable.name;
let ty = variable.ty.to_rust(self, "");
let name = Ident::new(name, Span::call_site());
let name = Ident::new(&name.to_snake_case(), Span::call_site());
quote!(pub #name: #ty)
});

Expand All @@ -54,6 +55,7 @@ impl QueryContext {

quote! {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Variables {
#(#fields,)*
}
Expand Down
2 changes: 1 addition & 1 deletion graphql_query_derive/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ mod tests {

#[test]
fn build_schema_works() {
let gql_schema = include_str!("star_wars_schema.graphql");
let gql_schema = include_str!("tests/star_wars_schema.graphql");
let gql_schema = graphql_parser::parse_schema(gql_schema).unwrap();
let built = Schema::from(gql_schema);
assert_eq!(
Expand Down
6 changes: 6 additions & 0 deletions graphql_query_derive/src/tests/star_wars_query.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
query StarWarsQuery($episodeForHero: Episode!) {
hero(episode: $episodeForHero) {
name
__typename
}
}
175 changes: 167 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! The top-level documentation resides on the [project README](https://github.com/tomhoule/graphql-client) at the moment.
//!
//! The main interface to this library is the custom derive that generates modules from a GraphQL query and schema.
//! The main interface to this library is the custom derive that generates modules from a GraphQL query and schema. See the docs for the [`GraphQLQuery`] trait for a full example.

#![deny(missing_docs)]

Expand All @@ -20,7 +20,52 @@ use std::collections::HashMap;

/// A convenience trait that can be used to build a GraphQL request body.
///
/// This will be implemented for you by codegen in the normal case.
/// This will be implemented for you by codegen in the normal case. It is implemented on the struct you place the derive on.
///
/// Example:
///
///
/// ```
/// extern crate failure;
/// #[macro_use]
/// extern crate graphql_client;
/// #[macro_use]
/// extern crate serde_derive;
/// #[macro_use]
/// extern crate serde_json;
/// extern crate serde;
///
/// #[derive(GraphQLQuery)]
/// #[graphql(
/// query_path = "graphql_query_derive/src/tests/star_wars_query.graphql",
/// schema_path = "graphql_query_derive/src/tests/star_wars_schema.graphql"
/// )]
/// struct StarWarsQuery;
///
/// fn main() -> Result<(), failure::Error> {
/// use graphql_client::GraphQLQuery;
///
/// let variables = star_wars_query::Variables {
/// episode_for_hero: star_wars_query::Episode::NEWHOPE,
/// };
///
/// let expected_body = json!({
/// "query": star_wars_query::QUERY,
/// "variables": {
/// "episodeForHero": "NEWHOPE"
/// },
/// });
///
/// let actual_body = serde_json::to_value(
/// StarWarsQuery::build_query(variables)
/// )?;
///
/// assert_eq!(actual_body, expected_body);
///
/// Ok(())
/// }
/// ```
/// ```
pub trait GraphQLQuery<'de> {
/// The shape of the variables expected by the query. This should be a generated struct most of the time.
type Variables: serde::Serialize;
Expand All @@ -31,7 +76,7 @@ pub trait GraphQLQuery<'de> {
fn build_query(variables: Self::Variables) -> GraphQLQueryBody<Self::Variables>;
}

/// The form in which queries are sent over HTTP in most implemnetations.
/// The form in which queries are sent over HTTP in most implementations. This will be built using the [`GraphQLQuery`] trait normally.
#[derive(Debug, Serialize, Deserialize)]
pub struct GraphQLQueryBody<Variables>
where
Expand All @@ -43,14 +88,16 @@ where
pub query: &'static str,
}

/// Represents a location inside a query string. Used in errors.
/// Represents a location inside a query string. Used in errors. See [`GraphQLError`].
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Location {
line: i32,
column: i32,
/// The line number in the query string where the error originated (starting from 1).
pub line: i32,
/// The column number in the query string where the error originated (starting from 1).
pub column: i32,
}

/// Part of a path in a query. It can be an object key or an array index.
/// Part of a path in a query. It can be an object key or an array index. See [`GraphQLError`].
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum PathFragment {
Expand All @@ -65,6 +112,69 @@ pub enum PathFragment {
/// This tries to be as close to the spec as possible.
///
/// [Spec](https://github.com/facebook/graphql/blob/master/spec/Section%207%20--%20Response.md)
///
///
/// ```
/// # extern crate failure;
/// # #[macro_use]
/// # extern crate serde_json;
/// # extern crate graphql_client;
/// # #[macro_use]
/// # extern crate serde_derive;
/// #
/// # #[derive(Debug, Deserialize, PartialEq)]
/// # struct ResponseData {
/// # something: i32
/// # }
/// #
/// # fn main() -> Result<(), failure::Error> {
/// use graphql_client::*;
///
/// let body: GraphQLResponse<ResponseData> = serde_json::from_value(json!({
/// "data": null,
/// "errors": [
/// {
/// "message": "The server crashed. Sorry.",
/// "locations": [{ "line": 1, "column": 1 }]
/// },
/// {
/// "message": "Seismic activity detected",
/// "path": ["undeground", 20]
/// },
/// ],
/// }))?;
///
/// let expected: GraphQLResponse<ResponseData> = GraphQLResponse {
/// data: None,
/// errors: Some(vec![
/// GraphQLError {
/// message: "The server crashed. Sorry.".to_owned(),
/// locations: Some(vec![
/// Location {
/// line: 1,
/// column: 1,
/// }
/// ]),
/// path: None,
/// extensions: None,
/// },
/// GraphQLError {
/// message: "Seismic activity detected".to_owned(),
/// locations: None,
/// path: Some(vec![
/// PathFragment::Key("undeground".into()),
/// PathFragment::Index(20),
/// ]),
/// extensions: None,
/// },
/// ]),
/// };
///
/// assert_eq!(body, expected);
///
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct GraphQLError {
/// The human-readable error message. This is the only required field.
Expand All @@ -82,7 +192,56 @@ pub struct GraphQLError {
/// This will generally be used with the `ResponseData` struct from a derived module.
///
/// [Spec](https://github.com/facebook/graphql/blob/master/spec/Section%207%20--%20Response.md)
#[derive(Debug, Serialize, Deserialize)]
///
/// ```
/// # extern crate failure;
/// # #[macro_use]
/// # extern crate serde_json;
/// # extern crate graphql_client;
/// # #[macro_use]
/// # extern crate serde_derive;
/// #
/// # #[derive(Debug, Deserialize, PartialEq)]
/// # struct User {
/// # id: i32,
/// # }
/// #
/// # #[derive(Debug, Deserialize, PartialEq)]
/// # struct Dog {
/// # name: String
/// # }
/// #
/// # #[derive(Debug, Deserialize, PartialEq)]
/// # struct ResponseData {
/// # users: Vec<User>,
/// # dogs: Vec<Dog>,
/// # }
/// #
/// # fn main() -> Result<(), failure::Error> {
/// use graphql_client::GraphQLResponse;
///
/// let body: GraphQLResponse<ResponseData> = serde_json::from_value(json!({
/// "data": {
/// "users": [{"id": 13}],
/// "dogs": [{"name": "Strelka"}],
/// },
/// "errors": [],
/// }))?;
///
/// let expected: GraphQLResponse<ResponseData> = GraphQLResponse {
/// data: Some(ResponseData {
/// users: vec![User { id: 13 }],
/// dogs: vec![Dog { name: "Strelka".to_owned() }],
/// }),
/// errors: Some(vec![]),
/// };
///
/// assert_eq!(body, expected);
///
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct GraphQLResponse<Data> {
/// The absent, partial or complete response data.
pub data: Option<Data>,
Expand Down