commit
b46951717d
50 changed files with 1668 additions and 1027 deletions
|
@ -8,10 +8,8 @@ rust:
|
|||
- nightly
|
||||
|
||||
# Prevent accidentally breaking older Rust versions
|
||||
- 1.17.0
|
||||
- 1.18.0
|
||||
- 1.19.0
|
||||
- 1.20.0
|
||||
- 1.21.0
|
||||
|
||||
env:
|
||||
global:
|
||||
|
|
|
@ -3,4 +3,6 @@ members = [
|
|||
"juniper",
|
||||
"juniper_codegen",
|
||||
"juniper_tests",
|
||||
"juniper_iron",
|
||||
"juniper_rocket",
|
||||
]
|
||||
|
|
4
Makefile.toml
Normal file
4
Makefile.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
[tasks.init]
|
||||
condition = { channels = ["beta", "stable"] }
|
||||
env = { "CARGO_MAKE_WORKSPACE_SKIP_MEMBERS" = "juniper_rocket;juniper_tests" }
|
180
README.md
180
README.md
|
@ -4,7 +4,7 @@
|
|||
> GraphQL server library for Rust
|
||||
|
||||
[![Build Status](https://travis-ci.org/graphql-rust/juniper.svg?branch=master)](https://travis-ci.org/graphql-rust/juniper)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/vsrwmsh9wobxugbs?svg=true)](https://ci.appveyor.com/project/theduke/juniper/branch/master)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/xav6tor6biu617uu?svg=true)](https://ci.appveyor.com/project/theduke/juniper)
|
||||
[![codecov](https://codecov.io/gh/graphql-rust/juniper/branch/master/graph/badge.svg)](https://codecov.io/gh/graphql-rust/juniper)
|
||||
[![Crates.io](https://img.shields.io/crates/v/juniper.svg?maxAge=2592000)](https://crates.io/crates/juniper)
|
||||
[![Gitter chat](https://badges.gitter.im/juniper-graphql/gitter.png)](https://gitter.im/juniper-graphql)
|
||||
|
@ -13,159 +13,87 @@
|
|||
---
|
||||
|
||||
[GraphQL][graphql] is a data query language developed by Facebook intended to
|
||||
serve mobile and web application frontends. Juniper makes it possible to write
|
||||
GraphQL servers in Rust that are type-safe and blazingly fast.
|
||||
serve mobile and web application frontends.
|
||||
|
||||
*Juniper* makes it possible to write GraphQL servers in Rust that are
|
||||
type-safe and blazingly fast. We also try to make declaring and resolving
|
||||
GraphQL schemas as convenient as possible as Rust will allow.
|
||||
|
||||
Juniper does not include a web server - instead it provides building blocks to
|
||||
make integration with existing servers straightforward. It optionally provides a
|
||||
pre-built integration for the [Iron][iron] and [Rocket] frameworks.
|
||||
pre-built integration for the [Iron][iron] and [Rocket] frameworks, including
|
||||
embedded [Graphiql][graphiql] for easy debugging.
|
||||
|
||||
* [Cargo crate](https://crates.io/crates/juniper)
|
||||
* [API Documentation](https://docs.rs/juniper)
|
||||
* [API Reference][docsrs]
|
||||
* [Book][book]: Guides and Examples
|
||||
|
||||
## Installation
|
||||
|
||||
Add Juniper to your Cargo.toml:
|
||||
## Getting Started
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper" }
|
||||
```
|
||||
The best place to get started is the [Juniper Book][book], which contains
|
||||
guides with plenty of examples, covering all features of Juniper. (very much WIP)
|
||||
|
||||
If you want Iron integration, you need to depend on the `juniper_iron` crate.
|
||||
feature flag:
|
||||
To get started quickly and get a feel for Juniper, check out the
|
||||
[Quickstart][book_quickstart] section.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper" }
|
||||
juniper_iron = { git = "https://github.com/graphql-rust/juniper_iron" }
|
||||
For specific information about macros, types and the Juniper api, the
|
||||
[API Reference][docsrs] is the best place to look.
|
||||
|
||||
```
|
||||
You can also check out [src/tests/schema.rs][test_schema_rs] to see a complex
|
||||
schema including polymorphism with traits and interfaces.
|
||||
For an example of web framework integration,
|
||||
see the [rocket][rocket_examples] and [iron][iron_examples] examples folders.
|
||||
|
||||
If you want Rocket integration, you need to depend on the `juniper_rocket` crate.
|
||||
|
||||
**Note**: Until 0.9 is released, you will need to use a Git dependency to the current master branch.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
juniper = { git = "https://github.com/graphql-rust/juniper" }
|
||||
juniper_rocket = { git = "https://github.com/graphql-rust/juniper_rocket" }
|
||||
```
|
||||
|
||||
## Building schemas
|
||||
|
||||
GraphQL turns the REST paradigm as it's usually implemented on its head: instead
|
||||
of providing a fixed structure of all types and relations in the system, GraphQL
|
||||
defines a _schema_ which your users can query. The schema defines all types,
|
||||
fields, and relations available, while the query defines which fields and
|
||||
relations a user is interested in.
|
||||
|
||||
Juniper expects you to already have the types you want to expose in GraphQL as
|
||||
Rust data types. Other than that, it doesn't make any assumptions whether they
|
||||
are stored in a database or just in memory. Exposing a type is a matter of
|
||||
implementing the `GraphQLType` for your type. To make things a bit easier,
|
||||
Juniper comes with a set of macros that help you do this, based on what kind of
|
||||
type you want to expose. Let's look at how one could expose parts of the [Star
|
||||
Wars Schema][swschema]:
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
|
||||
use juniper::FieldResult;
|
||||
|
||||
enum Episode {
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
struct Human {
|
||||
id: String,
|
||||
name: String,
|
||||
appears_in: Vec<Episode>,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
graphql_enum!(Episode {
|
||||
Episode::NewHope => "NEW_HOPE",
|
||||
Episode::Empire => "EMPIRE",
|
||||
Episode::Jedi => "JEDI",
|
||||
});
|
||||
|
||||
graphql_object!(Human: () |&self| {
|
||||
description: "A humanoid creature in the Star Wars universe"
|
||||
|
||||
// Field resolver methods look almost like ordinary methods. The macro picks
|
||||
// up arguments and return types for the introspection schema, and verifies
|
||||
// it during compilation.
|
||||
field id() -> FieldResult<&String> {
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
field name() -> FieldResult<&String> {
|
||||
Ok(&self.name)
|
||||
}
|
||||
|
||||
field appears_in() -> FieldResult<&Vec<Episode>> {
|
||||
Ok(&self.appears_in)
|
||||
}
|
||||
|
||||
field home_planet() -> FieldResult<&String> {
|
||||
Ok(&self.home_planet)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can find the full example in [src/tests/schema.rs][test_schema_rs],
|
||||
including polymorphism with traits and interfaces. For an example of framework
|
||||
integration, see the [rocket][rocket_examples] and [iron][iron_examples] examples folders.
|
||||
|
||||
## Features
|
||||
|
||||
Juniper supports the full GraphQL query language according to the
|
||||
[specification][graphql_spec], including the introspective schema and all
|
||||
validations. It does not, however, support the schema language.
|
||||
[specification][graphql_spec], including interfaces, unions, schema
|
||||
introspection, and validations.
|
||||
It does not, however, support the schema language.
|
||||
|
||||
As an exception to other GraphQL libraries for other languages, Juniper builds
|
||||
non-null types by default. A field of type `Vec<Episode>` will be converted into
|
||||
`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be
|
||||
`Option<Vec<Option<Episode>>>`.
|
||||
|
||||
## Integrations
|
||||
|
||||
### Data types
|
||||
|
||||
Juniper has automatic integration with some very common Rust crates to make
|
||||
building schemas a breeze. The types from these crates will be usable in
|
||||
your Schemas automatically.
|
||||
|
||||
* [uuid][uuid]
|
||||
* [url][url]
|
||||
* [chrono][chrono]
|
||||
|
||||
### Web Frameworks
|
||||
|
||||
* [rocket][rocket]
|
||||
* [iron][iron]
|
||||
|
||||
|
||||
## API Stability
|
||||
|
||||
Juniper has not reached 1.0 yet, thus some API instability should be expected.
|
||||
|
||||
## 1.0 Roadmap
|
||||
|
||||
> Version 0.8.1 probably be re-released as 1.0 to indicate API stability.
|
||||
|
||||
The road to 1.0 _focuses_ on two aspects: making sure the API hasn't got any
|
||||
obvious dead-ends with respect to probable future features, and improving test
|
||||
coverage for general execution. There are some chores that need to be completed
|
||||
as well.
|
||||
|
||||
* [X] Extensive execution testing
|
||||
* [X] Sending input objects and partial input objects in variables
|
||||
* [X] Sending enums in variables
|
||||
* [X] General input value type checking and validation
|
||||
* [X] Improve helper macros
|
||||
* [X] `graphql_union!` helper completely missing
|
||||
* [X] `graphql_input_object!` helper completely missing
|
||||
* [X] Add support for deprecating things
|
||||
* [X] Custom enum values and descriptions
|
||||
* [X] Improved syntax for fields that can't fail resolution - make
|
||||
`FieldResult<T>` optional maybe?
|
||||
* [X] Investigate asynchronous execution - implementing it is not necessary, but
|
||||
at least look at what API changes will be needed for us to hook into
|
||||
[Tokio][tokio], for example.
|
||||
* [X] Larger examples to illustrate things like database access
|
||||
|
||||
[graphql]: http://graphql.org
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[iron]: http://ironframework.io
|
||||
[swschema]: http://graphql.org/docs/typesystem/
|
||||
[graphql_spec]: http://facebook.github.io/graphql
|
||||
[test_schema_rs]: juniper/src/tests/schema.rs
|
||||
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/src/tests/schema.rs
|
||||
[tokio]: https://github.com/tokio-rs/tokio
|
||||
[rocket_examples]: https://github.com/graphql-rust/juniper_rocket/tree/master/examples
|
||||
[iron_examples]: https://github.com/graphql-rust/juniper_iron/tree/master/examples
|
||||
[rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples
|
||||
[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples
|
||||
[Rocket]: https://rocket.rs
|
||||
[book]: https://graphql-rust.github.io
|
||||
[book_quickstart]: https://graphql-rust.github.io/quickstart.html
|
||||
[docsrs]: https://docs.rs/juniper
|
||||
|
||||
[uuid]: https://crates.io/crates/uuid
|
||||
[url]: https://crates.io/crates/url
|
||||
[chrono]: https://crates.io/crates/chrono
|
||||
|
||||
|
|
19
appveyor.yml
19
appveyor.yml
|
@ -5,28 +5,16 @@
|
|||
environment:
|
||||
matrix:
|
||||
# Stable channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: stable
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: stable
|
||||
# Beta channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: beta
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: beta
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: beta
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: beta
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
|
@ -49,4 +37,9 @@ install:
|
|||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo make ci-flow
|
||||
- cd juniper && cargo build --verbose && cargo test --verbose && cd ..
|
||||
- cd juniper_codegen && cargo build --verbose && cargo test --verbose && cd ..
|
||||
- cd juniper_tests && cargo build --verbose && cargo test --verbose && cd ..
|
||||
- cd juniper_iron && cargo build --verbose && cargo test --verbose && cd ..
|
||||
- IF NOT %TARGET% == %TARGET:msvc=% ( IF %CHANNEL% == "nightly" ( cd juniper_rocket && cargo test --verbose && cargo build --verbose && cd .. ) )
|
||||
|
||||
|
|
89
changelog/0.9.0.md
Normal file
89
changelog/0.9.0.md
Normal file
|
@ -0,0 +1,89 @@
|
|||
# [0.9.0] 2017-12-03
|
||||
|
||||
## Changes
|
||||
|
||||
This is the first release in a long time.
|
||||
Quite a few changes have accumulated since `0.8.1`, including multiple breaking
|
||||
changes.
|
||||
|
||||
### Custom derive & macros
|
||||
|
||||
Juniper has gained custom derive implementations for input objects, objects and
|
||||
enums.
|
||||
|
||||
* `#[derive(GraphQLInputObject)]`
|
||||
* `#[derive(GraphQLEnum)]`
|
||||
* `#[derive(GraphQLObject)]`
|
||||
|
||||
The `graphql_enum!` and `graphql_input_object!` macros did not provide any more
|
||||
benefits, so they have been removed!
|
||||
All functionality is now covered by custom derive.
|
||||
Check the [docs](https://graphql-rust.github.io) to find out more.
|
||||
|
||||
### Web framework integrations - Iron & Rocket
|
||||
|
||||
The iron and rocket integrations were removed from the main crate, and are now
|
||||
available via the [juniper_iron](https://crates.io/crates/juniper_iron) and
|
||||
[juniper_rocket](https://crates.io/crates/juniper_rocket) crates.
|
||||
|
||||
### FieldError - Custom data
|
||||
|
||||
The `FieldError` type now supports custom data with the `Value` type from
|
||||
serde_json. Use this to populate the `data` field in returned errors.
|
||||
|
||||
### Dynamic Schemas
|
||||
|
||||
Juniper has gained support for dynamic schemas, thanks to @srijs.
|
||||
|
||||
That also means the type of `RootNode` has changed to include a lifetime.
|
||||
|
||||
The repository was restructured to a multi crate workspace to enable several new
|
||||
features like custom_derive and an extracted parser.
|
||||
|
||||
#[#66](https://github.com/graphql-rust/juniper/pull/66)
|
||||
|
||||
### Data Type Integrations
|
||||
|
||||
Integrations with multiple popular crates was added to make working with them
|
||||
easier.
|
||||
|
||||
* uuid
|
||||
* url
|
||||
* chrono
|
||||
|
||||
### Field Order
|
||||
|
||||
To better comply with the specification, order of requested fields is
|
||||
now preserved.
|
||||
|
||||
[#82](https://github.com/graphql-rust/juniper/issues/82
|
||||
|
||||
### From/ToInputValue
|
||||
|
||||
The `::from` and `::to` methods in `From/ToInputValue` were renamed to
|
||||
`from/to_input_value()` to not conflict with other methods.
|
||||
|
||||
[#90](https://github.com/graphql-rust/juniper/pull/90)
|
||||
|
||||
### Other changes
|
||||
|
||||
* Several small performance improvements
|
||||
* Use [fnv](https://github.com/servo/rust-fnv) hash map for better performance
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
A big shoutout to the many contributors for this version, sorted alphabetically.
|
||||
|
||||
* Cameron Eldridge <cameldridge+git@gmail.com>
|
||||
* Christian Legnitto <christian@legnitto.com>
|
||||
* Jacob Haslehurst <jacob@haslehurst.net>
|
||||
* Jane Keibler <wanderingelf14@gmail.com>
|
||||
* Magnus Hallin <mhallin@fastmail.com>
|
||||
* rushmorem <rushmore@webenchanter.com>
|
||||
* Rushmore Mushambi <rushmore@webenchanter.com>
|
||||
* Sagie Gur-Ari <sagiegurari@gmail.com>
|
||||
* Sam Rijs <srijs@airpost.net>
|
||||
* Stanko Krtalić <stanko.krtalic@gmail.com>
|
||||
* theduke <chris@theduke.at>
|
||||
* thomas-jeepe <penguinSoccer@outlook.com>
|
|
@ -1,22 +1,6 @@
|
|||
Change log
|
||||
==========
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
The repository was restructured to a multi crate workspace to enable several new features like custom_derive and an extracted parser.
|
||||
|
||||
### New features
|
||||
|
||||
* New juniper_codegen crate which provides custom derives:
|
||||
* `#[derive(GraphQLInputObject)]`
|
||||
* `#[derive(GraphQLEnum)]`
|
||||
* `#[derive(GraphQLObject)]`
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* To better comply with the specification, order of requested fields is
|
||||
now preserved.
|
||||
([#82](https://github.com/graphql-rust/juniper/issues/82)
|
||||
|
||||
## [0.8.1] – 2017-06-15
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "juniper"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
|
||||
description = "GraphQL server library"
|
||||
license = "BSD-2-Clause"
|
||||
|
@ -22,18 +22,25 @@ path = "benches/bench.rs"
|
|||
[features]
|
||||
nightly = []
|
||||
expose-test-schema = []
|
||||
default = ["chrono", "url", "uuid"]
|
||||
default = [
|
||||
"chrono",
|
||||
"url",
|
||||
"uuid"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
juniper_codegen = { version = "0.9.0", path = "../juniper_codegen" }
|
||||
|
||||
fnv = "1.0.3"
|
||||
chrono = { version = "^0.4.0", optional = true }
|
||||
ordermap = { version = "^0.2.11", features = ["serde-1"] }
|
||||
serde = { version = "^1.0.8" }
|
||||
serde_derive = {version="^1.0.8" }
|
||||
serde_json = { version="^1.0.2", optional = true }
|
||||
url = { version = "^1.5.1", optional = true }
|
||||
ordermap = { version = "0.2.11", features = ["serde-1"] }
|
||||
serde = { version = "1.0.8" }
|
||||
serde_derive = {version="1.0.2" }
|
||||
|
||||
chrono = { version = "0.4.0", optional = true }
|
||||
serde_json = { version="1.0.2", optional = true }
|
||||
url = { version = "1.5.1", optional = true }
|
||||
uuid = { version = "0.5.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bencher = "^0.1.2"
|
||||
serde_json = { version = "^1.0.2" }
|
||||
bencher = "0.1.2"
|
||||
serde_json = { version = "1.0.2" }
|
||||
|
|
|
@ -150,9 +150,9 @@ pub type Document<'a> = Vec<Definition<'a>>;
|
|||
/// Parse an unstructured input value into a Rust data type.
|
||||
///
|
||||
/// The conversion _can_ fail, and must in that case return None. Implemented
|
||||
/// automatically by the convenience macros `graphql_enum!` and
|
||||
/// `graphql_scalar!`. Must be implemented manually when manually exposing new
|
||||
/// enums or scalars.
|
||||
/// automatically by the convenience macro `graphql_scalar!` or by deriving GraphQLEnum.
|
||||
///
|
||||
/// Must be implemented manually when manually exposing new enums or scalars.
|
||||
pub trait FromInputValue: Sized {
|
||||
/// Performs the conversion.
|
||||
fn from_input_value(v: &InputValue) -> Option<Self>;
|
||||
|
|
|
@ -9,7 +9,8 @@ use validation::RuleError;
|
|||
use parser::SourcePosition;
|
||||
use types::scalars::EmptyMutation;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLEnum, Debug)]
|
||||
#[graphql(_internal)]
|
||||
enum Color {
|
||||
Red,
|
||||
Green,
|
||||
|
@ -17,12 +18,6 @@ enum Color {
|
|||
}
|
||||
struct TestType;
|
||||
|
||||
graphql_enum!(Color {
|
||||
Color::Red => "RED",
|
||||
Color::Green => "GREEN",
|
||||
Color::Blue => "BLUE",
|
||||
});
|
||||
|
||||
graphql_object!(TestType: () |&self| {
|
||||
field to_string(color: Color) -> String {
|
||||
format!("Color::{:?}", color)
|
||||
|
|
|
@ -5,34 +5,6 @@ use value::Value;
|
|||
use schema::model::RootNode;
|
||||
use types::scalars::EmptyMutation;
|
||||
|
||||
|
||||
enum DefaultName {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
enum Named {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
enum NoTrailingComma {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
enum EnumDescription {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
enum EnumValueDescription {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
enum EnumDeprecation {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
|
||||
struct Root;
|
||||
|
||||
/*
|
||||
|
||||
Syntax to validate:
|
||||
|
@ -45,37 +17,53 @@ Syntax to validate:
|
|||
|
||||
*/
|
||||
|
||||
graphql_enum!(DefaultName {
|
||||
DefaultName::Foo => "FOO",
|
||||
DefaultName::Bar => "BAR",
|
||||
});
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(_internal)]
|
||||
enum DefaultName {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
|
||||
graphql_enum!(Named as "ANamedEnum" {
|
||||
Named::Foo => "FOO",
|
||||
Named::Bar => "BAR",
|
||||
});
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(name = "ANamedEnum", _internal)]
|
||||
enum Named {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
|
||||
graphql_enum!(NoTrailingComma {
|
||||
NoTrailingComma::Foo => "FOO",
|
||||
NoTrailingComma::Bar => "BAR"
|
||||
});
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(_internal)]
|
||||
enum NoTrailingComma {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
|
||||
graphql_enum!(EnumDescription {
|
||||
description: "A description of the enum itself"
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(description = "A description of the enum itself", _internal)]
|
||||
enum EnumDescription {
|
||||
Foo,
|
||||
Bar,
|
||||
}
|
||||
|
||||
EnumDescription::Foo => "FOO",
|
||||
EnumDescription::Bar => "BAR",
|
||||
});
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(_internal)]
|
||||
enum EnumValueDescription {
|
||||
#[graphql(description = "The FOO value")]
|
||||
Foo,
|
||||
#[graphql(description = "The BAR value")]
|
||||
Bar,
|
||||
}
|
||||
|
||||
graphql_enum!(EnumValueDescription {
|
||||
EnumValueDescription::Foo => "FOO" as "The FOO value",
|
||||
EnumValueDescription::Bar => "BAR" as "The BAR value",
|
||||
});
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(_internal)]
|
||||
enum EnumDeprecation {
|
||||
#[graphql(deprecated = "Please don't use FOO any more")]
|
||||
Foo,
|
||||
#[graphql(description = "The BAR value", deprecated = "Please don't use BAR any more")]
|
||||
Bar,
|
||||
}
|
||||
|
||||
graphql_enum!(EnumDeprecation {
|
||||
EnumDeprecation::Foo => "FOO" deprecated "Please don't use FOO any more",
|
||||
EnumDeprecation::Bar => "BAR" as "The BAR value" deprecated "Please don't use BAR any more",
|
||||
});
|
||||
struct Root;
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field default_name() -> DefaultName { DefaultName::Foo }
|
|
@ -8,82 +8,81 @@ use types::scalars::EmptyMutation;
|
|||
|
||||
struct Root;
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(GraphQLInputObject)]
|
||||
#[graphql(_internal)]
|
||||
struct DefaultName {
|
||||
field_one: String,
|
||||
field_two: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(GraphQLInputObject)]
|
||||
#[graphql(_internal)]
|
||||
struct NoTrailingComma {
|
||||
field_one: String,
|
||||
field_two: String
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct Derive {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
struct Named as "ANamedInputObject" {
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(name = "ANamedInputObject", _internal)]
|
||||
struct Named {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
description: "Description for the input object"
|
||||
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(description = "Description for the input object", _internal)]
|
||||
struct Description {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
pub struct Public {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
description: "Description for the input object"
|
||||
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(description = "Description for the input object", _internal)]
|
||||
pub struct PublicWithDescription {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
description: "Description for the input object"
|
||||
|
||||
pub struct NamedPublicWithDescription as "APublicNamedInputObjectWithDescription" {
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(name = "APublicNamedInputObjectWithDescription",
|
||||
description = "Description for the input object",
|
||||
_internal)]
|
||||
pub struct NamedPublicWithDescription {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
pub struct NamedPublic as "APublicNamedInputObject" {
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(name = "APublicNamedInputObject", _internal)]
|
||||
pub struct NamedPublic {
|
||||
field_one: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct FieldDescription {
|
||||
field_one: String as "The first field",
|
||||
field_two: String as "The second field",
|
||||
#[graphql(description = "The first field")]
|
||||
field_one: String,
|
||||
#[graphql(description = "The second field")]
|
||||
field_two: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct FieldWithDefaults {
|
||||
field_one = 123: i32,
|
||||
field_two = 456: i32 as "The second field",
|
||||
#[graphql(default = "123")]
|
||||
field_one: i32,
|
||||
#[graphql(default = "456", description = "The second field")]
|
||||
field_two: i32,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field test_field(
|
|
@ -1,8 +1,17 @@
|
|||
mod input_object;
|
||||
mod enums;
|
||||
|
||||
// This asserts that the input objects defined public actually became public
|
||||
#[allow(unused_imports)]
|
||||
use self::input_object::{NamedPublic, NamedPublicWithDescription};
|
||||
|
||||
use executor::Variables;
|
||||
use value::Value;
|
||||
use schema::model::RootNode;
|
||||
use types::scalars::EmptyMutation;
|
||||
|
||||
#[derive(GraphQLEnum)]
|
||||
#[graphql(name = "SampleEnum", _internal)]
|
||||
enum Sample {
|
||||
One,
|
||||
Two,
|
||||
|
@ -24,11 +33,6 @@ graphql_scalar!(Scalar as "SampleScalar" {
|
|||
}
|
||||
});
|
||||
|
||||
graphql_enum!(Sample as "SampleEnum" {
|
||||
Sample::One => "ONE",
|
||||
Sample::Two => "TWO",
|
||||
});
|
||||
|
||||
graphql_interface!(Interface: () as "SampleInterface" |&self| {
|
||||
description: "A sample interface"
|
||||
|
|
@ -30,39 +30,35 @@ graphql_scalar!(TestComplexScalar {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct TestInputObject {
|
||||
a: Option<String>,
|
||||
b: Option<Vec<Option<String>>>,
|
||||
c: String,
|
||||
d: Option<TestComplexScalar>,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct TestNestedInputObject {
|
||||
na: TestInputObject,
|
||||
nb: String,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct ExampleInputObject {
|
||||
a: Option<String>,
|
||||
b: i32,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLInputObject, Debug)]
|
||||
#[graphql(_internal)]
|
||||
struct InputWithDefaults {
|
||||
a = 123: i32,
|
||||
#[graphql(default = "123")]
|
||||
a: i32,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_object!(TestType: () |&self| {
|
||||
field field_with_object_input(input: Option<TestInputObject>) -> String {
|
||||
|
|
|
@ -2,117 +2,90 @@
|
|||
|
||||
# GraphQL
|
||||
|
||||
[GraphQL][1] is a data query language developed by Facebook intended to serve
|
||||
mobile and web application frontends. A server provides a schema, containing
|
||||
types and fields that applications can query. Queries are hierarchical,
|
||||
composable, and statically typed. Schemas are introspective, which lets clients
|
||||
statically verify their queries against a server without actually executing
|
||||
them.
|
||||
[GraphQL][graphql] is a data query language developed by Facebook intended to
|
||||
serve mobile and web application frontends.
|
||||
|
||||
This library provides data types and traits to expose Rust types in a GraphQL
|
||||
schema, as well as an optional integration into the [Iron framework][Iron] and
|
||||
[Rocket]. It tries to keep the number of dynamic operations to a minimum, and
|
||||
give you as the schema developer the control of the query execution path.
|
||||
*Juniper* makes it possible to write GraphQL servers in Rust that are
|
||||
type-safe and blazingly fast. We also try to make declaring and resolving
|
||||
GraphQL schemas as convenient as possible as Rust will allow.
|
||||
|
||||
Juniper only depends on `serde` and `serde_derive` by default, making it
|
||||
lightweight and easy to drop into any project.
|
||||
Juniper does not include a web server - instead it provides building blocks to
|
||||
make integration with existing servers straightforward. It optionally provides a
|
||||
pre-built integration for the [Iron][iron] and [Rocket] frameworks, including
|
||||
embedded [Graphiql][graphiql] for easy debugging.
|
||||
|
||||
## Exposing data types
|
||||
* [Cargo crate](https://crates.io/crates/juniper)
|
||||
* [API Reference][docsrs]
|
||||
* [Book][book]: Guides and Examples
|
||||
|
||||
The `GraphQLType` trait is the primary interface towards application developers.
|
||||
By deriving this trait, you can expose your types as either objects, enums,
|
||||
interfaces, unions, or scalars.
|
||||
|
||||
However, due to the dynamic nature of GraphQL's type system, deriving this trait
|
||||
manually is a bit tedious, especially in order to do it in a fully type safe
|
||||
manner. To help with this task, this library provides a couple of macros; the
|
||||
most common one being `graphql_object!`. Use this macro to expose your already
|
||||
existing object types as GraphQL objects:
|
||||
## Getting Started
|
||||
|
||||
```rust
|
||||
#[macro_use] extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
use juniper::{Context, FieldResult};
|
||||
The best place to get started is the [Juniper Book][book], which contains
|
||||
guides with plenty of examples, covering all features of Juniper.
|
||||
|
||||
struct User { id: String, name: String, friend_ids: Vec<String> }
|
||||
struct QueryRoot;
|
||||
struct Database { users: HashMap<String, User> }
|
||||
To get started quickly and get a feel for Juniper, check out the
|
||||
[Quickstart][book_quickstart] section.
|
||||
|
||||
impl Context for Database {}
|
||||
For specific information about macros, types and the Juniper api, the
|
||||
[API Reference][docsrs] is the best place to look.
|
||||
|
||||
// GraphQL objects can access a "context object" during execution. Use this
|
||||
// object to provide e.g. database access to the field accessors. This object
|
||||
// must implement the `Context` trait. If you don't need a context, use the
|
||||
// empty tuple `()` to indicate this.
|
||||
//
|
||||
// In this example, we use the Database struct as our context.
|
||||
graphql_object!(User: Database |&self| {
|
||||
You can also check out [src/tests/schema.rs][test_schema_rs] to see a complex
|
||||
schema including polymorphism with traits and interfaces.
|
||||
For an example of web framework integration,
|
||||
see the [rocket][rocket_examples] and [iron][iron_examples] examples folders.
|
||||
|
||||
// Expose a simple field as a GraphQL string.
|
||||
field id() -> &String {
|
||||
&self.id
|
||||
}
|
||||
|
||||
field name() -> &String {
|
||||
&self.name
|
||||
}
|
||||
## Features
|
||||
|
||||
// FieldResult<T> is an alias for Result<T, FieldError>, which can be
|
||||
// converted to from anything that implements std::fmt::Display - simply
|
||||
// return an error with a string using the ? operator from this method and
|
||||
// it will be correctly inserted into the execution response.
|
||||
field secret() -> FieldResult<&String> {
|
||||
Err("Can't touch this".to_owned())?
|
||||
}
|
||||
Juniper supports the full GraphQL query language according to the
|
||||
[specification][graphql_spec], including interfaces, unions, schema
|
||||
introspection, and validations.
|
||||
It does not, however, support the schema language.
|
||||
|
||||
// Field accessors can optionally take an "executor" as their first
|
||||
// argument. This object can help guide query execution and provides
|
||||
// access to the context instance.
|
||||
//
|
||||
// In this example, the context is used to convert the friend_ids array
|
||||
// into actual User objects.
|
||||
field friends(&executor) -> Vec<&User> {
|
||||
self.friend_ids.iter()
|
||||
.filter_map(|id| executor.context().users.get(id))
|
||||
.collect()
|
||||
}
|
||||
});
|
||||
As an exception to other GraphQL libraries for other languages, Juniper builds
|
||||
non-null types by default. A field of type `Vec<Episode>` will be converted into
|
||||
`[Episode!]!`. The corresponding Rust type for e.g. `[Episode]` would be
|
||||
`Option<Vec<Option<Episode>>>`.
|
||||
|
||||
// The context object is passed down to all referenced types - all your exposed
|
||||
// types need to have the same context type.
|
||||
graphql_object!(QueryRoot: Database |&self| {
|
||||
## Integrations
|
||||
|
||||
// Arguments work just like they do on functions.
|
||||
field user(&executor, id: String) -> Option<&User> {
|
||||
executor.context().users.get(&id)
|
||||
}
|
||||
});
|
||||
### Data types
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
Juniper has automatic integration with some very common Rust crates to make
|
||||
building schemas a breeze. The types from these crates will be usable in
|
||||
your Schemas automatically.
|
||||
|
||||
Adding per type, field, and argument documentation is possible directly from
|
||||
this macro. For more in-depth information on how to expose fields and types, see
|
||||
the [`graphql_object!`][3] macro.
|
||||
* [uuid][uuid]
|
||||
* [url][url]
|
||||
* [chrono][chrono]
|
||||
|
||||
### Built-in object type integrations
|
||||
### Web Frameworks
|
||||
|
||||
Juniper has [built-in integrations][object_integrations] for converting existing object types to
|
||||
GraphQL objects for popular crates.
|
||||
* [rocket][rocket]
|
||||
* [iron][iron]
|
||||
|
||||
## Integrating with web servers
|
||||
|
||||
The most obvious usecase is to expose the GraphQL schema over an HTTP endpoint.
|
||||
To support this, Juniper offers additional crates that integrate with popular web frameworks.
|
||||
## API Stability
|
||||
|
||||
* [juniper_iron][juniper_iron]: Handlers for [Iron][Iron]
|
||||
* [juniper_rocket][juniper_rocket]: Handlers for [Rocket][Rocket]
|
||||
Juniper has not reached 1.0 yet, thus some API instability should be expected.
|
||||
|
||||
[1]: http://graphql.org
|
||||
[3]: macro.graphql_object!.html
|
||||
[Iron]: http://ironframework.io
|
||||
[graphql]: http://graphql.org
|
||||
[graphiql]: https://github.com/graphql/graphiql
|
||||
[iron]: http://ironframework.io
|
||||
[graphql_spec]: http://facebook.github.io/graphql
|
||||
[test_schema_rs]: https://github.com/graphql-rust/juniper/blob/master/juniper/src/tests/schema.rs
|
||||
[tokio]: https://github.com/tokio-rs/tokio
|
||||
[rocket_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_rocket/examples
|
||||
[iron_examples]: https://github.com/graphql-rust/juniper/tree/master/juniper_iron/examples
|
||||
[Rocket]: https://rocket.rs
|
||||
[object_integrations]: integrations/index.html
|
||||
[book]: https://graphql-rust.github.io/
|
||||
[book_quickstart]: https://graphql-rust.github.io/quickstart.html
|
||||
[docsrs]: https://docs.rs/juniper
|
||||
|
||||
[uuid]: https://crates.io/crates/uuid
|
||||
[url]: https://crates.io/crates/url
|
||||
[chrono]: https://crates.io/crates/chrono
|
||||
|
||||
*/
|
||||
#![warn(missing_docs)]
|
||||
|
@ -136,7 +109,13 @@ extern crate url;
|
|||
#[cfg(any(test, feature = "uuid"))]
|
||||
extern crate uuid;
|
||||
|
||||
use std::borrow::Cow;
|
||||
// Depend on juniper_codegen and re-export everything in it.
|
||||
// This allows users to just depend on juniper and get the derive functionality automatically.
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate juniper_codegen;
|
||||
#[doc(hidden)]
|
||||
pub use juniper_codegen::*;
|
||||
|
||||
#[macro_use]
|
||||
mod value;
|
||||
|
@ -147,6 +126,7 @@ pub mod parser;
|
|||
mod types;
|
||||
mod schema;
|
||||
mod validation;
|
||||
mod util;
|
||||
mod executor;
|
||||
// This needs to be public until docs have support for private modules:
|
||||
// https://github.com/rust-lang/cargo/issues/1520
|
||||
|
@ -164,6 +144,9 @@ pub mod tests;
|
|||
#[cfg(test)]
|
||||
mod executor_tests;
|
||||
|
||||
// Needs to be public because macros use it.
|
||||
pub use util::to_camel_case;
|
||||
|
||||
use parser::{parse_document_source, ParseError, Spanning};
|
||||
use validation::{validate_input_values, visit_all_rules, ValidatorContext};
|
||||
use executor::execute_validated_query;
|
||||
|
@ -232,40 +215,4 @@ impl<'a> From<Spanning<ParseError<'a>>> for GraphQLError<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn to_camel_case<'a>(s: &'a str) -> Cow<'a, str> {
|
||||
let mut dest = Cow::Borrowed(s);
|
||||
|
||||
for (i, part) in s.split('_').enumerate() {
|
||||
if i > 0 && part.len() == 1 {
|
||||
dest += Cow::Owned(part.to_uppercase());
|
||||
} else if i > 0 && part.len() > 1 {
|
||||
let first = part.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_uppercase()
|
||||
.collect::<String>();
|
||||
let second = &part[1..];
|
||||
|
||||
dest += Cow::Owned(first);
|
||||
dest += second;
|
||||
} else if i == 0 {
|
||||
dest = Cow::Borrowed(part);
|
||||
}
|
||||
}
|
||||
|
||||
dest
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_camel_case() {
|
||||
assert_eq!(&to_camel_case("test")[..], "test");
|
||||
assert_eq!(&to_camel_case("_test")[..], "Test");
|
||||
assert_eq!(&to_camel_case("first_second")[..], "firstSecond");
|
||||
assert_eq!(&to_camel_case("first_")[..], "first");
|
||||
assert_eq!(&to_camel_case("a_b_c")[..], "aBC");
|
||||
assert_eq!(&to_camel_case("a_bc")[..], "aBc");
|
||||
assert_eq!(&to_camel_case("a_b")[..], "aB");
|
||||
assert_eq!(&to_camel_case("a")[..], "a");
|
||||
assert_eq!(&to_camel_case("")[..], "");
|
||||
}
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
/**
|
||||
Expose simple enums
|
||||
|
||||
GraphQL enums are similar to enums classes C++ - more like grouped constants
|
||||
with type safety than what Rust enums offer. This macro can be used to export
|
||||
non-data carrying Rust enums to GraphQL:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
enum Color {
|
||||
Red,
|
||||
Orange,
|
||||
Green,
|
||||
Blue,
|
||||
Black,
|
||||
}
|
||||
|
||||
graphql_enum!(Color {
|
||||
Color::Red => "RED" as "The color red",
|
||||
Color::Orange => "ORANGE",
|
||||
Color::Green => "GREEN",
|
||||
Color::Blue => "BLUE",
|
||||
Color::Black => "BLACK" deprecated "Superseded by ORANGE",
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
The macro expands to a `match` statement which will result in a compilation
|
||||
error if not all enum variants are covered. It also creates an implementation
|
||||
for `FromInputValue` and `ToInputValue`, making it usable in arguments and
|
||||
default values.
|
||||
|
||||
If you want to expose the enum under a different name than the Rust type,
|
||||
you can write `graphql_enum!(Color as "MyColor" { ...`.
|
||||
|
||||
*/
|
||||
#[macro_export]
|
||||
macro_rules! graphql_enum {
|
||||
( @as_expr, $e:expr) => { $e };
|
||||
( @as_pattern, $p:pat) => { $p };
|
||||
( @as_path, $p:path) => { $p };
|
||||
|
||||
// Calls $val.$func($arg) if $arg is not None
|
||||
( @maybe_apply, None, $func:ident, $val:expr ) => { $val };
|
||||
( @maybe_apply, $arg:tt, $func:ident, $val:expr ) => { $val.$func($arg) };
|
||||
|
||||
// Each of the @parse match arms accumulates data up to a call to @generate.
|
||||
//
|
||||
// ( $name, $outname, $descr ): the name of the Rust enum, the name of the
|
||||
// GraphQL enum (as a string), and the description of the enum (as a string or None)
|
||||
//
|
||||
// [ ( $eval, $ename, $edescr, $edepr ) , ] the value of the Rust enum,
|
||||
// the value of the GraphQL enum (as a string), the description of the enum
|
||||
// value (as a string or None), and the deprecation reason of the enum value
|
||||
// (as a string or None).
|
||||
(
|
||||
@generate,
|
||||
( $name:path, $outname:tt, $descr:tt ),
|
||||
[ $( ( $eval:tt, $ename:tt, $edescr:tt, $edepr:tt ) , )* ]
|
||||
) => {
|
||||
impl $crate::GraphQLType for $name {
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
fn name(_: &()) -> Option<&str> {
|
||||
Some(graphql_enum!(@as_expr, $outname))
|
||||
}
|
||||
|
||||
fn meta<'r>(info: &(), registry: &mut $crate::Registry<'r>) -> $crate::meta::MetaType<'r> {
|
||||
graphql_enum!(
|
||||
@maybe_apply, $descr, description,
|
||||
registry.build_enum_type::<$name>(info, &[
|
||||
$(
|
||||
graphql_enum!(
|
||||
@maybe_apply,
|
||||
$edepr, deprecated,
|
||||
graphql_enum!(
|
||||
@maybe_apply,
|
||||
$edescr, description,
|
||||
$crate::meta::EnumValue::new(graphql_enum!(@as_expr, $ename))))
|
||||
),*
|
||||
]))
|
||||
.into_meta()
|
||||
}
|
||||
|
||||
fn resolve(&self, _: &(), _: Option<&[$crate::Selection]>, _: &$crate::Executor<Self::Context>) -> $crate::Value {
|
||||
match *self {
|
||||
$(
|
||||
graphql_enum!(@as_pattern, $eval) =>
|
||||
$crate::Value::string(graphql_enum!(@as_expr, $ename)) ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::FromInputValue for $name {
|
||||
fn from_input_value(v: &$crate::InputValue) -> Option<$name> {
|
||||
match v.as_enum_value().or_else(|| v.as_string_value()) {
|
||||
$(
|
||||
Some(graphql_enum!(@as_pattern, $ename))
|
||||
=> Some(graphql_enum!(@as_expr, $eval)), )*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::ToInputValue for $name {
|
||||
fn to_input_value(&self) -> $crate::InputValue {
|
||||
match *self {
|
||||
$(
|
||||
graphql_enum!(@as_pattern, $eval) =>
|
||||
$crate::InputValue::string(graphql_enum!(@as_expr, $ename)) ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// No more items to parse
|
||||
( @parse, $meta:tt, $acc:tt, ) => {
|
||||
graphql_enum!( @generate, $meta, $acc );
|
||||
};
|
||||
|
||||
// Remove extraneous commas
|
||||
( @parse, $meta:tt, $acc:tt, , $($rest:tt)* ) => {
|
||||
graphql_enum!( @parse, $meta, $acc, $($rest)* );
|
||||
};
|
||||
|
||||
// description: <description>
|
||||
(
|
||||
@parse,
|
||||
( $name:tt, $outname:tt, $_ignore:tt ),
|
||||
$acc:tt,
|
||||
description: $descr:tt $($items:tt)*
|
||||
) => {
|
||||
graphql_enum!( @parse, ( $name, $outname, $descr ), $acc, $($items)* );
|
||||
};
|
||||
|
||||
// RustEnumValue => "GraphQL enum value" deprecated <reason>
|
||||
(
|
||||
@parse,
|
||||
$meta:tt,
|
||||
[ $($acc:tt ,)* ],
|
||||
$eval:path => $ename:tt deprecated $depr:tt $($rest:tt)*
|
||||
) => {
|
||||
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval, $ename, None, $depr ), ], $($rest)* );
|
||||
};
|
||||
|
||||
// RustEnumValue => "GraphQL enum value" as <description> deprecated <reason>
|
||||
(
|
||||
@parse,
|
||||
$meta:tt,
|
||||
[ $($acc:tt ,)* ],
|
||||
$eval:path => $ename:tt as $descr:tt deprecated $depr:tt $($rest:tt)*
|
||||
) => {
|
||||
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval, $ename, $descr, $depr ), ], $($rest)* );
|
||||
};
|
||||
|
||||
// RustEnumValue => "GraphQL enum value" as <description>
|
||||
(
|
||||
@parse,
|
||||
$meta:tt,
|
||||
[ $($acc:tt ,)* ],
|
||||
$eval:path => $ename:tt as $descr:tt $($rest:tt)*
|
||||
) => {
|
||||
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval, $ename, $descr, None ), ], $($rest)* );
|
||||
};
|
||||
|
||||
// RustEnumValue => "GraphQL enum value"
|
||||
(
|
||||
@parse,
|
||||
$meta:tt,
|
||||
[ $($acc:tt ,)* ],
|
||||
$eval:path => $ename:tt $($rest:tt)*
|
||||
) => {
|
||||
graphql_enum!( @parse, $meta, [ $($acc ,)* ( $eval , $ename , None , None ), ], $($rest)* );
|
||||
};
|
||||
|
||||
// Entry point:
|
||||
// RustEnumName as "GraphQLEnumName" { ... }
|
||||
(
|
||||
$name:path as $outname:tt { $($items:tt)* }
|
||||
) => {
|
||||
graphql_enum!( @parse, ( $name, $outname, None ), [ ], $($items)* );
|
||||
};
|
||||
|
||||
// Entry point
|
||||
// RustEnumName { ... }
|
||||
(
|
||||
$name:path { $($items:tt)* }
|
||||
) => {
|
||||
graphql_enum!( @parse, ( $name, (stringify!($name)), None ), [ ], $($items)* );
|
||||
};
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
/**
|
||||
Create an input object
|
||||
|
||||
Input objects are used as data carriers for complex input values to
|
||||
fields and mutations. Unlike the other helper macros,
|
||||
`graphql_input_object!` actually *creates* the struct you define. It
|
||||
does not add anything to the struct definition itself - what you type
|
||||
is what will be generated:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
#
|
||||
graphql_input_object!(
|
||||
description: "Coordinates for the user"
|
||||
|
||||
struct Coordinates {
|
||||
longitude: f64 as "The X coordinate, from -180 to +180",
|
||||
latitude: f64 as "The Y coordinate, from -90 to +90",
|
||||
}
|
||||
);
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
This macro creates the struct as specified and implements
|
||||
`FromInputValue` to automatically parse values provided from variables
|
||||
and arguments.
|
||||
|
||||
If you want to expose the struct under a different name than the Rust
|
||||
type, you can write `struct Coordinates as "MyCoordinates" { ...`.
|
||||
|
||||
You can specify *default values* for input object fields; the syntax
|
||||
is similar to argument default values:
|
||||
|
||||
```rust
|
||||
# #[macro_use] extern crate juniper;
|
||||
#
|
||||
graphql_input_object!(
|
||||
struct SampleObject {
|
||||
foo = 123: i32 as "A sample field, defaults to 123 if omitted"
|
||||
}
|
||||
);
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
*/
|
||||
#[macro_export]
|
||||
macro_rules! graphql_input_object {
|
||||
// Calls $val.$func($arg) if $arg is not None
|
||||
( @maybe_apply, None, $func:ident, $val:expr ) => { $val };
|
||||
( @maybe_apply, $arg:tt, $func:ident, $val:expr ) => { $val.$func($arg) };
|
||||
|
||||
// Calls $val.description($descr) when $descr is not empty
|
||||
( @apply_description, , $val:expr ) => { $val };
|
||||
( @apply_description, $descr:tt , $val:expr ) => { $val.description($descr) };
|
||||
|
||||
// Generate the FromInputValue::from method body, provided a
|
||||
// HashMap<&str, &InputValue> in $var
|
||||
(
|
||||
@generate_from_input_value,
|
||||
$name:tt, $var:tt,
|
||||
( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* )
|
||||
) => {
|
||||
Some($name {
|
||||
$( $field_name: {
|
||||
let n = $crate::to_camel_case(stringify!($field_name));
|
||||
let v: Option<&&$crate::InputValue> = $var.get(&n[..]);
|
||||
|
||||
match v {
|
||||
$( Some(&&$crate::InputValue::Null) | None if true => $default, )*
|
||||
Some(v) => $crate::FromInputValue::from_input_value(v).unwrap(),
|
||||
_ => $crate::FromInputValue::from_input_value(&$crate::InputValue::null()).unwrap()
|
||||
}
|
||||
} ),*
|
||||
})
|
||||
};
|
||||
|
||||
// Generate the ToInputValue::To method body, provided self in $self
|
||||
(
|
||||
@generate_to_input_value,
|
||||
$name:tt, $selfvar:tt,
|
||||
( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* )
|
||||
) => {
|
||||
$crate::InputValue::object(vec![
|
||||
$(
|
||||
($crate::to_camel_case(stringify!($field_name)), $selfvar.$field_name.to_input_value())
|
||||
),*
|
||||
].into_iter().collect())
|
||||
};
|
||||
|
||||
// Generate the struct declaration, including (Rust) meta attributes
|
||||
(
|
||||
@generate_struct_fields,
|
||||
( $($meta:tt)* ), ( $($pubmod:tt)* ), $name:tt,
|
||||
( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* )
|
||||
) => {
|
||||
$($meta)* $($pubmod)* struct $name {
|
||||
$( $field_name: $field_type, )*
|
||||
}
|
||||
};
|
||||
|
||||
// Generate single field meta for field with default value
|
||||
(
|
||||
@generate_single_meta_field,
|
||||
$reg:tt,
|
||||
( $field_name:ident = $default:tt : $field_type:ty $(as $descr:tt)* )
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@apply_description,
|
||||
$($descr)*,
|
||||
$reg.arg_with_default::<$field_type>(
|
||||
&$crate::to_camel_case(stringify!($field_name)),
|
||||
&$default, &()))
|
||||
};
|
||||
|
||||
// Generate single field meta for field without default value
|
||||
(
|
||||
@generate_single_meta_field,
|
||||
$reg:tt,
|
||||
( $field_name:ident : $field_type:ty $(as $descr:tt)* )
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@apply_description,
|
||||
$($descr)*,
|
||||
$reg.arg::<$field_type>(
|
||||
&$crate::to_camel_case(stringify!($field_name)), &()))
|
||||
};
|
||||
|
||||
// Generate the input field meta list, i.e. &[Argument] for
|
||||
(
|
||||
@generate_meta_fields,
|
||||
$reg:tt,
|
||||
( $($field_name:ident $(= $default:tt)* : $field_type:ty $(as $descr:tt)* $(,)* ),* )
|
||||
) => {
|
||||
&[
|
||||
$(
|
||||
graphql_input_object!(
|
||||
@generate_single_meta_field,
|
||||
$reg,
|
||||
( $field_name $(= $default)* : $field_type $(as $descr)* )
|
||||
)
|
||||
),*
|
||||
]
|
||||
};
|
||||
|
||||
// #[...] struct $name { ... }
|
||||
// struct $name { ... }
|
||||
(
|
||||
@parse,
|
||||
( $_ignore1:tt, $_ignore2:tt, $_ignore3:tt, $_ignore4:tt, $_ignore5:tt, $descr:tt ),
|
||||
$(#[$meta:meta])* struct $name:ident { $($fields:tt)* } $($rest:tt)*
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( $(#[$meta])* ), ( ), $name, (stringify!($name)), ($($fields)*), $descr ),
|
||||
$($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
// #[...] pub struct $name { ... }
|
||||
// pub struct $name { ... }
|
||||
(
|
||||
@parse,
|
||||
( $_ignore1:tt, $_ignore2:tt, $_ignore3:tt, $_ignore4:tt, $_ignore5:tt, $descr:tt ),
|
||||
$(#[$meta:meta])* pub struct $name:ident { $($fields:tt)* } $($rest:tt)*
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( $(#[$meta])* ), ( pub ), $name, (stringify!($name)), ($($fields)*), $descr ),
|
||||
$($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
// #[...] struct $name as "GraphQLName" { ... }
|
||||
// struct $name as "GraphQLName" { ... }
|
||||
(
|
||||
@parse,
|
||||
( $_ignore1:tt, $_ignore2:tt, $_ignore3:tt, $_ignore4:tt, $_ignore5:tt, $descr:tt ),
|
||||
$(#[$meta:meta])* struct $name:ident as $outname:tt { $($fields:tt)* } $($rest:tt)*
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( $($meta)* ), ( ), $name, $outname, ($($fields)*), $descr ),
|
||||
$($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
// #[...] pub struct $name as "GraphQLName" { ... }
|
||||
// pub struct $name as "GraphQLName" { ... }
|
||||
(
|
||||
@parse,
|
||||
( $_ignore1:tt, $_ignore2:tt, $_ignore3:tt, $_ignore4:tt, $_ignore5:tt, $descr:tt ),
|
||||
$(#[$meta:meta])* pub struct $name:ident as $outname:tt { $($fields:tt)* } $($rest:tt)*
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( $($meta)* ), ( pub ), $name, $outname, ($($fields)*), $descr ),
|
||||
$($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
// description: <description>
|
||||
(
|
||||
@parse,
|
||||
( $meta:tt, $pubmod:tt, $name:tt, $outname:tt, $fields:tt, $_ignore:tt ),
|
||||
description: $descr:tt $($rest:tt)*
|
||||
) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( $meta, $pubmod, $name, $outname, $fields, $descr ),
|
||||
$($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
// No more data to parse, generate the struct and impls
|
||||
(
|
||||
@parse,
|
||||
( $meta:tt, $pubmod:tt, $name:tt, $outname:tt, $fields:tt, $descr:tt ),
|
||||
) => {
|
||||
graphql_input_object!(@generate_struct_fields, $meta, $pubmod, $name, $fields);
|
||||
|
||||
impl $crate::FromInputValue for $name {
|
||||
fn from_input_value(value: &$crate::InputValue) -> Option<$name> {
|
||||
if let Some(obj) = value.to_object_value() {
|
||||
graphql_input_object!(@generate_from_input_value, $name, obj, $fields)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::ToInputValue for $name {
|
||||
fn to_input_value(&self) -> $crate::InputValue {
|
||||
graphql_input_object!(@generate_to_input_value, $name, self, $fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::GraphQLType for $name {
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
fn name(_: &()) -> Option<&str> {
|
||||
Some($outname)
|
||||
}
|
||||
|
||||
fn meta<'r>(_: &(), registry: &mut $crate::Registry<'r>) -> $crate::meta::MetaType<'r> {
|
||||
let fields = graphql_input_object!(@generate_meta_fields, registry, $fields);
|
||||
graphql_input_object!(
|
||||
@maybe_apply, $descr, description,
|
||||
registry.build_input_object_type::<$name>(&(), fields)).into_meta()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Entry point: parse calls starting with a struct declaration
|
||||
( $(#[$meta:meta])* struct $($items:tt)* ) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( ), ( ), None, None, None, None ),
|
||||
$(#[$meta])* struct $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
// Entry point: parse calls starting with a public struct declaration
|
||||
( $(#[$meta:meta])* pub struct $($items:tt)* ) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( ), ( ), None, None, None, None ),
|
||||
$(#[$meta])* pub struct $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
// Entry point: parse calls starting with the description
|
||||
( description: $($items:tt)* ) => {
|
||||
graphql_input_object!(
|
||||
@parse,
|
||||
( ( ), ( ), None, None, None, None ),
|
||||
description: $($items)*
|
||||
);
|
||||
};
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
#[macro_use]
|
||||
mod enums;
|
||||
#[macro_use]
|
||||
mod object;
|
||||
#[macro_use]
|
||||
mod interface;
|
||||
|
@ -11,8 +9,6 @@ mod args;
|
|||
#[macro_use]
|
||||
mod field;
|
||||
#[macro_use]
|
||||
mod input_object;
|
||||
#[macro_use]
|
||||
mod union;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -19,12 +19,11 @@ Syntax to validate:
|
|||
|
||||
*/
|
||||
|
||||
graphql_input_object!(
|
||||
#[derive(Debug)]
|
||||
#[derive(GraphQLInputObject)]
|
||||
#[graphql(_internal)]
|
||||
struct Point {
|
||||
x: i32,
|
||||
}
|
||||
);
|
||||
|
||||
graphql_object!(Root: () |&self| {
|
||||
field simple() -> i32 { 0 }
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
mod enums;
|
||||
mod scalar;
|
||||
#[allow(dead_code)]
|
||||
mod input_object;
|
||||
mod args;
|
||||
mod field;
|
||||
mod object;
|
||||
|
@ -9,6 +6,4 @@ mod interface;
|
|||
mod union;
|
||||
|
||||
|
||||
// This asserts that the input objects defined public actually became public
|
||||
#[allow(unused_imports)]
|
||||
use self::input_object::{NamedPublic, NamedPublicWithDescription};
|
||||
|
||||
|
|
|
@ -49,13 +49,17 @@ pub struct DirectiveType<'a> {
|
|||
pub arguments: Vec<Argument<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(GraphQLEnum, Clone, PartialEq, Eq, Debug)]
|
||||
#[graphql(name = "__DirectiveLocation", _internal)]
|
||||
pub enum DirectiveLocation {
|
||||
Query,
|
||||
Mutation,
|
||||
Field,
|
||||
#[graphql(name = "FRAGMENT_DEFINITION")]
|
||||
FragmentDefinition,
|
||||
#[graphql(name = "FRAGMENT_SPREAD")]
|
||||
FragmentSpread,
|
||||
#[graphql(name = "INLINE_SPREAD")]
|
||||
InlineFragment,
|
||||
}
|
||||
|
||||
|
|
|
@ -232,17 +232,6 @@ graphql_object!(EnumValue: () as "__EnumValue" |&self| {
|
|||
}
|
||||
});
|
||||
|
||||
graphql_enum!(TypeKind as "__TypeKind" {
|
||||
TypeKind::Scalar => "SCALAR",
|
||||
TypeKind::Object => "OBJECT",
|
||||
TypeKind::Interface => "INTERFACE",
|
||||
TypeKind::Union => "UNION",
|
||||
TypeKind::Enum => "ENUM",
|
||||
TypeKind::InputObject => "INPUT_OBJECT",
|
||||
TypeKind::List => "LIST",
|
||||
TypeKind::NonNull => "NON_NULL",
|
||||
});
|
||||
|
||||
|
||||
graphql_object!(<'a> DirectiveType<'a>: SchemaType<'a> as "__Directive" |&self| {
|
||||
field name() -> &String {
|
||||
|
@ -282,11 +271,3 @@ graphql_object!(<'a> DirectiveType<'a>: SchemaType<'a> as "__Directive" |&self|
|
|||
}
|
||||
});
|
||||
|
||||
graphql_enum!(DirectiveLocation as "__DirectiveLocation" {
|
||||
DirectiveLocation::Query => "QUERY",
|
||||
DirectiveLocation::Mutation => "MUTATION",
|
||||
DirectiveLocation::Field => "FIELD",
|
||||
DirectiveLocation::FragmentDefinition => "FRAGMENT_DEFINITION",
|
||||
DirectiveLocation::FragmentSpread => "FRAGMENT_SPREAD",
|
||||
DirectiveLocation::InlineFragment => "INLINE_FRAGMENT",
|
||||
});
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[derive(GraphQLEnum, Copy, Clone, Eq, PartialEq, Debug)]
|
||||
#[graphql(_internal)]
|
||||
pub enum Episode {
|
||||
#[graphql(name = "NEW_HOPE")]
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
|
|
|
@ -3,12 +3,6 @@ use executor::Context;
|
|||
|
||||
impl Context for Database {}
|
||||
|
||||
graphql_enum!(Episode {
|
||||
Episode::NewHope => "NEW_HOPE",
|
||||
Episode::Empire => "EMPIRE",
|
||||
Episode::Jedi => "JEDI",
|
||||
});
|
||||
|
||||
graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||
description: "A character in the Star Wars Trilogy"
|
||||
|
||||
|
|
|
@ -13,7 +13,9 @@ use parser::Spanning;
|
|||
///
|
||||
/// The GraphQL specification defines a number of type kinds - the meta type
|
||||
/// of a type.
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
#[derive(GraphQLEnum, Clone, Eq, PartialEq, Debug)]
|
||||
// Note: _internal flag needed to make derive work in juniper crate itself.
|
||||
#[graphql(name = "__TypeKind", _internal)]
|
||||
pub enum TypeKind {
|
||||
/// ## Scalar types
|
||||
///
|
||||
|
@ -48,6 +50,7 @@ pub enum TypeKind {
|
|||
/// ## Input objects
|
||||
///
|
||||
/// Represents complex values provided in queries _into_ the system.
|
||||
#[graphql(name = "INPUT_OBJECT")]
|
||||
InputObject,
|
||||
|
||||
/// ## List types
|
||||
|
@ -61,6 +64,7 @@ pub enum TypeKind {
|
|||
///
|
||||
/// In GraphQL, nullable types are the default. By putting a `!` after a
|
||||
/// type, it becomes non-nullable.
|
||||
#[graphql(name = "NON_NULL")]
|
||||
NonNull,
|
||||
}
|
||||
|
||||
|
|
42
juniper/src/util.rs
Normal file
42
juniper/src/util.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
/// Convert string to camel case.
|
||||
///
|
||||
/// Note: needs to be public because several macros use it.
|
||||
#[doc(hidden)]
|
||||
pub fn to_camel_case<'a>(s: &'a str) -> Cow<'a, str> {
|
||||
let mut dest = Cow::Borrowed(s);
|
||||
|
||||
for (i, part) in s.split('_').enumerate() {
|
||||
if i > 0 && part.len() == 1 {
|
||||
dest += Cow::Owned(part.to_uppercase());
|
||||
} else if i > 0 && part.len() > 1 {
|
||||
let first = part.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_uppercase()
|
||||
.collect::<String>();
|
||||
let second = &part[1..];
|
||||
|
||||
dest += Cow::Owned(first);
|
||||
dest += second;
|
||||
} else if i == 0 {
|
||||
dest = Cow::Borrowed(part);
|
||||
}
|
||||
}
|
||||
|
||||
dest
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_camel_case() {
|
||||
assert_eq!(&to_camel_case("test")[..], "test");
|
||||
assert_eq!(&to_camel_case("_test")[..], "Test");
|
||||
assert_eq!(&to_camel_case("first_second")[..], "firstSecond");
|
||||
assert_eq!(&to_camel_case("first_")[..], "first");
|
||||
assert_eq!(&to_camel_case("a_b_c")[..], "aBC");
|
||||
assert_eq!(&to_camel_case("a_bc")[..], "aBc");
|
||||
assert_eq!(&to_camel_case("a_b")[..], "aB");
|
||||
assert_eq!(&to_camel_case("a")[..], "a");
|
||||
assert_eq!(&to_camel_case("")[..], "");
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "juniper_codegen"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
|
||||
description = "Internal custom derive trait for Juniper GraphQL"
|
||||
license = "BSD-2-Clause"
|
||||
|
@ -11,8 +11,8 @@ repository = "https://github.com/graphql-rust/juniper"
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "0.11.11"
|
||||
quote = "0.3.15"
|
||||
syn = "0.11"
|
||||
quote = "0.3"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "graphql-rust/juniper" }
|
||||
|
|
|
@ -9,11 +9,17 @@ use util::*;
|
|||
struct EnumAttrs {
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
internal: bool,
|
||||
}
|
||||
|
||||
impl EnumAttrs {
|
||||
fn from_input(input: &DeriveInput) -> EnumAttrs {
|
||||
let mut res = EnumAttrs::default();
|
||||
let mut res = EnumAttrs{
|
||||
name: None,
|
||||
description: None,
|
||||
/// Flag to specify whether the calling crate is the "juniper" crate itself.
|
||||
internal: false,
|
||||
};
|
||||
|
||||
// Check attributes for name and description.
|
||||
if let Some(items) = get_graphl_attr(&input.attrs) {
|
||||
|
@ -26,6 +32,15 @@ impl EnumAttrs {
|
|||
res.description = Some(val);
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||
if ident == "_internal" {
|
||||
res.internal = true;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
panic!(format!(
|
||||
"Unknown attribute for #[derive(GraphQLEnum)]: {:?}",
|
||||
item
|
||||
|
@ -109,7 +124,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
// Build value.
|
||||
let name = var_attrs
|
||||
.name
|
||||
.unwrap_or(variant.ident.as_ref().to_uppercase());
|
||||
.unwrap_or(::util::to_upper_snake_case(variant.ident.as_ref()));
|
||||
let descr = match var_attrs.description {
|
||||
Some(s) => quote!{ Some(#s.to_string()) },
|
||||
None => quote!{ None },
|
||||
|
@ -119,7 +134,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
None => quote!{ None },
|
||||
};
|
||||
let value = quote!{
|
||||
::juniper::meta::EnumValue{
|
||||
_juniper::meta::EnumValue{
|
||||
name: #name.to_string(),
|
||||
description: #descr,
|
||||
deprecation_reason: #depr,
|
||||
|
@ -129,7 +144,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
|
||||
// Build resolve match clause.
|
||||
let resolve = quote!{
|
||||
&#ident::#var_ident => ::juniper::Value::String(#name.to_string()),
|
||||
&#ident::#var_ident => _juniper::Value::String(#name.to_string()),
|
||||
};
|
||||
resolves.push(resolve);
|
||||
|
||||
|
@ -142,13 +157,13 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
// Buil to_input clause.
|
||||
let to_input = quote!{
|
||||
&#ident::#var_ident =>
|
||||
::juniper::InputValue::string(#name.to_string()),
|
||||
_juniper::InputValue::string(#name.to_string()),
|
||||
};
|
||||
to_inputs.push(to_input);
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl ::juniper::GraphQLType for #ident {
|
||||
let body = quote! {
|
||||
impl _juniper::GraphQLType for #ident {
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
|
@ -156,7 +171,7 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
Some(#name)
|
||||
}
|
||||
|
||||
fn meta<'r>(_: &(), registry: &mut ::juniper::Registry<'r>) -> ::juniper::meta::MetaType<'r> {
|
||||
fn meta<'r>(_: &(), registry: &mut _juniper::Registry<'r>) -> _juniper::meta::MetaType<'r> {
|
||||
let meta = registry.build_enum_type::<#ident>(&(), &[
|
||||
#(#values)*
|
||||
]);
|
||||
|
@ -164,15 +179,15 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
meta.into_meta()
|
||||
}
|
||||
|
||||
fn resolve(&self, _: &(), _: Option<&[::juniper::Selection]>, _: &::juniper::Executor<Self::Context>) -> ::juniper::Value {
|
||||
fn resolve(&self, _: &(), _: Option<&[_juniper::Selection]>, _: &_juniper::Executor<Self::Context>) -> _juniper::Value {
|
||||
match self {
|
||||
#(#resolves)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::juniper::FromInputValue for #ident {
|
||||
fn from_input_value(v: &::juniper::InputValue) -> Option<#ident> {
|
||||
impl _juniper::FromInputValue for #ident {
|
||||
fn from_input_value(v: &_juniper::InputValue) -> Option<#ident> {
|
||||
match v.as_enum_value().or_else(|| v.as_string_value()) {
|
||||
#(#from_inputs)*
|
||||
_ => None,
|
||||
|
@ -180,12 +195,49 @@ pub fn impl_enum(ast: &syn::DeriveInput) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
impl ::juniper::ToInputValue for #ident {
|
||||
fn to_input_value(&self) -> ::juniper::InputValue {
|
||||
impl _juniper::ToInputValue for #ident {
|
||||
fn to_input_value(&self) -> _juniper::InputValue {
|
||||
match self {
|
||||
#(#to_inputs)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let dummy_const = Ident::new(format!("_IMPL_GRAPHQLENUM_FOR_{}", ident));
|
||||
|
||||
// This ugly hack makes it possible to use the derive inside juniper itself.
|
||||
// FIXME: Figure out a better way to do this!
|
||||
let crate_reference = if attrs.internal {
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
mod _juniper {
|
||||
pub use ::{
|
||||
InputValue,
|
||||
Value,
|
||||
ToInputValue,
|
||||
FromInputValue,
|
||||
Executor,
|
||||
Selection,
|
||||
Registry,
|
||||
GraphQLType,
|
||||
meta
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
extern crate juniper as _juniper;
|
||||
}
|
||||
};
|
||||
let generated = quote! {
|
||||
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
|
||||
#[doc(hidden)]
|
||||
const #dummy_const : () = {
|
||||
#crate_reference
|
||||
#body
|
||||
};
|
||||
};
|
||||
|
||||
generated
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use util::*;
|
|||
struct ObjAttrs {
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
internal: bool,
|
||||
}
|
||||
|
||||
impl ObjAttrs {
|
||||
|
@ -26,6 +27,15 @@ impl ObjAttrs {
|
|||
res.description = Some(val);
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||
if ident == "_internal" {
|
||||
res.internal = true;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
panic!(format!(
|
||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
||||
item
|
||||
|
@ -40,7 +50,8 @@ impl ObjAttrs {
|
|||
struct ObjFieldAttrs {
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
default: Option<String>,
|
||||
default: bool,
|
||||
default_expr: Option<String>,
|
||||
}
|
||||
|
||||
impl ObjFieldAttrs {
|
||||
|
@ -59,9 +70,18 @@ impl ObjFieldAttrs {
|
|||
continue;
|
||||
}
|
||||
if let Some(val) = keyed_item_value(item, "default", true) {
|
||||
res.default = Some(val);
|
||||
res.default_expr = Some(val);
|
||||
continue;
|
||||
}
|
||||
match item {
|
||||
&NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) => {
|
||||
if ident == "default" {
|
||||
res.default = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
panic!(format!(
|
||||
"Unknown attribute for #[derive(GraphQLInputObject)]: {:?}",
|
||||
item
|
||||
|
@ -122,7 +142,11 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
None => quote!{ let field = field; },
|
||||
};
|
||||
|
||||
let default = match field_attrs.default {
|
||||
let default = {
|
||||
if field_attrs.default {
|
||||
Some(quote! { Default::default() } )
|
||||
} else {
|
||||
match field_attrs.default_expr {
|
||||
Some(ref def) => match syn::parse_token_trees(def) {
|
||||
Ok(t) => Some(quote! { #(#t)* }),
|
||||
Err(_) => {
|
||||
|
@ -130,6 +154,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let create_meta_field = match default {
|
||||
|
@ -158,7 +184,7 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
let from_input_default = match default {
|
||||
Some(ref def) => {
|
||||
quote!{
|
||||
Some(&&::juniper::InputValue::Null) | None if true => #def,
|
||||
Some(&&_juniper::InputValue::Null) | None if true => #def,
|
||||
}
|
||||
}
|
||||
None => quote!{},
|
||||
|
@ -169,8 +195,11 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
// TODO: investigate the unwraps here, they seem dangerous!
|
||||
match obj.get(#name) {
|
||||
#from_input_default
|
||||
Some(v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||
_ => ::juniper::FromInputValue::from_input_value(&::juniper::InputValue::null()).unwrap()
|
||||
Some(v) => _juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||
_ => {
|
||||
_juniper::FromInputValue::from_input_value(&_juniper::InputValue::null())
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -183,8 +212,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
to_inputs.push(to_input);
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl ::juniper::GraphQLType for #ident {
|
||||
let body = quote! {
|
||||
impl _juniper::GraphQLType for #ident {
|
||||
type Context = ();
|
||||
type TypeInfo = ();
|
||||
|
||||
|
@ -192,7 +221,7 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
Some(#name)
|
||||
}
|
||||
|
||||
fn meta<'r>(_: &(), registry: &mut ::juniper::Registry<'r>) -> ::juniper::meta::MetaType<'r> {
|
||||
fn meta<'r>(_: &(), registry: &mut _juniper::Registry<'r>) -> _juniper::meta::MetaType<'r> {
|
||||
let fields = &[
|
||||
#(#meta_fields)*
|
||||
];
|
||||
|
@ -202,8 +231,8 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
impl ::juniper::FromInputValue for #ident {
|
||||
fn from_input_value(value: &::juniper::InputValue) -> Option<#ident> {
|
||||
impl _juniper::FromInputValue for #ident {
|
||||
fn from_input_value(value: &_juniper::InputValue) -> Option<#ident> {
|
||||
if let Some(obj) = value.to_object_value() {
|
||||
let item = #ident {
|
||||
#(#from_inputs)*
|
||||
|
@ -216,12 +245,46 @@ pub fn impl_input_object(ast: &syn::DeriveInput) -> Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
impl ::juniper::ToInputValue for #ident {
|
||||
fn to_input_value(&self) -> ::juniper::InputValue {
|
||||
::juniper::InputValue::object(vec![
|
||||
impl _juniper::ToInputValue for #ident {
|
||||
fn to_input_value(&self) -> _juniper::InputValue {
|
||||
_juniper::InputValue::object(vec![
|
||||
#(#to_inputs)*
|
||||
].into_iter().collect())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let dummy_const = Ident::new(format!("_IMPL_GRAPHQLINPUTOBJECT_FOR_{}", ident));
|
||||
|
||||
// This ugly hack makes it possible to use the derive inside juniper itself.
|
||||
// FIXME: Figure out a better way to do this!
|
||||
let crate_reference = if attrs.internal {
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
mod _juniper {
|
||||
pub use ::{
|
||||
InputValue,
|
||||
FromInputValue,
|
||||
GraphQLType,
|
||||
Registry,
|
||||
meta,
|
||||
ToInputValue
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
extern crate juniper as _juniper;
|
||||
}
|
||||
};
|
||||
let generated = quote! {
|
||||
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
|
||||
#[doc(hidden)]
|
||||
const #dummy_const : () = {
|
||||
#crate_reference
|
||||
#body
|
||||
};
|
||||
};
|
||||
|
||||
generated
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
//! This crate supplies custom derive implementations for the
|
||||
//! [juniper](https://github.com/graphql-rust/juniper) crate.
|
||||
//!
|
||||
//! You should not depend on juniper_codegen directly.
|
||||
//! You only need the `juniper` crate.
|
||||
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
|
|
@ -80,3 +80,38 @@ fn test_to_camel_case() {
|
|||
assert_eq!(&to_camel_case("a")[..], "a");
|
||||
assert_eq!(&to_camel_case("")[..], "");
|
||||
}
|
||||
|
||||
pub(crate) fn to_upper_snake_case(s: &str) -> String {
|
||||
let mut last_lower = false;
|
||||
let mut upper = String::new();
|
||||
for c in s.chars() {
|
||||
if c == '_' {
|
||||
last_lower = false;
|
||||
}
|
||||
else if c.is_lowercase() {
|
||||
last_lower = true;
|
||||
} else if c.is_uppercase() {
|
||||
if last_lower {
|
||||
upper.push('_');
|
||||
}
|
||||
last_lower = false;
|
||||
}
|
||||
|
||||
for u in c.to_uppercase() {
|
||||
upper.push(u);
|
||||
}
|
||||
}
|
||||
upper
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_upper_snake_case() {
|
||||
assert_eq!(to_upper_snake_case("abc"), "ABC");
|
||||
assert_eq!(to_upper_snake_case("a_bc"), "A_BC");
|
||||
assert_eq!(to_upper_snake_case("ABC"), "ABC");
|
||||
assert_eq!(to_upper_snake_case("A_BC"), "A_BC");
|
||||
assert_eq!(to_upper_snake_case("SomeInput"), "SOME_INPUT");
|
||||
assert_eq!(to_upper_snake_case("someInput"), "SOME_INPUT");
|
||||
assert_eq!(to_upper_snake_case("someINpuT"), "SOME_INPU_T");
|
||||
assert_eq!(to_upper_snake_case("some_INpuT"), "SOME_INPU_T");
|
||||
}
|
||||
|
|
2
juniper_iron/.gitignore
vendored
Normal file
2
juniper_iron/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
Cargo.lock
|
0
juniper_iron/CHANGELOG.md
Normal file
0
juniper_iron/CHANGELOG.md
Normal file
31
juniper_iron/Cargo.toml
Normal file
31
juniper_iron/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
|||
[package]
|
||||
name = "juniper_iron"
|
||||
version = "0.1.0"
|
||||
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
|
||||
description = "Iron integration for juniper"
|
||||
license = "BSD-2-Clause"
|
||||
documentation = "https://docs.rs/juniper_iron"
|
||||
repository = "https://github.com/graphql-rust/juniper"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.2" }
|
||||
serde_json = { version = "1.0.2" }
|
||||
juniper = { version = "0.9.0", path = "../juniper" }
|
||||
|
||||
urlencoded = { version = "0.5.0" }
|
||||
iron = "0.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
iron-test = "^0.5.0"
|
||||
router = "^0.5.0"
|
||||
mount = "^0.3.0"
|
||||
logger = "^0.3.0"
|
||||
|
||||
[dev-dependencies.juniper]
|
||||
version = "0.9.0"
|
||||
features = ["expose-test-schema", "serde_json"]
|
||||
path = "../juniper"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "graphql-rust/juniper_iron" }
|
||||
appveyor = { repository = "graphql-rust/juniper_iron" }
|
25
juniper_iron/LICENSE
Normal file
25
juniper_iron/LICENSE
Normal file
|
@ -0,0 +1,25 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2016, Magnus Hallin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
juniper_iron/README.md
Normal file
31
juniper_iron/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# juniper_iron
|
||||
|
||||
This repository contains the [Iron][Iron] web framework integration for
|
||||
[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust.
|
||||
|
||||
For documentation, including guides and examples, check out [Juniper][Juniper].
|
||||
|
||||
A basic usage example can also be found in the [Api documentation][documentation].
|
||||
|
||||
## Examples
|
||||
|
||||
Check [examples/iron_server.rs][example] for example code of a working Iron server with GraphQL handlers.
|
||||
|
||||
## Links
|
||||
|
||||
* [Juniper][Juniper]
|
||||
* [Api Reference][documetation]
|
||||
* [Iron framework][Iron]
|
||||
|
||||
## License
|
||||
|
||||
This project is under the BSD-2 license.
|
||||
|
||||
Check the LICENSE file for details.
|
||||
|
||||
[Iron]: https://github.com/iron/iron
|
||||
[Juniper]: https://github.com/graphql-rust/juniper
|
||||
[GraphQL]: http://graphql.org
|
||||
[documentation]: https://docs.rs/juniper_iron
|
||||
[example]: https://github.com/graphql-rust/juniper/blob/master/juniper_iron/examples/iron_server.rs
|
||||
|
43
juniper_iron/examples/iron_server.rs
Normal file
43
juniper_iron/examples/iron_server.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
extern crate iron;
|
||||
extern crate mount;
|
||||
extern crate logger;
|
||||
extern crate serde;
|
||||
extern crate juniper;
|
||||
extern crate juniper_iron;
|
||||
|
||||
use std::env;
|
||||
|
||||
use mount::Mount;
|
||||
use logger::Logger;
|
||||
use iron::prelude::*;
|
||||
use juniper::EmptyMutation;
|
||||
use juniper_iron::{GraphQLHandler, GraphiQLHandler};
|
||||
use juniper::tests::model::Database;
|
||||
|
||||
fn context_factory(_: &mut Request) -> Database {
|
||||
Database::new()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut mount = Mount::new();
|
||||
|
||||
let graphql_endpoint = GraphQLHandler::new(
|
||||
context_factory,
|
||||
Database::new(),
|
||||
EmptyMutation::<Database>::new(),
|
||||
);
|
||||
let graphiql_endpoint = GraphiQLHandler::new("/graphql");
|
||||
|
||||
mount.mount("/", graphiql_endpoint);
|
||||
mount.mount("/graphql", graphql_endpoint);
|
||||
|
||||
let (logger_before, logger_after) = Logger::new(None);
|
||||
|
||||
let mut chain = Chain::new(mount);
|
||||
chain.link_before(logger_before);
|
||||
chain.link_after(logger_after);
|
||||
|
||||
let host = env::var("LISTEN").unwrap_or("0.0.0.0:8080".to_owned());
|
||||
println!("GraphQL server started on {}", host);
|
||||
Iron::new(chain).http(host.as_str()).unwrap();
|
||||
}
|
405
juniper_iron/src/lib.rs
Normal file
405
juniper_iron/src/lib.rs
Normal file
|
@ -0,0 +1,405 @@
|
|||
/*!
|
||||
|
||||
# juniper_iron
|
||||
|
||||
This repository contains the [Iron][Iron] web framework integration for
|
||||
[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust.
|
||||
|
||||
For documentation, including guides and examples, check out [Juniper][Juniper].
|
||||
|
||||
A basic usage example can also be found in the [Api documentation][documentation].
|
||||
|
||||
## Links
|
||||
|
||||
* [Juniper][Juniper]
|
||||
* [Api Reference][documentation]
|
||||
* [Iron framework][Iron]
|
||||
|
||||
## Integrating with Iron
|
||||
|
||||
|
||||
For example, continuing from the schema created above and using Iron to expose
|
||||
the schema on an HTTP endpoint supporting both GET and POST requests:
|
||||
|
||||
```rust,no_run
|
||||
extern crate iron;
|
||||
# #[macro_use] extern crate juniper;
|
||||
# extern crate juniper_iron;
|
||||
# use std::collections::HashMap;
|
||||
|
||||
use iron::prelude::*;
|
||||
use juniper_iron::GraphQLHandler;
|
||||
use juniper::{Context, EmptyMutation};
|
||||
|
||||
# use juniper::FieldResult;
|
||||
#
|
||||
# struct User { id: String, name: String, friend_ids: Vec<String> }
|
||||
# struct QueryRoot;
|
||||
# struct Database { users: HashMap<String, User> }
|
||||
#
|
||||
# graphql_object!(User: Database |&self| {
|
||||
# field id() -> FieldResult<&String> {
|
||||
# Ok(&self.id)
|
||||
# }
|
||||
#
|
||||
# field name() -> FieldResult<&String> {
|
||||
# Ok(&self.name)
|
||||
# }
|
||||
#
|
||||
# field friends(&executor) -> FieldResult<Vec<&User>> {
|
||||
# Ok(self.friend_ids.iter()
|
||||
# .filter_map(|id| executor.context().users.get(id))
|
||||
# .collect())
|
||||
# }
|
||||
# });
|
||||
#
|
||||
# graphql_object!(QueryRoot: Database |&self| {
|
||||
# field user(&executor, id: String) -> FieldResult<Option<&User>> {
|
||||
# Ok(executor.context().users.get(&id))
|
||||
# }
|
||||
# });
|
||||
|
||||
// This function is executed for every request. Here, we would realistically
|
||||
// provide a database connection or similar. For this example, we'll be
|
||||
// creating the database from scratch.
|
||||
fn context_factory(_: &mut Request) -> Database {
|
||||
Database {
|
||||
users: vec![
|
||||
( "1000".to_owned(), User {
|
||||
id: "1000".to_owned(), name: "Robin".to_owned(),
|
||||
friend_ids: vec!["1001".to_owned()] } ),
|
||||
( "1001".to_owned(), User {
|
||||
id: "1001".to_owned(), name: "Max".to_owned(),
|
||||
friend_ids: vec!["1000".to_owned()] } ),
|
||||
].into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for Database {}
|
||||
|
||||
fn main() {
|
||||
// GraphQLHandler takes a context factory function, the root object,
|
||||
// and the mutation object. If we don't have any mutations to expose, we
|
||||
// can use the empty tuple () to indicate absence.
|
||||
let graphql_endpoint = GraphQLHandler::new(
|
||||
context_factory, QueryRoot, EmptyMutation::<Database>::new());
|
||||
|
||||
// Start serving the schema at the root on port 8080.
|
||||
Iron::new(graphql_endpoint).http("localhost:8080").unwrap();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
See the the [`GraphQLHandler`][3] documentation for more information on what request methods are
|
||||
supported.
|
||||
|
||||
[3]: ./struct.GraphQLHandler.html
|
||||
[Iron]: https://github.com/iron/iron
|
||||
[Juniper]: https://github.com/graphql-rust/juniper
|
||||
[GraphQL]: http://graphql.org
|
||||
[documentation]: https://docs.rs/juniper_iron
|
||||
|
||||
*/
|
||||
|
||||
extern crate serde_json;
|
||||
extern crate juniper;
|
||||
extern crate urlencoded;
|
||||
#[macro_use]
|
||||
extern crate iron;
|
||||
#[cfg(test)]
|
||||
extern crate iron_test;
|
||||
|
||||
use iron::prelude::*;
|
||||
use iron::middleware::Handler;
|
||||
use iron::mime::Mime;
|
||||
use iron::status;
|
||||
use iron::method;
|
||||
use urlencoded::{UrlDecodingError, UrlEncodedQuery};
|
||||
|
||||
use std::io::Read;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
use serde_json::error::Error as SerdeError;
|
||||
|
||||
use juniper::{GraphQLType, InputValue, RootNode};
|
||||
use juniper::http;
|
||||
|
||||
/// Handler that executes GraphQL queries in the given schema
|
||||
///
|
||||
/// The handler responds to GET requests and POST requests only. In GET
|
||||
/// requests, the query should be supplied in the `query` URL parameter, e.g.
|
||||
/// `http://localhost:3000/graphql?query={hero{name}}`.
|
||||
///
|
||||
/// POST requests support both queries and variables. POST a JSON document to
|
||||
/// this endpoint containing the field `"query"` and optionally `"variables"`.
|
||||
/// The variables should be a JSON object containing the variable to value
|
||||
/// mapping.
|
||||
pub struct GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
|
||||
where
|
||||
CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
|
||||
CtxT: 'static,
|
||||
Query: GraphQLType<Context = CtxT> + Send + Sync + 'static,
|
||||
Mutation: GraphQLType<Context = CtxT> + Send + Sync + 'static,
|
||||
{
|
||||
context_factory: CtxFactory,
|
||||
root_node: RootNode<'a, Query, Mutation>,
|
||||
}
|
||||
|
||||
/// Handler that renders GraphiQL - a graphical query editor interface
|
||||
pub struct GraphiQLHandler {
|
||||
graphql_url: String,
|
||||
}
|
||||
|
||||
|
||||
fn get_single_value<T>(mut values: Vec<T>) -> IronResult<T> {
|
||||
if values.len() == 1 {
|
||||
Ok(values.remove(0))
|
||||
} else {
|
||||
Err(
|
||||
GraphQLIronError::InvalidData("Duplicate URL query parameter").into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_url_param(params: Option<Vec<String>>) -> IronResult<Option<String>> {
|
||||
if let Some(values) = params {
|
||||
get_single_value(values).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_variable_param(params: Option<Vec<String>>) -> IronResult<Option<InputValue>> {
|
||||
if let Some(values) = params {
|
||||
Ok(serde_json::from_str::<InputValue>(
|
||||
get_single_value(values)?.as_ref(),
|
||||
).map(Some)
|
||||
.map_err(GraphQLIronError::Serde)?)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a, CtxFactory, Query, Mutation, CtxT> GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
|
||||
where
|
||||
CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
|
||||
CtxT: 'static,
|
||||
Query: GraphQLType<Context = CtxT, TypeInfo=()> + Send + Sync + 'static,
|
||||
Mutation: GraphQLType<Context = CtxT, TypeInfo=()> + Send + Sync + 'static,
|
||||
{
|
||||
/// Build a new GraphQL handler
|
||||
///
|
||||
/// The context factory will receive the Iron request object and is
|
||||
/// expected to construct a context object for the given schema. This can
|
||||
/// be used to construct e.g. database connections or similar data that
|
||||
/// the schema needs to execute the query.
|
||||
pub fn new(context_factory: CtxFactory, query: Query, mutation: Mutation) -> Self {
|
||||
GraphQLHandler {
|
||||
context_factory: context_factory,
|
||||
root_node: RootNode::new(query, mutation),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn handle_get(&self, req: &mut Request) -> IronResult<http::GraphQLRequest> {
|
||||
let url_query_string = req.get_mut::<UrlEncodedQuery>()
|
||||
.map_err(|e| GraphQLIronError::Url(e))?;
|
||||
|
||||
let input_query = parse_url_param(url_query_string.remove("query"))?
|
||||
.ok_or_else(|| GraphQLIronError::InvalidData("No query provided"))?;
|
||||
let operation_name = parse_url_param(url_query_string.remove("operationName"))?;
|
||||
let variables = parse_variable_param(url_query_string.remove("variables"))?;
|
||||
|
||||
Ok(http::GraphQLRequest::new(
|
||||
input_query,
|
||||
operation_name,
|
||||
variables,
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_post(&self, req: &mut Request) -> IronResult<http::GraphQLRequest> {
|
||||
let mut request_payload = String::new();
|
||||
itry!(req.body.read_to_string(&mut request_payload));
|
||||
|
||||
Ok(serde_json::from_str::<http::GraphQLRequest>(
|
||||
request_payload.as_str(),
|
||||
).map_err(|err| GraphQLIronError::Serde(err))?)
|
||||
}
|
||||
|
||||
fn execute(&self, context: &CtxT, request: http::GraphQLRequest) -> IronResult<Response> {
|
||||
let response = request.execute(&self.root_node, context);
|
||||
let content_type = "application/json".parse::<Mime>().unwrap();
|
||||
let json = serde_json::to_string_pretty(&response).unwrap();
|
||||
let status = if response.is_ok() {
|
||||
status::Ok
|
||||
} else {
|
||||
status::BadRequest
|
||||
};
|
||||
Ok(Response::with((content_type, status, json)))
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphiQLHandler {
|
||||
/// Build a new GraphiQL handler targeting the specified URL.
|
||||
///
|
||||
/// The provided URL should point to the URL of the attached `GraphQLHandler`. It can be
|
||||
/// relative, so a common value could be `"/graphql"`.
|
||||
pub fn new(graphql_url: &str) -> GraphiQLHandler {
|
||||
GraphiQLHandler {
|
||||
graphql_url: graphql_url.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, CtxFactory, Query, Mutation, CtxT> Handler
|
||||
for GraphQLHandler<'a, CtxFactory, Query, Mutation, CtxT>
|
||||
where
|
||||
CtxFactory: Fn(&mut Request) -> CtxT + Send + Sync + 'static,
|
||||
CtxT: 'static,
|
||||
Query: GraphQLType<Context = CtxT, TypeInfo=()> + Send + Sync + 'static,
|
||||
Mutation: GraphQLType<Context = CtxT, TypeInfo=()> + Send + Sync + 'static,
|
||||
'a: 'static,
|
||||
{
|
||||
fn handle(&self, mut req: &mut Request) -> IronResult<Response> {
|
||||
let context = (self.context_factory)(req);
|
||||
|
||||
let graphql_request = match req.method {
|
||||
method::Get => self.handle_get(&mut req)?,
|
||||
method::Post => self.handle_post(&mut req)?,
|
||||
_ => return Ok(Response::with((status::MethodNotAllowed))),
|
||||
};
|
||||
|
||||
self.execute(&context, graphql_request)
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for GraphiQLHandler {
|
||||
fn handle(&self, _: &mut Request) -> IronResult<Response> {
|
||||
let content_type = "text/html".parse::<Mime>().unwrap();
|
||||
|
||||
Ok(Response::with((
|
||||
content_type,
|
||||
status::Ok,
|
||||
juniper::graphiql::graphiql_source(&self.graphql_url),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum GraphQLIronError {
|
||||
Serde(SerdeError),
|
||||
Url(UrlDecodingError),
|
||||
InvalidData(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for GraphQLIronError {
|
||||
fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f),
|
||||
GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f),
|
||||
GraphQLIronError::InvalidData(ref err) => fmt::Display::fmt(err, &mut f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for GraphQLIronError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
GraphQLIronError::Serde(ref err) => err.description(),
|
||||
GraphQLIronError::Url(ref err) => err.description(),
|
||||
GraphQLIronError::InvalidData(ref err) => err,
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
GraphQLIronError::Serde(ref err) => Some(err),
|
||||
GraphQLIronError::Url(ref err) => Some(err),
|
||||
GraphQLIronError::InvalidData(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GraphQLIronError> for IronError {
|
||||
fn from(err: GraphQLIronError) -> IronError {
|
||||
let message = format!("{}", err);
|
||||
IronError::new(err, (status::BadRequest, message))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use iron::prelude::*;
|
||||
use iron_test::{request, response};
|
||||
use iron::{Handler, Headers};
|
||||
|
||||
use juniper::tests::model::Database;
|
||||
use juniper::http::tests as http_tests;
|
||||
use juniper::EmptyMutation;
|
||||
|
||||
use super::GraphQLHandler;
|
||||
|
||||
struct TestIronIntegration;
|
||||
|
||||
impl http_tests::HTTPIntegration for TestIronIntegration {
|
||||
fn get(&self, url: &str) -> http_tests::TestResponse {
|
||||
make_test_response(request::get(
|
||||
&("http://localhost:3000".to_owned() + url),
|
||||
Headers::new(),
|
||||
&make_handler(),
|
||||
))
|
||||
}
|
||||
|
||||
fn post(&self, url: &str, body: &str) -> http_tests::TestResponse {
|
||||
make_test_response(request::post(
|
||||
&("http://localhost:3000".to_owned() + url),
|
||||
Headers::new(),
|
||||
body,
|
||||
&make_handler(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iron_integration() {
|
||||
let integration = TestIronIntegration;
|
||||
|
||||
http_tests::run_http_test_suite(&integration);
|
||||
}
|
||||
|
||||
fn context_factory(_: &mut Request) -> Database {
|
||||
Database::new()
|
||||
}
|
||||
|
||||
fn make_test_response(response: IronResult<Response>) -> http_tests::TestResponse {
|
||||
let response = response.expect("Error response from GraphQL handler");
|
||||
let status_code = response
|
||||
.status
|
||||
.expect("No status code returned from handler")
|
||||
.to_u16() as i32;
|
||||
let content_type = String::from_utf8(
|
||||
response
|
||||
.headers
|
||||
.get_raw("content-type")
|
||||
.expect("No content type header from handler")[0]
|
||||
.clone(),
|
||||
).expect("Content-type header invalid UTF-8");
|
||||
let body = response::extract_body_to_string(response);
|
||||
|
||||
http_tests::TestResponse {
|
||||
status_code: status_code,
|
||||
body: Some(body),
|
||||
content_type: content_type,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_handler() -> Box<Handler> {
|
||||
Box::new(GraphQLHandler::new(
|
||||
context_factory,
|
||||
Database::new(),
|
||||
EmptyMutation::<Database>::new(),
|
||||
))
|
||||
}
|
||||
}
|
2
juniper_rocket/.gitignore
vendored
Normal file
2
juniper_rocket/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
target
|
||||
Cargo.lock
|
27
juniper_rocket/Cargo.toml
Normal file
27
juniper_rocket/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "juniper_rocket"
|
||||
version = "0.1.0"
|
||||
authors = ["Magnus Hallin <mhallin@fastmail.com>"]
|
||||
description = "Juniper GraphQL integration with Rocket"
|
||||
license = "BSD-2-Clause"
|
||||
documentation = "https://docs.rs/juniper_rocket"
|
||||
repository = "https://github.com/graphql-rust/juniper"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.2" }
|
||||
serde_derive = {version="1.0.2" }
|
||||
serde_json = { version = "1.0.2" }
|
||||
juniper = { version = "0.9.0" , path = "../juniper"}
|
||||
|
||||
rocket = { version = "0.3.0" }
|
||||
rocket_codegen = { version = "0.3.0" }
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "mhallin/juniper" }
|
||||
appveyor = { repository = "mhallin/juniper" }
|
||||
|
||||
[dev-dependencies.juniper]
|
||||
version = "0.9.0"
|
||||
features = ["expose-test-schema", "serde_json"]
|
||||
path = "../juniper"
|
||||
|
25
juniper_rocket/LICENSE
Normal file
25
juniper_rocket/LICENSE
Normal file
|
@ -0,0 +1,25 @@
|
|||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2016, Magnus Hallin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
35
juniper_rocket/README.md
Normal file
35
juniper_rocket/README.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# juniper_rocket
|
||||
|
||||
This repository contains the [Rocket][Rocket] web server integration for
|
||||
[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust.
|
||||
|
||||
## Documentation
|
||||
|
||||
For documentation, including guides and examples, check out [Juniper][Juniper].
|
||||
|
||||
A basic usage example can also be found in the [Api documentation][documentation].
|
||||
|
||||
## Examples
|
||||
|
||||
Check [examples/rocket_server.rs][example] for example code of a working Rocket
|
||||
server with GraphQL handlers.
|
||||
|
||||
## Links
|
||||
|
||||
* [Juniper][Juniper]
|
||||
* [Api Reference][documetation]
|
||||
* [Rocket][Iron]
|
||||
|
||||
## License
|
||||
|
||||
This project is under the BSD-2 license.
|
||||
|
||||
Check the LICENSE file for details.
|
||||
|
||||
[Rocket]: https://rocket.rs
|
||||
[Juniper]: https://github.com/graphql-rust/juniper
|
||||
[GraphQL]: http://graphql.org
|
||||
[documentation]: https://docs.rs/juniper_rocket
|
||||
[example]: https://github.com/graphql-rust/juniper_rocket/blob/master/examples/rocket_server.rs
|
||||
|
||||
|
51
juniper_rocket/examples/rocket_server.rs
Normal file
51
juniper_rocket/examples/rocket_server.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
#![feature(plugin)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rocket;
|
||||
extern crate juniper;
|
||||
extern crate juniper_rocket;
|
||||
|
||||
use rocket::response::content;
|
||||
use rocket::State;
|
||||
|
||||
use juniper::tests::model::Database;
|
||||
use juniper::{EmptyMutation, RootNode};
|
||||
|
||||
type Schema = RootNode<'static, Database, EmptyMutation<Database>>;
|
||||
|
||||
#[get("/")]
|
||||
fn graphiql() -> content::Html<String> {
|
||||
juniper_rocket::graphiql_source("/graphql")
|
||||
}
|
||||
|
||||
#[get("/graphql?<request>")]
|
||||
fn get_graphql_handler(
|
||||
context: State<Database>,
|
||||
request: juniper_rocket::GraphQLRequest,
|
||||
schema: State<Schema>,
|
||||
) -> juniper_rocket::GraphQLResponse {
|
||||
request.execute(&schema, &context)
|
||||
}
|
||||
|
||||
#[post("/graphql", data = "<request>")]
|
||||
fn post_graphql_handler(
|
||||
context: State<Database>,
|
||||
request: juniper_rocket::GraphQLRequest,
|
||||
schema: State<Schema>,
|
||||
) -> juniper_rocket::GraphQLResponse {
|
||||
request.execute(&schema, &context)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rocket::ignite()
|
||||
.manage(Database::new())
|
||||
.manage(Schema::new(
|
||||
Database::new(),
|
||||
EmptyMutation::<Database>::new(),
|
||||
))
|
||||
.mount(
|
||||
"/",
|
||||
routes![graphiql, get_graphql_handler, post_graphql_handler],
|
||||
)
|
||||
.launch();
|
||||
}
|
270
juniper_rocket/src/lib.rs
Normal file
270
juniper_rocket/src/lib.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*!
|
||||
|
||||
# juniper_rocket
|
||||
|
||||
This repository contains the [Rocket][Rocket] web server integration for
|
||||
[Juniper][Juniper], a [GraphQL][GraphQL] implementation for Rust.
|
||||
|
||||
## Documentation
|
||||
|
||||
For documentation, including guides and examples, check out [Juniper][Juniper].
|
||||
|
||||
A basic usage example can also be found in the [Api documentation][documentation].
|
||||
|
||||
## Examples
|
||||
|
||||
Check [examples/rocket_server.rs][example] for example code of a working Rocket
|
||||
server with GraphQL handlers.
|
||||
|
||||
## Links
|
||||
|
||||
* [Juniper][Juniper]
|
||||
* [Api Reference][documentation]
|
||||
* [Rocket][Rocket]
|
||||
|
||||
## License
|
||||
|
||||
This project is under the BSD-2 license.
|
||||
|
||||
Check the LICENSE file for details.
|
||||
|
||||
[Rocket]: https://rocket.rs
|
||||
[Juniper]: https://github.com/graphql-rust/juniper
|
||||
[GraphQL]: http://graphql.org
|
||||
[documentation]: https://docs.rs/juniper_rocket
|
||||
[example]: https://github.com/graphql-rust/juniper_rocket/blob/master/examples/rocket_server.rs
|
||||
|
||||
*/
|
||||
|
||||
#![feature(plugin)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate juniper;
|
||||
extern crate serde_json;
|
||||
extern crate rocket;
|
||||
|
||||
use std::io::{Cursor, Read};
|
||||
use std::error::Error;
|
||||
|
||||
use rocket::Request;
|
||||
use rocket::request::{FormItems, FromForm};
|
||||
use rocket::data::{FromData, Outcome as FromDataOutcome};
|
||||
use rocket::response::{content, Responder, Response};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::Data;
|
||||
use rocket::Outcome::{Failure, Forward, Success};
|
||||
|
||||
use juniper::InputValue;
|
||||
use juniper::http;
|
||||
|
||||
use juniper::GraphQLType;
|
||||
use juniper::RootNode;
|
||||
|
||||
/// Simple wrapper around an incoming GraphQL request
|
||||
///
|
||||
/// See the `http` module for more information. This type can be constructed
|
||||
/// automatically from both GET and POST routes by implementing the `FromForm`
|
||||
/// and `FromData` traits.
|
||||
pub struct GraphQLRequest(http::GraphQLRequest);
|
||||
|
||||
/// Simple wrapper around the result of executing a GraphQL query
|
||||
pub struct GraphQLResponse(Status, String);
|
||||
|
||||
/// Generate an HTML page containing GraphiQL
|
||||
pub fn graphiql_source(graphql_endpoint_url: &str) -> content::Html<String> {
|
||||
content::Html(juniper::graphiql::graphiql_source(graphql_endpoint_url))
|
||||
}
|
||||
|
||||
impl GraphQLRequest {
|
||||
/// Execute an incoming GraphQL query
|
||||
pub fn execute<CtxT, QueryT, MutationT>(
|
||||
&self,
|
||||
root_node: &RootNode<QueryT, MutationT>,
|
||||
context: &CtxT,
|
||||
) -> GraphQLResponse
|
||||
where
|
||||
QueryT: GraphQLType<Context = CtxT>,
|
||||
MutationT: GraphQLType<Context = CtxT>,
|
||||
{
|
||||
let response = self.0.execute(root_node, context);
|
||||
let status = if response.is_ok() {
|
||||
Status::Ok
|
||||
} else {
|
||||
Status::BadRequest
|
||||
};
|
||||
let json = serde_json::to_string_pretty(&response).unwrap();
|
||||
|
||||
GraphQLResponse(status, json)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'f> FromForm<'f> for GraphQLRequest {
|
||||
type Error = String;
|
||||
|
||||
fn from_form(form_items: &mut FormItems<'f>, strict: bool) -> Result<Self, String> {
|
||||
let mut query = None;
|
||||
let mut operation_name = None;
|
||||
let mut variables = None;
|
||||
|
||||
for (key, value) in form_items {
|
||||
match key.as_str() {
|
||||
"query" => if query.is_some() {
|
||||
return Err("Query parameter must not occur more than once".to_owned());
|
||||
} else {
|
||||
query = Some(value.as_str().to_string());
|
||||
},
|
||||
"operation_name" => if operation_name.is_some() {
|
||||
return Err(
|
||||
"Operation name parameter must not occur more than once".to_owned(),
|
||||
);
|
||||
} else {
|
||||
operation_name = Some(value.as_str().to_string());
|
||||
},
|
||||
"variables" => if variables.is_some() {
|
||||
return Err(
|
||||
"Variables parameter must not occur more than once".to_owned(),
|
||||
);
|
||||
} else {
|
||||
variables = Some(serde_json::from_str::<InputValue>(value.as_str())
|
||||
.map_err(|err| err.description().to_owned())?);
|
||||
},
|
||||
_ => if strict {
|
||||
return Err(format!("Prohibited extra field '{}'", key).to_owned());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(query) = query {
|
||||
Ok(GraphQLRequest(
|
||||
http::GraphQLRequest::new(query, operation_name, variables),
|
||||
))
|
||||
} else {
|
||||
Err("Query parameter missing".to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromData for GraphQLRequest {
|
||||
type Error = String;
|
||||
|
||||
fn from_data(request: &Request, data: Data) -> FromDataOutcome<Self, Self::Error> {
|
||||
if !request.content_type().map_or(false, |ct| ct.is_json()) {
|
||||
return Forward(data);
|
||||
}
|
||||
|
||||
let mut body = String::new();
|
||||
if let Err(e) = data.open().read_to_string(&mut body) {
|
||||
return Failure((Status::InternalServerError, format!("{:?}", e)));
|
||||
}
|
||||
|
||||
match serde_json::from_str(&body) {
|
||||
Ok(value) => Success(GraphQLRequest(value)),
|
||||
Err(failure) => return Failure((Status::BadRequest, format!("{}", failure))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Responder<'r> for GraphQLResponse {
|
||||
fn respond_to(self, _: &Request) -> Result<Response<'r>, Status> {
|
||||
let GraphQLResponse(status, body) = self;
|
||||
|
||||
Ok(
|
||||
Response::build()
|
||||
.header(ContentType::new("application", "json"))
|
||||
.status(status)
|
||||
.sized_body(Cursor::new(body))
|
||||
.finalize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use rocket;
|
||||
use rocket::Rocket;
|
||||
use rocket::http::{ContentType, Method};
|
||||
use rocket::State;
|
||||
|
||||
use juniper::RootNode;
|
||||
use juniper::tests::model::Database;
|
||||
use juniper::http::tests as http_tests;
|
||||
use juniper::EmptyMutation;
|
||||
|
||||
type Schema = RootNode<'static, Database, EmptyMutation<Database>>;
|
||||
|
||||
|
||||
#[get("/?<request>")]
|
||||
fn get_graphql_handler(
|
||||
context: State<Database>,
|
||||
request: super::GraphQLRequest,
|
||||
schema: State<Schema>,
|
||||
) -> super::GraphQLResponse {
|
||||
request.execute(&schema, &context)
|
||||
}
|
||||
|
||||
#[post("/", data = "<request>")]
|
||||
fn post_graphql_handler(
|
||||
context: State<Database>,
|
||||
request: super::GraphQLRequest,
|
||||
schema: State<Schema>,
|
||||
) -> super::GraphQLResponse {
|
||||
request.execute(&schema, &context)
|
||||
}
|
||||
|
||||
struct TestRocketIntegration {
|
||||
rocket: Rocket,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
impl http_tests::HTTPIntegration for TestRocketIntegration
|
||||
{
|
||||
fn get(&self, url: &str) -> http_tests::TestResponse {
|
||||
make_test_response(&self.rocket, MockRequest::new(
|
||||
Method::Get,
|
||||
url))
|
||||
}
|
||||
|
||||
fn post(&self, url: &str, body: &str) -> http_tests::TestResponse {
|
||||
make_test_response(
|
||||
&self.rocket,
|
||||
MockRequest::new(
|
||||
Method::Post,
|
||||
url,
|
||||
).header(ContentType::JSON).body(body))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rocket_integration() {
|
||||
let integration = TestRocketIntegration {
|
||||
rocket: make_rocket(),
|
||||
};
|
||||
|
||||
http_tests::run_http_test_suite(&integration);
|
||||
}
|
||||
|
||||
fn make_rocket() -> Rocket {
|
||||
rocket::ignite()
|
||||
.manage(Database::new())
|
||||
.manage(Schema::new(Database::new(), EmptyMutation::<Database>::new()))
|
||||
.mount("/", routes![post_graphql_handler, get_graphql_handler])
|
||||
}
|
||||
|
||||
fn make_test_response<'r>(rocket: &'r Rocket, mut request: MockRequest<'r>) -> http_tests::TestResponse {
|
||||
let mut response = request.dispatch_with(&rocket);
|
||||
let status_code = response.status().code as i32;
|
||||
let content_type = response.header_values("content-type").collect::<Vec<_>>().into_iter().next()
|
||||
.expect("No content type header from handler").to_owned();
|
||||
let body = response.body().expect("No body returned from GraphQL handler").into_string();
|
||||
|
||||
http_tests::TestResponse {
|
||||
status_code: status_code,
|
||||
body: body,
|
||||
content_type: content_type,
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
|
@ -3,9 +3,8 @@ name = "juniper_tests"
|
|||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
juniper = { path = "../juniper" }
|
||||
juniper_codegen = { path = "../juniper_codegen" }
|
||||
serde_json = { version = "^1.0.2" }
|
||||
juniper = { version = "0.9.0", path = "../juniper" }
|
||||
serde_json = { version = "1" }
|
||||
|
||||
[dev-dependencies]
|
||||
fnv = "1.0.3"
|
||||
|
|
|
@ -9,7 +9,7 @@ use juniper::{self, FromInputValue, GraphQLType, InputValue, ToInputValue};
|
|||
enum SomeEnum {
|
||||
Regular,
|
||||
|
||||
#[graphql(name = "FULL", description = "field descr", deprecated = "depr")]
|
||||
#[graphql(name = "full", description = "field descr", deprecated = "depr")]
|
||||
Full,
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,9 @@ fn test_derived_enum() {
|
|||
);
|
||||
|
||||
// Test FULL variant.
|
||||
assert_eq!(SomeEnum::Full.to_input_value(), InputValue::String("FULL".into()));
|
||||
assert_eq!(SomeEnum::Full.to_input_value(), InputValue::String("full".into()));
|
||||
assert_eq!(
|
||||
FromInputValue::from_input_value(&InputValue::String("FULL".into())),
|
||||
FromInputValue::from_input_value(&InputValue::String("full".into())),
|
||||
Some(SomeEnum::Full)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use fnv::FnvHashMap;
|
||||
|
||||
#[cfg(test)]
|
||||
use juniper::{self, FromInputValue, GraphQLType, ToInputValue};
|
||||
use juniper::{self, FromInputValue, GraphQLType, InputValue};
|
||||
|
||||
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
||||
#[graphql(name = "MyInput", description = "input descr")]
|
||||
|
@ -10,6 +10,9 @@ struct Input {
|
|||
regular_field: String,
|
||||
#[graphql(name = "haha", default = "33", description = "haha descr")]
|
||||
c: i32,
|
||||
|
||||
#[graphql(default)]
|
||||
other: Option<bool>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -22,10 +25,31 @@ fn test_derived_input_object() {
|
|||
assert_eq!(meta.name(), Some("MyInput"));
|
||||
assert_eq!(meta.description(), Some(&"input descr".to_string()));
|
||||
|
||||
let obj = Input {
|
||||
regular_field: "a".to_string(),
|
||||
// Test default value injection.
|
||||
|
||||
let input_no_defaults: InputValue = ::serde_json::from_value(json!({
|
||||
"regularField": "a",
|
||||
})).unwrap();
|
||||
|
||||
let output_no_defaults: Input = FromInputValue::from_input_value(&input_no_defaults).unwrap();
|
||||
assert_eq!(output_no_defaults, Input{
|
||||
regular_field: "a".into(),
|
||||
c: 33,
|
||||
};
|
||||
let restored: Input = FromInputValue::from_input_value(&obj.to_input_value()).unwrap();
|
||||
assert_eq!(obj, restored);
|
||||
other: None,
|
||||
});
|
||||
|
||||
// Test with all values supplied.
|
||||
|
||||
let input: InputValue = ::serde_json::from_value(json!({
|
||||
"regularField": "a",
|
||||
"haha": 55,
|
||||
"other": true,
|
||||
})).unwrap();
|
||||
|
||||
let output: Input = FromInputValue::from_input_value(&input).unwrap();
|
||||
assert_eq!(output, Input{
|
||||
regular_field: "a".into(),
|
||||
c: 55,
|
||||
other: Some(true),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@ struct Obj {
|
|||
c: i32,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject, Debug, PartialEq)]
|
||||
struct Nested {
|
||||
obj: Obj,
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
graphql_object!(Query: () |&self| {
|
||||
|
@ -21,6 +26,15 @@ graphql_object!(Query: () |&self| {
|
|||
c: 22,
|
||||
}
|
||||
}
|
||||
|
||||
field nested() -> Nested {
|
||||
Nested{
|
||||
obj: Obj{
|
||||
regular_field: false,
|
||||
c: 333,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#[test]
|
||||
|
@ -54,3 +68,30 @@ fn test_derived_object() {
|
|||
].into_iter().collect()),
|
||||
vec![])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_derived_object_nested() {
|
||||
let doc = r#"
|
||||
{
|
||||
nested {
|
||||
obj {
|
||||
regularField
|
||||
renamedField
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
let schema = RootNode::new(Query, EmptyMutation::<()>::new());
|
||||
|
||||
assert_eq!(
|
||||
execute(doc, None, &schema, &Variables::new(), &()),
|
||||
Ok((Value::object(vec![
|
||||
("nested", Value::object(vec![
|
||||
("obj", Value::object(vec![
|
||||
("regularField", Value::boolean(false)),
|
||||
("renamedField", Value::int(333)),
|
||||
].into_iter().collect())
|
||||
)].into_iter().collect())),
|
||||
].into_iter().collect()),
|
||||
vec![])));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate juniper;
|
||||
#[macro_use]
|
||||
extern crate juniper_codegen;
|
||||
extern crate serde_json;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Reference in a new issue