Skip to content

Commit

Permalink
Merge pull request #69 from tomhoule/doctests
Browse files Browse the repository at this point in the history
Add doctests on GraphQLResponse and GraphQLError
  • Loading branch information
tomhoule authored Jul 24, 2018
2 parents 471d1f5 + 7b9c3a3 commit 9594d09
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 17 deletions.
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

0 comments on commit 9594d09

Please sign in to comment.