diff --git a/CHANGELOG.md b/CHANGELOG.md index 2351fb7a..77a3f3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index e41f55f8..64395fb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphql_client" -version = "0.2.0" +version = "0.3.0" authors = ["Tom Houlé "] description = "Typed GraphQL requests and responses" repository = "https://github.com/tomhoule/graphql-client" @@ -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" diff --git a/graphql_client_cli/Cargo.toml b/graphql_client_cli/Cargo.toml index c3f306e1..225eb368 100644 --- a/graphql_client_cli/Cargo.toml +++ b/graphql_client_cli/Cargo.toml @@ -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é "] license = "Apache-2.0 OR MIT" @@ -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" diff --git a/graphql_query_derive/Cargo.toml b/graphql_query_derive/Cargo.toml index cbba871f..20e5aefa 100644 --- a/graphql_query_derive/Cargo.toml +++ b/graphql_query_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "graphql_query_derive" -version = "0.2.0" +version = "0.3.0" authors = ["Tom Houlé "] description = "Utility crate for graphql_client" license = "Apache-2.0 OR MIT" diff --git a/graphql_query_derive/src/inputs.rs b/graphql_query_derive/src/inputs.rs index 76b9f200..1c594d7c 100644 --- a/graphql_query_derive/src/inputs.rs +++ b/graphql_query_derive/src/inputs.rs @@ -1,5 +1,6 @@ use failure; use graphql_parser; +use heck::SnakeCase; use introspection_response; use objects::GqlObjectField; use proc_macro2::{Ident, Span, TokenStream}; @@ -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) }); @@ -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() diff --git a/graphql_query_derive/src/query.rs b/graphql_query_derive/src/query.rs index e9fcba21..3368a613 100644 --- a/graphql_query_derive/src/query.rs +++ b/graphql_query_derive/src/query.rs @@ -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; @@ -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) }); @@ -54,6 +55,7 @@ impl QueryContext { quote! { #[derive(Serialize)] + #[serde(rename_all = "camelCase")] pub struct Variables { #(#fields,)* } diff --git a/graphql_query_derive/src/schema.rs b/graphql_query_derive/src/schema.rs index d5bb435d..b0c5a0eb 100644 --- a/graphql_query_derive/src/schema.rs +++ b/graphql_query_derive/src/schema.rs @@ -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!( diff --git a/graphql_query_derive/src/tests/star_wars_query.graphql b/graphql_query_derive/src/tests/star_wars_query.graphql new file mode 100644 index 00000000..4a10aebf --- /dev/null +++ b/graphql_query_derive/src/tests/star_wars_query.graphql @@ -0,0 +1,6 @@ +query StarWarsQuery($episodeForHero: Episode!) { + hero(episode: $episodeForHero) { + name + __typename + } +} diff --git a/graphql_query_derive/src/star_wars_schema.graphql b/graphql_query_derive/src/tests/star_wars_schema.graphql similarity index 100% rename from graphql_query_derive/src/star_wars_schema.graphql rename to graphql_query_derive/src/tests/star_wars_schema.graphql diff --git a/src/lib.rs b/src/lib.rs index 4a11071d..0ef45a94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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)] @@ -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; @@ -31,7 +76,7 @@ pub trait GraphQLQuery<'de> { fn build_query(variables: Self::Variables) -> GraphQLQueryBody; } -/// 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 where @@ -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 { @@ -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 = 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 = 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. @@ -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, +/// # dogs: Vec, +/// # } +/// # +/// # fn main() -> Result<(), failure::Error> { +/// use graphql_client::GraphQLResponse; +/// +/// let body: GraphQLResponse = serde_json::from_value(json!({ +/// "data": { +/// "users": [{"id": 13}], +/// "dogs": [{"name": "Strelka"}], +/// }, +/// "errors": [], +/// }))?; +/// +/// let expected: GraphQLResponse = 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 { /// The absent, partial or complete response data. pub data: Option,