Merge pull request #333 from theduke/graphql-object-proc-macro
Graphql object proc macro
This commit is contained in:
commit
61f288b54c
60 changed files with 2788 additions and 1023 deletions
|
@ -5,9 +5,9 @@ rust:
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
|
|
||||||
|
# TODO: re-enable once new versions are released.
|
||||||
# Prevent accidentally breaking older Rust versions
|
# Prevent accidentally breaking older Rust versions
|
||||||
- 1.32.0
|
# - 1.33.0
|
||||||
- 1.31.0
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
|
|
@ -10,10 +10,11 @@ jobs:
|
||||||
rustup_toolchain: beta
|
rustup_toolchain: beta
|
||||||
nightly:
|
nightly:
|
||||||
rustup_toolchain: nightly
|
rustup_toolchain: nightly
|
||||||
minimum_supported_version_plus_one:
|
# TODO: re-enable once new versions are released.
|
||||||
rustup_toolchain: 1.32.0
|
# minimum_supported_version_plus_one:
|
||||||
minimum_supported_version:
|
# rustup_toolchain: 1.32.0
|
||||||
rustup_toolchain: 1.31.0
|
#minimum_supported_version:
|
||||||
|
# rustup_toolchain: 1.33.0
|
||||||
steps:
|
steps:
|
||||||
- ${{ if ne(parameters.name, 'Windows') }}:
|
- ${{ if ne(parameters.name, 'Windows') }}:
|
||||||
# Linux and macOS.
|
# Linux and macOS.
|
||||||
|
|
|
@ -30,9 +30,6 @@ result can then be converted to JSON for use with tools and libraries such as
|
||||||
[graphql-client](https://github.com/graphql-rust/graphql-client):
|
[graphql-client](https://github.com/graphql-rust/graphql-client):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# // Only needed due to 2018 edition because the macro is not accessible.
|
|
||||||
# extern crate juniper;
|
|
||||||
# extern crate serde_json;
|
|
||||||
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
|
||||||
|
|
||||||
// Define our schema.
|
// Define our schema.
|
||||||
|
@ -47,11 +44,14 @@ impl juniper::Context for Context {}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Context |&self| {
|
#[juniper::object(
|
||||||
field example(&executor, id: String) -> FieldResult<Example> {
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
fn example(id: String) -> FieldResult<Example> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>>;
|
||||||
|
|
||||||
|
|
|
@ -23,21 +23,22 @@ enum SignUpResult {
|
||||||
Error(Vec<ValidationError>),
|
Error(Vec<ValidationError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(SignUpResult: () |&self| {
|
#[juniper::object]
|
||||||
field user() -> Option<&User> {
|
impl SignUpResult {
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
match *self {
|
match *self {
|
||||||
SignUpResult::Ok(ref user) => Some(user),
|
SignUpResult::Ok(ref user) => Some(user),
|
||||||
SignUpResult::Error(_) => None,
|
SignUpResult::Error(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
match *self {
|
match *self {
|
||||||
SignUpResult::Ok(_) => None,
|
SignUpResult::Ok(_) => None,
|
||||||
SignUpResult::Error(ref errors) => Some(errors)
|
SignUpResult::Error(ref errors) => Some(errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
|
@ -25,25 +25,31 @@ struct ValidationError {
|
||||||
# #[allow(dead_code)]
|
# #[allow(dead_code)]
|
||||||
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
struct MutationResult<T>(Result<T, Vec<ValidationError>>);
|
||||||
|
|
||||||
juniper::graphql_object!(MutationResult<User>: () as "UserResult" |&self| {
|
#[juniper::object(
|
||||||
field user() -> Option<&User> {
|
name = "UserResult",
|
||||||
|
)]
|
||||||
|
impl MutationResult<User> {
|
||||||
|
fn user(&self) -> Option<&User> {
|
||||||
self.0.as_ref().ok()
|
self.0.as_ref().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
self.0.as_ref().err()
|
self.0.as_ref().err()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(MutationResult<ForumPost>: () as "ForumPostResult" |&self| {
|
#[juniper::object(
|
||||||
field forum_post() -> Option<&ForumPost> {
|
name = "ForumPostResult",
|
||||||
|
)]
|
||||||
|
impl MutationResult<ForumPost> {
|
||||||
|
fn forum_post(&self) -> Option<&ForumPost> {
|
||||||
self.0.as_ref().ok()
|
self.0.as_ref().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
field error() -> Option<&Vec<ValidationError>> {
|
fn error(&self) -> Option<&Vec<ValidationError>> {
|
||||||
self.0.as_ref().err()
|
self.0.as_ref().err()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
|
@ -20,7 +20,7 @@ naturally map to GraphQL features, such as `Option<T>`, `Vec<T>`, `Box<T>`,
|
||||||
|
|
||||||
For more advanced mappings, Juniper provides multiple macros to map your Rust
|
For more advanced mappings, Juniper provides multiple macros to map your Rust
|
||||||
types to a GraphQL schema. The most important one is the
|
types to a GraphQL schema. The most important one is the
|
||||||
[graphql_object!][jp_obj_macro] macro that is used for declaring an object with
|
[object][jp_object] procedural macro that is used for declaring an object with
|
||||||
resolvers, which you will use for the `Query` and `Mutation` roots.
|
resolvers, which you will use for the `Query` and `Mutation` roots.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
@ -60,7 +60,7 @@ struct NewHuman {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we create our root Query and Mutation types with resolvers by using the
|
// Now, we create our root Query and Mutation types with resolvers by using the
|
||||||
// graphql_object! macro.
|
// object macro.
|
||||||
// Objects can have contexts that allow accessing shared state like a database
|
// Objects can have contexts that allow accessing shared state like a database
|
||||||
// pool.
|
// pool.
|
||||||
|
|
||||||
|
@ -74,17 +74,23 @@ impl juniper::Context for Context {}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Context |&self| {
|
#[juniper::object(
|
||||||
|
// Here we specify the context type for the object.
|
||||||
|
// We need to do this in every type that
|
||||||
|
// needs access to the context.
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
|
||||||
field apiVersion() -> &str {
|
fn apiVersion() -> &str {
|
||||||
"1.0"
|
"1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments to resolvers can either be simple types or input objects.
|
// Arguments to resolvers can either be simple types or input objects.
|
||||||
// The executor is a special (optional) argument that allows accessing the context.
|
// To gain access to the context, we specify a argument
|
||||||
field human(&executor, id: String) -> FieldResult<Human> {
|
// that is a reference to the Context type.
|
||||||
// Get the context from the executor.
|
// Juniper automatically injects the correct context here.
|
||||||
let context = executor.context();
|
fn human(context: &Context, id: String) -> FieldResult<Human> {
|
||||||
// Get a db connection.
|
// Get a db connection.
|
||||||
let connection = context.pool.get_connection()?;
|
let connection = context.pool.get_connection()?;
|
||||||
// Execute a db query.
|
// Execute a db query.
|
||||||
|
@ -93,18 +99,23 @@ juniper::graphql_object!(Query: Context |&self| {
|
||||||
// Return the result.
|
// Return the result.
|
||||||
Ok(human)
|
Ok(human)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// Now, we do the same for our Mutation type.
|
||||||
|
|
||||||
struct Mutation;
|
struct Mutation;
|
||||||
|
|
||||||
juniper::graphql_object!(Mutation: Context |&self| {
|
#[juniper::object(
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Mutation {
|
||||||
|
|
||||||
field createHuman(&executor, new_human: NewHuman) -> FieldResult<Human> {
|
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human> {
|
||||||
let db = executor.context().pool.get_connection()?;
|
let db = executor.context().pool.get_connection()?;
|
||||||
let human: Human = db.insert_human(&new_human)?;
|
let human: Human = db.insert_human(&new_human)?;
|
||||||
Ok(human)
|
Ok(human)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// A root schema consists of a query and a mutation.
|
// A root schema consists of a query and a mutation.
|
||||||
// Request queries can be executed against a RootNode.
|
// Request queries can be executed against a RootNode.
|
||||||
|
@ -130,6 +141,7 @@ You can invoke `juniper::execute` directly to run a GraphQL query:
|
||||||
# #[macro_use] extern crate juniper;
|
# #[macro_use] extern crate juniper;
|
||||||
use juniper::{FieldResult, Variables, EmptyMutation};
|
use juniper::{FieldResult, Variables, EmptyMutation};
|
||||||
|
|
||||||
|
|
||||||
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
#[derive(juniper::GraphQLEnum, Clone, Copy)]
|
||||||
enum Episode {
|
enum Episode {
|
||||||
NewHope,
|
NewHope,
|
||||||
|
@ -137,18 +149,23 @@ enum Episode {
|
||||||
Jedi,
|
Jedi,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Query;
|
|
||||||
|
|
||||||
juniper::graphql_object!(Query: Ctx |&self| {
|
|
||||||
field favoriteEpisode(&executor) -> FieldResult<Episode> {
|
|
||||||
// Use the special &executor argument to fetch our fav episode.
|
|
||||||
Ok(executor.context().0)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Arbitrary context data.
|
// Arbitrary context data.
|
||||||
struct Ctx(Episode);
|
struct Ctx(Episode);
|
||||||
|
|
||||||
|
impl juniper::Context for Ctx {}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[juniper::object(
|
||||||
|
Context = Ctx,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
fn favoriteEpisode(context: &Ctx) -> FieldResult<Episode> {
|
||||||
|
Ok(context.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A root schema consists of a query and a mutation.
|
// A root schema consists of a query and a mutation.
|
||||||
// Request queries can be executed against a RootNode.
|
// Request queries can be executed against a RootNode.
|
||||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;
|
||||||
|
@ -181,4 +198,4 @@ fn main() {
|
||||||
[rocket]: servers/rocket.md
|
[rocket]: servers/rocket.md
|
||||||
[iron]: servers/iron.md
|
[iron]: servers/iron.md
|
||||||
[tutorial]: ./tutorial.html
|
[tutorial]: ./tutorial.html
|
||||||
[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.graphql_object.html
|
[jp_obj_macro]: https://docs.rs/juniper/latest/juniper/macro.object.html
|
||||||
|
|
|
@ -20,19 +20,20 @@ object somewhere but never references it, it will not be exposed in a schema.
|
||||||
## The query root
|
## The query root
|
||||||
|
|
||||||
The query root is just a GraphQL object. You define it like any other GraphQL
|
The query root is just a GraphQL object. You define it like any other GraphQL
|
||||||
object in Juniper, most commonly using the `graphql_object!` macro:
|
object in Juniper, most commonly using the `object` proc macro:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# use juniper::FieldResult;
|
# use juniper::FieldResult;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field userWithUsername(username: String) -> FieldResult<Option<User>> {
|
impl Root {
|
||||||
|
fn userWithUsername(username: String) -> FieldResult<Option<User>> {
|
||||||
// Look up user in database...
|
// Look up user in database...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
```
|
```
|
||||||
|
@ -47,12 +48,13 @@ usually performs some mutating side-effect, such as updating a database.
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
struct Mutations;
|
struct Mutations;
|
||||||
|
|
||||||
juniper::graphql_object!(Mutations: () |&self| {
|
#[juniper::object]
|
||||||
field signUpUser(name: String, email: String) -> FieldResult<User> {
|
impl Mutations {
|
||||||
|
fn signUpUser(name: String, email: String) -> FieldResult<User> {
|
||||||
// Validate inputs and save user in database...
|
// Validate inputs and save user in database...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
```
|
```
|
||||||
|
|
|
@ -47,11 +47,12 @@ fn context_factory(_: &mut Request) -> IronResult<()> {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field foo() -> String {
|
impl Root {
|
||||||
|
fn foo() -> String {
|
||||||
"Bar".to_owned()
|
"Bar".to_owned()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# #[allow(unreachable_code, unused_variables)]
|
# #[allow(unreachable_code, unused_variables)]
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -98,13 +99,14 @@ fn context_factory(req: &mut Request) -> IronResult<Context> {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
juniper::graphql_object!(Root: Context |&self| {
|
#[juniper::object(
|
||||||
field my_addr(&executor) -> String {
|
Context = Context,
|
||||||
let context = executor.context();
|
)]
|
||||||
|
impl Root {
|
||||||
|
field my_addr(context: &Context) -> String {
|
||||||
format!("Hello, you're coming from {}", context.remote_addr)
|
format!("Hello, you're coming from {}", context.remote_addr)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {
|
# fn main() {
|
||||||
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
|
||||||
|
@ -115,10 +117,6 @@ juniper::graphql_object!(Root: Context |&self| {
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Accessing global data
|
|
||||||
|
|
||||||
FIXME: Show how the `persistent` crate works with contexts using e.g. `r2d2`.
|
|
||||||
|
|
||||||
[iron]: http://ironframework.io
|
[iron]: http://ironframework.io
|
||||||
[graphiql]: https://github.com/graphql/graphiql
|
[graphiql]: https://github.com/graphql/graphiql
|
||||||
[mount]: https://github.com/iron/mount
|
[mount]: https://github.com/iron/mount
|
||||||
|
|
|
@ -14,12 +14,14 @@ struct Coordinate {
|
||||||
struct Root;
|
struct Root;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
impl Root {
|
||||||
|
fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
|
||||||
// Send coordinate to database
|
// Send coordinate to database
|
||||||
|
// ...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
@ -43,12 +45,14 @@ struct WorldCoordinate {
|
||||||
struct Root;
|
struct Root;
|
||||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||||
|
|
||||||
juniper::graphql_object!(Root: () |&self| {
|
#[juniper::object]
|
||||||
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
impl Root {
|
||||||
|
fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
|
||||||
// Send coordinate to database
|
// Send coordinate to database
|
||||||
|
// ...
|
||||||
# unimplemented!()
|
# unimplemented!()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl Character for Droid {
|
||||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S>|&self| {
|
juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
|
||||||
field id() -> &str { self.id() }
|
field id() -> &str { self.id() }
|
||||||
|
|
||||||
instance_resolvers: |_| {
|
instance_resolvers: |_| {
|
||||||
|
@ -79,14 +79,14 @@ we'll use two hashmaps, but this could be two tables and some SQL calls instead:
|
||||||
```rust
|
```rust
|
||||||
# use std::collections::HashMap;
|
# use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
If you've got a struct that can't be mapped directly to GraphQL, that contains
|
If you've got a struct that can't be mapped directly to GraphQL, that contains
|
||||||
computed fields or circular structures, you have to use a more powerful tool:
|
computed fields or circular structures, you have to use a more powerful tool:
|
||||||
the `graphql_object!` macro. This macro lets you define GraphQL objects similar
|
the `object` procedural macro. This macro lets you define GraphQL object
|
||||||
to how you define methods in a Rust `impl` block for a type. Continuing with the
|
fields in a Rust `impl` block for a type. Continuing with the
|
||||||
example from the last chapter, this is how you would define `Person` using the
|
example from the last chapter, this is how you would define `Person` using the
|
||||||
macro:
|
macro:
|
||||||
|
|
||||||
|
@ -14,15 +14,16 @@ struct Person {
|
||||||
age: i32,
|
age: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Person: () |&self| {
|
#[juniper::object]
|
||||||
field name() -> &str {
|
impl Person {
|
||||||
|
fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
field age() -> i32 {
|
fn age(&self) -> i32 {
|
||||||
self.age
|
self.age
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
```
|
```
|
||||||
|
@ -42,12 +43,13 @@ struct House {
|
||||||
inhabitants: Vec<Person>,
|
inhabitants: Vec<Person>,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(House: () |&self| {
|
#[juniper::object]
|
||||||
|
impl House {
|
||||||
// Creates the field inhabitantWithName(name), returning a nullable person
|
// Creates the field inhabitantWithName(name), returning a nullable person
|
||||||
field inhabitant_with_name(name: String) -> Option<&Person> {
|
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
|
||||||
self.inhabitants.iter().find(|p| p.name == name)
|
self.inhabitants.iter().find(|p| p.name == name)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
@ -68,15 +70,19 @@ struct Person {
|
||||||
website_url: String,
|
website_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Person: () as "PersonObject" |&self| {
|
#[juniper::object(
|
||||||
field name() -> &str {
|
// With this attribtue you can change the public GraphQL name of the type.
|
||||||
|
name = "PersonObject",
|
||||||
|
)]
|
||||||
|
impl Person {
|
||||||
|
fn name(&self) -> &str {
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
field websiteURL() -> &str {
|
fn websiteURL(&self) -> &str {
|
||||||
self.website_url.as_str()
|
self.website_url.as_str()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
```
|
```
|
||||||
|
@ -90,4 +96,4 @@ GraphQL fields expose more features than Rust's standard method syntax gives us:
|
||||||
* Per-argument descriptions
|
* Per-argument descriptions
|
||||||
|
|
||||||
These, and more features, are described more thorougly in [the reference
|
These, and more features, are described more thorougly in [the reference
|
||||||
documentation](https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html).
|
documentation](https://docs.rs/juniper/latest/juniper/macro.object.html).
|
||||||
|
|
|
@ -154,7 +154,7 @@ attribute:
|
||||||
struct Person {
|
struct Person {
|
||||||
name: String,
|
name: String,
|
||||||
age: i32,
|
age: i32,
|
||||||
#[graphql(deprecation="Please use the name field instead")]
|
#[graphql(deprecated = "Please use the name field instead")]
|
||||||
first_name: String,
|
first_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,16 @@ struct Example {
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Example: () |&self| {
|
#[juniper::object]
|
||||||
field contents() -> FieldResult<String> {
|
impl Example {
|
||||||
|
fn contents() -> FieldResult<String> {
|
||||||
let mut file = File::open(&self.filename)?;
|
let mut file = File::open(&self.filename)?;
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
field foo() -> FieldResult<Option<String>> {
|
|
||||||
|
fn foo() -> FieldResult<Option<String>> {
|
||||||
// Some invalid bytes.
|
// Some invalid bytes.
|
||||||
let invalid = vec![128, 223];
|
let invalid = vec![128, 223];
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ juniper::graphql_object!(Example: () |&self| {
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
@ -141,14 +143,15 @@ struct Example {
|
||||||
whatever: Option<bool>,
|
whatever: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Example: () |&self| {
|
#[juniper::object]
|
||||||
field whatever() -> Result<bool, CustomError> {
|
impl Example {
|
||||||
|
fn whatever() -> Result<bool, CustomError> {
|
||||||
if let Some(value) = self.whatever {
|
if let Some(value) = self.whatever {
|
||||||
return Ok(value);
|
return Ok(value);
|
||||||
}
|
}
|
||||||
Err(CustomError::WhateverNotSet)
|
Err(CustomError::WhateverNotSet)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
|
@ -31,42 +31,57 @@ We would like a `friends` field on `User` that returns a list of `User` objects.
|
||||||
In order to write such a field though, the database must be queried.
|
In order to write such a field though, the database must be queried.
|
||||||
|
|
||||||
To solve this, we mark the `Database` as a valid context type and assign it to
|
To solve this, we mark the `Database` as a valid context type and assign it to
|
||||||
the user object. Then, we use the special `&executor` argument to access the
|
the user object.
|
||||||
current context object:
|
|
||||||
|
To gain access to the context, we need to specify an argument with the same
|
||||||
|
type as the specified `Context` for the type:
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# use std::collections::HashMap;
|
# use std::collections::HashMap;
|
||||||
extern crate juniper;
|
extern crate juniper;
|
||||||
|
|
||||||
|
// This struct represents our context.
|
||||||
struct Database {
|
struct Database {
|
||||||
users: HashMap<i32, User>,
|
users: HashMap<i32, User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the Database as a valid context type for Juniper
|
||||||
|
impl juniper::Context for Database {}
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
id: i32,
|
id: i32,
|
||||||
name: String,
|
name: String,
|
||||||
friend_ids: Vec<i32>,
|
friend_ids: Vec<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Mark the Database as a valid context type for Juniper
|
|
||||||
impl juniper::Context for Database {}
|
|
||||||
|
|
||||||
// 2. Assign Database as the context type for User
|
// Assign Database as the context type for User
|
||||||
juniper::graphql_object!(User: Database |&self| {
|
#[juniper::object(
|
||||||
// 3. Use the special executor argument
|
Context = Database,
|
||||||
field friends(&executor) -> Vec<&User> {
|
)]
|
||||||
// 4. Use the executor to access the context object
|
impl User {
|
||||||
let database = executor.context();
|
// 3. Inject the context by specifying an argument
|
||||||
|
// with the context type.
|
||||||
|
// Note:
|
||||||
|
// - the type must be a reference
|
||||||
|
// - the name of the argument SHOULD be context
|
||||||
|
fn friends(&self, context: &Database) -> Vec<&User> {
|
||||||
|
|
||||||
// 5. Use the database to lookup users
|
// 5. Use the database to lookup users
|
||||||
self.friend_ids.iter()
|
self.friend_ids.iter()
|
||||||
.map(|id| database.users.get(id).expect("Could not find user with ID"))
|
.map(|id| context.users.get(id).expect("Could not find user with ID"))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
field name() -> &str { self.name.as_str() }
|
fn name(&self) -> &str {
|
||||||
field id() -> i32 { self.id }
|
self.name.as_str()
|
||||||
});
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> i32 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# fn main() { }
|
# fn main() { }
|
||||||
```
|
```
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl Character for Droid {
|
||||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
|
juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
|
||||||
instance_resolvers: |_| {
|
instance_resolvers: |_| {
|
||||||
// The left hand side indicates the concrete type T, the right hand
|
// The left hand side indicates the concrete type T, the right hand
|
||||||
// side should be an expression returning Option<T>
|
// side should be an expression returning Option<T>
|
||||||
|
@ -61,14 +61,14 @@ FIXME: This example does not compile at the moment
|
||||||
```rust
|
```rust
|
||||||
# use std::collections::HashMap;
|
# use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
@ -108,14 +108,14 @@ juniper::graphql_union!(<'a> &'a Character: Database as "Character" where Scalar
|
||||||
```rust
|
```rust
|
||||||
# use std::collections::HashMap;
|
# use std::collections::HashMap;
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Human {
|
struct Human {
|
||||||
id: String,
|
id: String,
|
||||||
home_planet: String,
|
home_planet: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(juniper::GraphQLObject)]
|
#[derive(juniper::GraphQLObject)]
|
||||||
#[graphql(Context = "Database")]
|
#[graphql(Context = Database)]
|
||||||
struct Droid {
|
struct Droid {
|
||||||
id: String,
|
id: String,
|
||||||
primary_function: String,
|
primary_function: String,
|
||||||
|
|
|
@ -6,13 +6,14 @@ edition = "2018"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
juniper = { version = "0.11", path = "../../../juniper" }
|
juniper = { path = "../../../juniper" }
|
||||||
juniper_iron = { version = "0.3", path = "../../../juniper_iron" }
|
juniper_iron = { path = "../../../juniper_iron" }
|
||||||
|
|
||||||
iron = "^0.5.0"
|
iron = "^0.5.0"
|
||||||
mount = "^0.3.0"
|
mount = "^0.3.0"
|
||||||
|
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
|
serde_json = "1.0.39"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
|
|
|
@ -4,15 +4,7 @@ version = "0.1.0"
|
||||||
publish = false
|
publish = false
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
juniper = { version = "0.11.0", path = "../../juniper" }
|
|
||||||
serde_json = { version = "1" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
juniper = { path = "../../juniper" }
|
||||||
|
serde_json = { version = "1" }
|
||||||
fnv = "1.0.3"
|
fnv = "1.0.3"
|
||||||
indexmap = "1.0"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "integration_tests"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
harness = true
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ enum DocEnum {
|
||||||
Foo,
|
Foo,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Doc 1.
|
/// Doc 1.\
|
||||||
/// Doc 2.
|
/// Doc 2.
|
||||||
///
|
///
|
||||||
/// Doc 4.
|
/// Doc 4.
|
||||||
|
@ -85,7 +85,7 @@ fn test_multi_doc_comment() {
|
||||||
let meta = MultiDocEnum::meta(&(), &mut registry);
|
let meta = MultiDocEnum::meta(&(), &mut registry);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
meta.description(),
|
meta.description(),
|
||||||
Some(&"Doc 1. Doc 2.\nDoc 4.".to_string())
|
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ struct DocComment {
|
||||||
regular_field: bool,
|
regular_field: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Doc 1.
|
/// Doc 1.\
|
||||||
/// Doc 2.
|
/// Doc 2.
|
||||||
///
|
///
|
||||||
/// Doc 4.
|
/// Doc 4.
|
||||||
|
@ -151,7 +151,7 @@ fn test_multi_doc_comment() {
|
||||||
let meta = MultiDocComment::meta(&(), &mut registry);
|
let meta = MultiDocComment::meta(&(), &mut registry);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
meta.description(),
|
meta.description(),
|
||||||
Some(&"Doc 1. Doc 2.\nDoc 4.".to_string())
|
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,20 +12,20 @@ use juniper::{self, execute, EmptyMutation, GraphQLType, RootNode, Value, Variab
|
||||||
#[graphql(
|
#[graphql(
|
||||||
name = "MyObj",
|
name = "MyObj",
|
||||||
description = "obj descr",
|
description = "obj descr",
|
||||||
scalar = "DefaultScalarValue"
|
scalar = DefaultScalarValue
|
||||||
)]
|
)]
|
||||||
struct Obj {
|
struct Obj {
|
||||||
regular_field: bool,
|
regular_field: bool,
|
||||||
#[graphql(
|
#[graphql(
|
||||||
name = "renamedField",
|
name = "renamedField",
|
||||||
description = "descr",
|
description = "descr",
|
||||||
deprecation = "field descr"
|
deprecated = "field deprecation"
|
||||||
)]
|
)]
|
||||||
c: i32,
|
c: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(GraphQLObject, Debug, PartialEq)]
|
#[derive(GraphQLObject, Debug, PartialEq)]
|
||||||
#[graphql(scalar = "DefaultScalarValue")]
|
#[graphql(scalar = DefaultScalarValue)]
|
||||||
struct Nested {
|
struct Nested {
|
||||||
obj: Obj,
|
obj: Obj,
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ struct DocComment {
|
||||||
regular_field: bool,
|
regular_field: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Doc 1.
|
/// Doc 1.\
|
||||||
/// Doc 2.
|
/// Doc 2.
|
||||||
///
|
///
|
||||||
/// Doc 4.
|
/// Doc 4.
|
||||||
|
@ -75,56 +75,57 @@ struct Context;
|
||||||
impl juniper::Context for Context {}
|
impl juniper::Context for Context {}
|
||||||
|
|
||||||
#[derive(GraphQLObject, Debug)]
|
#[derive(GraphQLObject, Debug)]
|
||||||
#[graphql(Context = "Context")]
|
#[graphql(Context = Context)]
|
||||||
struct WithCustomContext {
|
struct WithCustomContext {
|
||||||
a: bool,
|
a: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Query: () |&self| {
|
#[juniper::object]
|
||||||
field obj() -> Obj {
|
impl Query {
|
||||||
Obj{
|
fn obj() -> Obj {
|
||||||
regular_field: true,
|
Obj {
|
||||||
c: 22,
|
regular_field: true,
|
||||||
}
|
c: 22,
|
||||||
}
|
|
||||||
|
|
||||||
field nested() -> Nested {
|
|
||||||
Nested{
|
|
||||||
obj: Obj{
|
|
||||||
regular_field: false,
|
|
||||||
c: 333,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field doc() -> DocComment {
|
fn nested() -> Nested {
|
||||||
DocComment{
|
Nested {
|
||||||
regular_field: true,
|
obj: Obj {
|
||||||
}
|
regular_field: false,
|
||||||
|
c: 333,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field multi_doc() -> MultiDocComment {
|
fn doc() -> DocComment {
|
||||||
MultiDocComment{
|
DocComment {
|
||||||
regular_field: true,
|
regular_field: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field override_doc() -> OverrideDocComment {
|
fn multi_doc() -> MultiDocComment {
|
||||||
OverrideDocComment{
|
MultiDocComment {
|
||||||
regular_field: true,
|
regular_field: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field skipped_field_obj() -> SkippedFieldObj {
|
fn override_doc() -> OverrideDocComment {
|
||||||
SkippedFieldObj{
|
OverrideDocComment {
|
||||||
|
regular_field: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skipped_field_obj() -> SkippedFieldObj {
|
||||||
|
SkippedFieldObj {
|
||||||
regular_field: false,
|
regular_field: false,
|
||||||
skipped: 42,
|
skipped: 42,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_doc_comment() {
|
fn test_doc_comment_simple() {
|
||||||
let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
|
let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
|
||||||
let meta = DocComment::meta(&(), &mut registry);
|
let meta = DocComment::meta(&(), &mut registry);
|
||||||
assert_eq!(meta.description(), Some(&"Object comment.".to_string()));
|
assert_eq!(meta.description(), Some(&"Object comment.".to_string()));
|
||||||
|
@ -143,14 +144,14 @@ fn test_multi_doc_comment() {
|
||||||
let meta = MultiDocComment::meta(&(), &mut registry);
|
let meta = MultiDocComment::meta(&(), &mut registry);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
meta.description(),
|
meta.description(),
|
||||||
Some(&"Doc 1. Doc 2.\nDoc 4.".to_string())
|
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
check_descriptions(
|
check_descriptions(
|
||||||
"MultiDocComment",
|
"MultiDocComment",
|
||||||
&Value::scalar("Doc 1. Doc 2.\nDoc 4."),
|
&Value::scalar("Doc 1. Doc 2.\n\nDoc 4."),
|
||||||
"regularField",
|
"regularField",
|
||||||
&Value::scalar("Field 1. Field 2."),
|
&Value::scalar("Field 1.\nField 2."),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod util;
|
||||||
|
|
||||||
mod derive_enum;
|
mod derive_enum;
|
||||||
mod derive_input_object;
|
mod derive_input_object;
|
||||||
mod derive_object;
|
mod derive_object;
|
||||||
|
|
54
integration_tests/juniper_tests/src/codegen/util.rs
Normal file
54
integration_tests/juniper_tests/src/codegen/util.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use juniper::{DefaultScalarValue, GraphQLType, RootNode, Value, Variables};
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
pub fn run_query<Query, Mutation, Context>(query: &str) -> Value
|
||||||
|
where
|
||||||
|
Query: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Mutation: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Context: Default,
|
||||||
|
{
|
||||||
|
let schema = RootNode::new(Query::default(), Mutation::default());
|
||||||
|
let (result, errs) =
|
||||||
|
juniper::execute(query, None, &schema, &Variables::new(), &Context::default())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_info_query<Query, Mutation, Context>(type_name: &str) -> Value
|
||||||
|
where
|
||||||
|
Query: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Mutation: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Context: Default,
|
||||||
|
{
|
||||||
|
let query = format!(
|
||||||
|
r#"
|
||||||
|
{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
fields {{
|
||||||
|
name
|
||||||
|
description
|
||||||
|
args {{
|
||||||
|
name
|
||||||
|
description
|
||||||
|
type {{
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
type_name
|
||||||
|
);
|
||||||
|
let result = run_query::<Query, Mutation, Context>(&query);
|
||||||
|
result
|
||||||
|
.as_object_value()
|
||||||
|
.expect("Result is not an object")
|
||||||
|
.get_field_value("__type")
|
||||||
|
.expect("__type field missing")
|
||||||
|
.clone()
|
||||||
|
}
|
|
@ -151,15 +151,18 @@ juniper::graphql_scalar!(i64 as "Long" where Scalar = MyScalarValue {
|
||||||
|
|
||||||
struct TestType;
|
struct TestType;
|
||||||
|
|
||||||
juniper::graphql_object!(TestType: () where Scalar = MyScalarValue |&self| {
|
#[juniper::object(
|
||||||
field long_field() -> i64 {
|
Scalar = MyScalarValue
|
||||||
|
)]
|
||||||
|
impl TestType {
|
||||||
|
fn long_field() -> i64 {
|
||||||
(::std::i32::MAX as i64) + 1
|
(::std::i32::MAX as i64) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
field long_with_arg(long_arg: i64) -> i64 {
|
fn long_with_arg(long_arg: i64) -> i64 {
|
||||||
long_arg
|
long_arg
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn run_variable_query<F>(query: &str, vars: Variables<MyScalarValue>, f: F)
|
fn run_variable_query<F>(query: &str, vars: Variables<MyScalarValue>, f: F)
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
extern crate juniper;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate fnv;
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate indexmap;
|
|
||||||
|
|
||||||
mod codegen;
|
mod codegen;
|
||||||
|
#[cfg(test)]
|
||||||
mod custom_scalar;
|
mod custom_scalar;
|
||||||
|
|
|
@ -1,10 +1,27 @@
|
||||||
# master
|
# master
|
||||||
|
|
||||||
- Refactored all crates to the 2018 edition. [#345](https://github.com/graphql-rust/juniper/pull/345)
|
### object macro
|
||||||
- The minimum required Rust version is now `1.31.0`.
|
|
||||||
|
The `graphql_object!` macro is deprecated and will be removed in the future.
|
||||||
|
It is replaced by the new [object](https://docs.rs/juniper/latest/juniper/macro.object.html) procedural macro.
|
||||||
|
|
||||||
|
[#333](https://github.com/graphql-rust/juniper/pull/333)
|
||||||
|
|
||||||
|
### 2018 Edition
|
||||||
|
|
||||||
|
All crates were refactored to the Rust 2018 edition.
|
||||||
|
|
||||||
|
This should not have any impact on your code, since juniper already was 2018 compatible.
|
||||||
|
|
||||||
|
[#345](https://github.com/graphql-rust/juniper/pull/345)
|
||||||
|
|
||||||
|
### Other changes
|
||||||
|
|
||||||
|
- The minimum required Rust version is now `1.34.0`.
|
||||||
- The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`.
|
- The `ScalarValue` custom derive has been renamed to `GraphQLScalarValue`.
|
||||||
- Added built-in support for the canonical schema introspection query via
|
- Added built-in support for the canonical schema introspection query via
|
||||||
`juniper::introspect()`. [#307](https://github.com/graphql-rust/juniper/issues/307)
|
`juniper::introspect()`.
|
||||||
|
[#307](https://github.com/graphql-rust/juniper/issues/307)
|
||||||
- Fix introspection query validity
|
- Fix introspection query validity
|
||||||
The DirectiveLocation::InlineFragment had an invalid literal value,
|
The DirectiveLocation::InlineFragment had an invalid literal value,
|
||||||
which broke third party tools like apollo cli.
|
which broke third party tools like apollo cli.
|
||||||
|
|
|
@ -23,7 +23,6 @@ harness = false
|
||||||
path = "benches/bench.rs"
|
path = "benches/bench.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
nightly = []
|
|
||||||
expose-test-schema = []
|
expose-test-schema = []
|
||||||
default = [
|
default = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
@ -6,6 +6,8 @@ upload-doc = false
|
||||||
pre-release-replacements = [
|
pre-release-replacements = [
|
||||||
# Juniper's changelog
|
# Juniper's changelog
|
||||||
{file="CHANGELOG.md", search="# master", replace="# master\n\n- No changes yet\n\n# [[{{version}}] {{date}}](https://github.com/graphql-rust/juniper/releases/tag/{{crate_name}}-{{version}})"},
|
{file="CHANGELOG.md", search="# master", replace="# master\n\n- No changes yet\n\n# [[{{version}}] {{date}}](https://github.com/graphql-rust/juniper/releases/tag/{{crate_name}}-{{version}})"},
|
||||||
|
# codegen
|
||||||
|
{file="../juniper_codegen/Cargo.toml", search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""},
|
||||||
# Tests.
|
# Tests.
|
||||||
{file="../integration_tests/juniper_tests/Cargo.toml", search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""},
|
{file="../integration_tests/juniper_tests/Cargo.toml", search="juniper = \\{ version = \"[^\"]+\"", replace="juniper = { version = \"{{version}}\""},
|
||||||
# Hyper
|
# Hyper
|
||||||
|
|
|
@ -5,15 +5,16 @@ use crate::value::{DefaultScalarValue, Object, Value};
|
||||||
|
|
||||||
struct TestType;
|
struct TestType;
|
||||||
|
|
||||||
graphql_object!(TestType: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str {
|
impl TestType {
|
||||||
|
fn a() -> &str {
|
||||||
"a"
|
"a"
|
||||||
}
|
}
|
||||||
|
|
||||||
field b() -> &str {
|
fn b() -> &str {
|
||||||
"b"
|
"b"
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
|
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -17,15 +17,16 @@ enum Color {
|
||||||
}
|
}
|
||||||
struct TestType;
|
struct TestType;
|
||||||
|
|
||||||
graphql_object!(TestType: () |&self| {
|
#[crate::object_internal]
|
||||||
field to_string(color: Color) -> String {
|
impl TestType {
|
||||||
|
fn to_string(color: Color) -> String {
|
||||||
format!("Color::{:?}", color)
|
format!("Color::{:?}", color)
|
||||||
}
|
}
|
||||||
|
|
||||||
field a_color() -> Color {
|
fn a_color() -> Color {
|
||||||
Color::Red
|
Color::Red
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
|
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -7,34 +7,57 @@ mod field_execution {
|
||||||
struct DataType;
|
struct DataType;
|
||||||
struct DeepDataType;
|
struct DeepDataType;
|
||||||
|
|
||||||
graphql_object!(DataType: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str { "Apple" }
|
impl DataType {
|
||||||
field b() -> &str { "Banana" }
|
fn a() -> &str {
|
||||||
field c() -> &str { "Cookie" }
|
"Apple"
|
||||||
field d() -> &str { "Donut" }
|
}
|
||||||
field e() -> &str { "Egg" }
|
fn b() -> &str {
|
||||||
field f() -> &str { "Fish" }
|
"Banana"
|
||||||
|
}
|
||||||
|
fn c() -> &str {
|
||||||
|
"Cookie"
|
||||||
|
}
|
||||||
|
fn d() -> &str {
|
||||||
|
"Donut"
|
||||||
|
}
|
||||||
|
fn e() -> &str {
|
||||||
|
"Egg"
|
||||||
|
}
|
||||||
|
fn f() -> &str {
|
||||||
|
"Fish"
|
||||||
|
}
|
||||||
|
|
||||||
field pic(size: Option<i32>) -> String {
|
fn pic(size: Option<i32>) -> String {
|
||||||
format!("Pic of size: {}", size.unwrap_or(50))
|
format!("Pic of size: {}", size.unwrap_or(50))
|
||||||
}
|
}
|
||||||
|
|
||||||
field deep() -> DeepDataType {
|
fn deep() -> DeepDataType {
|
||||||
DeepDataType
|
DeepDataType
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(DeepDataType: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str { "Already Been Done" }
|
impl DeepDataType {
|
||||||
field b() -> &str { "Boring" }
|
fn a() -> &str {
|
||||||
field c() -> Vec<Option<&str>> { vec![Some("Contrived"), None, Some("Confusing")] }
|
"Already Been Done"
|
||||||
|
}
|
||||||
|
fn b() -> &str {
|
||||||
|
"Boring"
|
||||||
|
}
|
||||||
|
fn c() -> Vec<Option<&str>> {
|
||||||
|
vec![Some("Contrived"), None, Some("Confusing")]
|
||||||
|
}
|
||||||
|
|
||||||
field deeper() -> Vec<Option<DataType>> { vec![Some(DataType), None, Some(DataType) ] }
|
fn deeper() -> Vec<Option<DataType>> {
|
||||||
});
|
vec![Some(DataType), None, Some(DataType)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let schema = RootNode::new(DataType, EmptyMutation::<()>::new());
|
let schema =
|
||||||
|
RootNode::<_, _, crate::DefaultScalarValue>::new(DataType, EmptyMutation::<()>::new());
|
||||||
let doc = r"
|
let doc = r"
|
||||||
query Example($size: Int) {
|
query Example($size: Int) {
|
||||||
a,
|
a,
|
||||||
|
@ -139,12 +162,21 @@ mod merge_parallel_fragments {
|
||||||
|
|
||||||
struct Type;
|
struct Type;
|
||||||
|
|
||||||
graphql_object!(Type: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str { "Apple" }
|
impl Type {
|
||||||
field b() -> &str { "Banana" }
|
fn a() -> &str {
|
||||||
field c() -> &str { "Cherry" }
|
"Apple"
|
||||||
field deep() -> Type { Type }
|
}
|
||||||
});
|
fn b() -> &str {
|
||||||
|
"Banana"
|
||||||
|
}
|
||||||
|
fn c() -> &str {
|
||||||
|
"Cherry"
|
||||||
|
}
|
||||||
|
fn deep() -> Type {
|
||||||
|
Type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
|
@ -214,21 +246,43 @@ mod merge_parallel_inline_fragments {
|
||||||
struct Type;
|
struct Type;
|
||||||
struct Other;
|
struct Other;
|
||||||
|
|
||||||
graphql_object!(Type: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str { "Apple" }
|
impl Type {
|
||||||
field b() -> &str { "Banana" }
|
fn a() -> &str {
|
||||||
field c() -> &str { "Cherry" }
|
"Apple"
|
||||||
field deep() -> Type { Type }
|
}
|
||||||
field other() -> Vec<Other> { vec![Other, Other] }
|
fn b() -> &str {
|
||||||
});
|
"Banana"
|
||||||
|
}
|
||||||
|
fn c() -> &str {
|
||||||
|
"Cherry"
|
||||||
|
}
|
||||||
|
fn deep() -> Type {
|
||||||
|
Type
|
||||||
|
}
|
||||||
|
fn other() -> Vec<Other> {
|
||||||
|
vec![Other, Other]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
graphql_object!(Other: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str { "Apple" }
|
impl Other {
|
||||||
field b() -> &str { "Banana" }
|
fn a() -> &str {
|
||||||
field c() -> &str { "Cherry" }
|
"Apple"
|
||||||
field deep() -> Type { Type }
|
}
|
||||||
field other() -> Vec<Other> { vec![Other, Other] }
|
fn b() -> &str {
|
||||||
});
|
"Banana"
|
||||||
|
}
|
||||||
|
fn c() -> &str {
|
||||||
|
"Cherry"
|
||||||
|
}
|
||||||
|
fn deep() -> Type {
|
||||||
|
Type
|
||||||
|
}
|
||||||
|
fn other() -> Vec<Other> {
|
||||||
|
vec![Other, Other]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
|
@ -342,9 +396,14 @@ mod threads_context_correctly {
|
||||||
|
|
||||||
impl Context for TestContext {}
|
impl Context for TestContext {}
|
||||||
|
|
||||||
graphql_object!(Schema: TestContext |&self| {
|
#[crate::object_internal(
|
||||||
field a(&executor) -> String { executor.context().value.clone() }
|
Context = TestContext,
|
||||||
});
|
)]
|
||||||
|
impl Schema {
|
||||||
|
fn a(context: &TestContext) -> String {
|
||||||
|
context.value.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
|
@ -403,36 +462,42 @@ mod dynamic_context_switching {
|
||||||
|
|
||||||
struct ItemRef;
|
struct ItemRef;
|
||||||
|
|
||||||
graphql_object!(Schema: OuterContext |&self| {
|
#[crate::object_internal(Context = OuterContext)]
|
||||||
field item_opt(&executor, key: i32) -> Option<(&InnerContext, ItemRef)> {
|
impl Schema {
|
||||||
|
fn item_opt(context: &OuterContext, key: i32) -> Option<(&InnerContext, ItemRef)> {
|
||||||
executor.context().items.get(&key).map(|c| (c, ItemRef))
|
executor.context().items.get(&key).map(|c| (c, ItemRef))
|
||||||
}
|
}
|
||||||
|
|
||||||
field item_res(&executor, key: i32) -> FieldResult<(&InnerContext, ItemRef)> {
|
fn item_res(context: &OuterContext, key: i32) -> FieldResult<(&InnerContext, ItemRef)> {
|
||||||
let res = executor.context().items.get(&key)
|
let res = context
|
||||||
|
.items
|
||||||
|
.get(&key)
|
||||||
.ok_or(format!("Could not find key {}", key))
|
.ok_or(format!("Could not find key {}", key))
|
||||||
.map(|c| (c, ItemRef))?;
|
.map(|c| (c, ItemRef))?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
field item_res_opt(&executor, key: i32) -> FieldResult<Option<(&InnerContext, ItemRef)>> {
|
fn item_res_opt(
|
||||||
|
context: &OuterContext,
|
||||||
|
key: i32,
|
||||||
|
) -> FieldResult<Option<(&InnerContext, ItemRef)>> {
|
||||||
if key > 100 {
|
if key > 100 {
|
||||||
Err(format!("Key too large: {}", key))?;
|
Err(format!("Key too large: {}", key))?;
|
||||||
}
|
}
|
||||||
Ok(executor.context().items.get(&key)
|
Ok(context.items.get(&key).map(|c| (c, ItemRef)))
|
||||||
.map(|c| (c, ItemRef)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
field item_always(&executor, key: i32) -> (&InnerContext, ItemRef) {
|
fn item_always(context: &OuterContext, key: i32) -> (&InnerContext, ItemRef) {
|
||||||
executor.context().items.get(&key)
|
context.items.get(&key).map(|c| (c, ItemRef)).unwrap()
|
||||||
.map(|c| (c, ItemRef))
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(ItemRef: InnerContext |&self| {
|
#[crate::object_internal(Context = InnerContext)]
|
||||||
field value(&executor) -> String { executor.context().value.clone() }
|
impl ItemRef {
|
||||||
});
|
fn value(context: &InnerContext) -> String {
|
||||||
|
context.value.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_opt() {
|
fn test_opt() {
|
||||||
|
@ -736,19 +801,37 @@ mod propagates_errors_to_nullable_fields {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Schema: () |&self| {
|
#[crate::object_internal]
|
||||||
field inner() -> Inner { Inner }
|
impl Schema {
|
||||||
field inners() -> Vec<Inner> { (0..5).map(|_| Inner).collect() }
|
fn inner() -> Inner {
|
||||||
field nullable_inners() -> Vec<Option<Inner>> { (0..5).map(|_| Some(Inner)).collect() }
|
Inner
|
||||||
});
|
}
|
||||||
|
fn inners() -> Vec<Inner> {
|
||||||
|
(0..5).map(|_| Inner).collect()
|
||||||
|
}
|
||||||
|
fn nullable_inners() -> Vec<Option<Inner>> {
|
||||||
|
(0..5).map(|_| Some(Inner)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
graphql_object!(Inner: () |&self| {
|
#[crate::object_internal]
|
||||||
field nullable_field() -> Option<Inner> { Some(Inner) }
|
impl Inner {
|
||||||
field non_nullable_field() -> Inner { Inner }
|
fn nullable_field() -> Option<Inner> {
|
||||||
field nullable_error_field() -> FieldResult<Option<&str>> { Err("Error for nullableErrorField")? }
|
Some(Inner)
|
||||||
field non_nullable_error_field() -> FieldResult<&str> { Err("Error for nonNullableErrorField")? }
|
}
|
||||||
field custom_error_field() -> Result<&str, CustomError> { Err(CustomError::NotFound) }
|
fn non_nullable_field() -> Inner {
|
||||||
});
|
Inner
|
||||||
|
}
|
||||||
|
fn nullable_error_field() -> FieldResult<Option<&str>> {
|
||||||
|
Err("Error for nullableErrorField")?
|
||||||
|
}
|
||||||
|
fn non_nullable_error_field() -> FieldResult<&str> {
|
||||||
|
Err("Error for nonNullableErrorField")?
|
||||||
|
}
|
||||||
|
fn custom_error_field() -> Result<&str, CustomError> {
|
||||||
|
Err(CustomError::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nullable_first_level() {
|
fn nullable_first_level() {
|
||||||
|
@ -985,13 +1068,17 @@ mod named_operations {
|
||||||
|
|
||||||
struct Schema;
|
struct Schema;
|
||||||
|
|
||||||
graphql_object!(Schema: () |&self| {
|
#[crate::object_internal]
|
||||||
field a() -> &str { "b" }
|
impl Schema {
|
||||||
});
|
fn a() -> &str {
|
||||||
|
"b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uses_inline_operation_if_no_name_provided() {
|
fn uses_inline_operation_if_no_name_provided() {
|
||||||
let schema = RootNode::new(Schema, EmptyMutation::<()>::new());
|
let schema =
|
||||||
|
RootNode::<_, _, crate::DefaultScalarValue>::new(Schema, EmptyMutation::<()>::new());
|
||||||
let doc = r"{ a }";
|
let doc = r"{ a }";
|
||||||
|
|
||||||
let vars = vec![].into_iter().collect();
|
let vars = vec![].into_iter().collect();
|
||||||
|
|
|
@ -37,12 +37,17 @@ mod interface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Dog: () |&self| {
|
#[crate::object_internal(
|
||||||
field name() -> &str { &self.name }
|
interfaces = [&Pet]
|
||||||
field woofs() -> bool { self.woofs }
|
)]
|
||||||
|
impl Dog {
|
||||||
interfaces: [&Pet]
|
fn name(&self) -> &str {
|
||||||
});
|
&self.name
|
||||||
|
}
|
||||||
|
fn woofs(&self) -> bool {
|
||||||
|
self.woofs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Cat {
|
struct Cat {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -58,22 +63,28 @@ mod interface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Cat: () |&self| {
|
#[crate::object_internal(
|
||||||
field name() -> &str { &self.name }
|
interfaces = [&Pet]
|
||||||
field meows() -> bool { self.meows }
|
)]
|
||||||
|
impl Cat {
|
||||||
interfaces: [&Pet]
|
fn name(&self) -> &str {
|
||||||
});
|
&self.name
|
||||||
|
}
|
||||||
|
fn meows(&self) -> bool {
|
||||||
|
self.meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Schema {
|
struct Schema {
|
||||||
pets: Vec<Box<Pet>>,
|
pets: Vec<Box<Pet>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Schema: () |&self| {
|
#[crate::object_internal]
|
||||||
field pets() -> Vec<&Pet> {
|
impl Schema {
|
||||||
|
fn pets(&self) -> Vec<&Pet> {
|
||||||
self.pets.iter().map(|p| p.as_ref()).collect()
|
self.pets.iter().map(|p| p.as_ref()).collect()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
|
@ -177,10 +188,15 @@ mod union {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Dog: () |&self| {
|
#[crate::object_internal]
|
||||||
field name() -> &str { &self.name }
|
impl Dog {
|
||||||
field woofs() -> bool { self.woofs }
|
fn name(&self) -> &str {
|
||||||
});
|
&self.name
|
||||||
|
}
|
||||||
|
fn woofs(&self) -> bool {
|
||||||
|
self.woofs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Cat {
|
struct Cat {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -193,20 +209,26 @@ mod union {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Cat: () |&self| {
|
#[crate::object_internal]
|
||||||
field name() -> &str { &self.name }
|
impl Cat {
|
||||||
field meows() -> bool { self.meows }
|
fn name(&self) -> &str {
|
||||||
});
|
&self.name
|
||||||
|
}
|
||||||
|
fn meows(&self) -> bool {
|
||||||
|
self.meows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Schema {
|
struct Schema {
|
||||||
pets: Vec<Box<Pet>>,
|
pets: Vec<Box<Pet>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Schema: () |&self| {
|
#[crate::object_internal]
|
||||||
field pets() -> Vec<&Pet> {
|
impl Schema {
|
||||||
|
fn pets(&self) -> Vec<&Pet> {
|
||||||
self.pets.iter().map(|p| p.as_ref()).collect()
|
self.pets.iter().map(|p| p.as_ref()).collect()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
|
|
|
@ -64,14 +64,27 @@ enum EnumDeprecation {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
graphql_object!(Root: () |&self| {
|
#[crate::object_internal]
|
||||||
field default_name() -> DefaultName { DefaultName::Foo }
|
impl Root {
|
||||||
field named() -> Named { Named::Foo }
|
fn default_name() -> DefaultName {
|
||||||
field no_trailing_comma() -> NoTrailingComma { NoTrailingComma::Foo }
|
DefaultName::Foo
|
||||||
field enum_description() -> EnumDescription { EnumDescription::Foo }
|
}
|
||||||
field enum_value_description() -> EnumValueDescription { EnumValueDescription::Foo }
|
fn named() -> Named {
|
||||||
field enum_deprecation() -> EnumDeprecation { EnumDeprecation::Foo }
|
Named::Foo
|
||||||
});
|
}
|
||||||
|
fn no_trailing_comma() -> NoTrailingComma {
|
||||||
|
NoTrailingComma::Foo
|
||||||
|
}
|
||||||
|
fn enum_description() -> EnumDescription {
|
||||||
|
EnumDescription::Foo
|
||||||
|
}
|
||||||
|
fn enum_value_description() -> EnumValueDescription {
|
||||||
|
EnumValueDescription::Foo
|
||||||
|
}
|
||||||
|
fn enum_deprecation() -> EnumDeprecation {
|
||||||
|
EnumDeprecation::Foo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_type_info_query<F>(doc: &str, f: F)
|
fn run_type_info_query<F>(doc: &str, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -79,8 +79,9 @@ struct FieldWithDefaults {
|
||||||
field_two: i32,
|
field_two: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Root: () |&self| {
|
#[crate::object_internal]
|
||||||
field test_field(
|
impl Root {
|
||||||
|
fn test_field(
|
||||||
a1: DefaultName,
|
a1: DefaultName,
|
||||||
a2: NoTrailingComma,
|
a2: NoTrailingComma,
|
||||||
a3: Derive,
|
a3: Derive,
|
||||||
|
@ -95,7 +96,7 @@ graphql_object!(Root: () |&self| {
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
fn run_type_info_query<F>(doc: &str, f: F)
|
fn run_type_info_query<F>(doc: &str, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -51,22 +51,26 @@ graphql_interface!(Interface: () as "SampleInterface" |&self| {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_object!(Root: () |&self| {
|
/// The root query object in the schema
|
||||||
description: "The root query object in the schema"
|
#[crate::object_internal(
|
||||||
|
interfaces = [&Interface]
|
||||||
interfaces: [Interface]
|
Scalar = crate::DefaultScalarValue,
|
||||||
|
)]
|
||||||
field sample_enum() -> Sample {
|
impl Root {
|
||||||
|
fn sample_enum() -> Sample {
|
||||||
Sample::One
|
Sample::One
|
||||||
}
|
}
|
||||||
|
|
||||||
field sample_scalar(
|
#[graphql(arguments(
|
||||||
first: i32 as "The first number",
|
first(description = "The first number",),
|
||||||
second = 123: i32 as "The second number"
|
second(description = "The second number", default = 123,),
|
||||||
) -> Scalar as "A sample scalar field on the object" {
|
))]
|
||||||
|
|
||||||
|
/// A sample scalar field on the object
|
||||||
|
fn sample_scalar(first: i32, second: i32) -> Scalar {
|
||||||
Scalar(first + second)
|
Scalar(first + second)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_execution() {
|
fn test_execution() {
|
||||||
|
|
|
@ -62,59 +62,67 @@ struct InputWithDefaults {
|
||||||
a: i32,
|
a: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(TestType: () |&self| {
|
#[crate::object_internal]
|
||||||
field field_with_object_input(input: Option<TestInputObject>) -> String {
|
impl TestType {
|
||||||
|
fn field_with_object_input(input: Option<TestInputObject>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field field_with_nullable_string_input(input: Option<String>) -> String {
|
fn field_with_nullable_string_input(input: Option<String>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field field_with_non_nullable_string_input(input: String) -> String {
|
fn field_with_non_nullable_string_input(input: String) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field field_with_default_argument_value(input = ("Hello World".to_owned()): String) -> String {
|
#[graphql(
|
||||||
|
arguments(
|
||||||
|
input(
|
||||||
|
default = "Hello World".to_string(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
fn field_with_default_argument_value(input: String) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
|
fn field_with_nested_object_input(input: Option<TestNestedInputObject>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field list(input: Option<Vec<Option<String>>>) -> String {
|
fn list(input: Option<Vec<Option<String>>>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field nn_list(input: Vec<Option<String>>) -> String {
|
fn nn_list(input: Vec<Option<String>>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field list_nn(input: Option<Vec<String>>) -> String {
|
fn list_nn(input: Option<Vec<String>>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field nn_list_nn(input: Vec<String>) -> String {
|
fn nn_list_nn(input: Vec<String>) -> String {
|
||||||
format!("{:?}", input)
|
format!("{:?}", input)
|
||||||
}
|
}
|
||||||
|
|
||||||
field example_input(arg: ExampleInputObject) -> String {
|
fn example_input(arg: ExampleInputObject) -> String {
|
||||||
format!("a: {:?}, b: {:?}", arg.a, arg.b)
|
format!("a: {:?}, b: {:?}", arg.a, arg.b)
|
||||||
}
|
}
|
||||||
|
|
||||||
field input_with_defaults(arg: InputWithDefaults) -> String {
|
fn input_with_defaults(arg: InputWithDefaults) -> String {
|
||||||
format!("a: {:?}", arg.a)
|
format!("a: {:?}", arg.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
field integer_input(value: i32) -> String {
|
fn integer_input(value: i32) -> String {
|
||||||
format!("value: {}", value)
|
format!("value: {}", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
field float_input(value: f64) -> String {
|
fn float_input(value: f64) -> String {
|
||||||
format!("value: {}", value)
|
format!("value: {}", value)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
|
fn run_variable_query<F>(query: &str, vars: Variables<DefaultScalarValue>, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -209,20 +209,22 @@ mod integration_test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serialization() {
|
fn test_serialization() {
|
||||||
struct Root;
|
struct Root;
|
||||||
graphql_object!(Root: () |&self| {
|
|
||||||
field exampleNaiveDate() -> NaiveDate {
|
#[crate::object_internal]
|
||||||
|
impl Root {
|
||||||
|
fn exampleNaiveDate() -> NaiveDate {
|
||||||
NaiveDate::from_ymd(2015, 3, 14)
|
NaiveDate::from_ymd(2015, 3, 14)
|
||||||
}
|
}
|
||||||
field exampleNaiveDateTime() -> NaiveDateTime {
|
fn exampleNaiveDateTime() -> NaiveDateTime {
|
||||||
NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
|
NaiveDate::from_ymd(2016, 7, 8).and_hms(9, 10, 11)
|
||||||
}
|
}
|
||||||
field exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
|
fn exampleDateTimeFixedOffset() -> DateTime<FixedOffset> {
|
||||||
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
|
DateTime::parse_from_rfc3339("1996-12-19T16:39:57-08:00").unwrap()
|
||||||
}
|
}
|
||||||
field exampleDateTimeUtc() -> DateTime<Utc> {
|
fn exampleDateTimeUtc() -> DateTime<Utc> {
|
||||||
Utc.timestamp(61, 0)
|
Utc.timestamp(61, 0)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let doc = r#"
|
let doc = r#"
|
||||||
{
|
{
|
||||||
|
|
|
@ -108,8 +108,15 @@ extern crate uuid;
|
||||||
// Depend on juniper_codegen and re-export everything in it.
|
// Depend on juniper_codegen and re-export everything in it.
|
||||||
// This allows users to just depend on juniper and get the derive
|
// This allows users to just depend on juniper and get the derive
|
||||||
// functionality automatically.
|
// functionality automatically.
|
||||||
#[doc(hidden)]
|
pub use juniper_codegen::{
|
||||||
pub use juniper_codegen::*;
|
object, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, ScalarValue,
|
||||||
|
};
|
||||||
|
// Internal macros are not exported,
|
||||||
|
// but declared at the root to make them easier to use.
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use juniper_codegen::{
|
||||||
|
object_internal, GraphQLEnumInternal, GraphQLInputObjectInternal, GraphQLScalarValueInternal,
|
||||||
|
};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod value;
|
mod value;
|
||||||
|
|
|
@ -61,13 +61,18 @@ impl Character for Droid {
|
||||||
fn id(&self) -> &str { &self.id }
|
fn id(&self) -> &str { &self.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Human: Database as "Human" |&self| {
|
#[juniper::object(Context = Database)]
|
||||||
field id() -> &str { &self.id }
|
impl Human {
|
||||||
});
|
fn id(&self) -> &str { &self.id }
|
||||||
|
}
|
||||||
|
|
||||||
juniper::graphql_object!(Droid: Database as "Droid" |&self| {
|
#[juniper::object(
|
||||||
field id() -> &str { &self.id }
|
name = "Droid",
|
||||||
});
|
Context = Database,
|
||||||
|
)]
|
||||||
|
impl Droid {
|
||||||
|
fn id(&self) -> &str { &self.id }
|
||||||
|
}
|
||||||
|
|
||||||
// You can introduce lifetimes or generic parameters by < > before the name.
|
// You can introduce lifetimes or generic parameters by < > before the name.
|
||||||
juniper::graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
juniper::graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
/**
|
/**
|
||||||
|
## DEPRECATION WARNING
|
||||||
|
|
||||||
|
The `graphql_object!` macro is deprecated and will be removed soon.
|
||||||
|
Use the new[object](https://docs.rs/juniper/latest/juniper/macro.object.html) macro instead.
|
||||||
|
|
||||||
Expose GraphQL objects
|
Expose GraphQL objects
|
||||||
|
|
||||||
|
|
||||||
This is a short-hand macro that implements the `GraphQLType` trait for a given
|
This is a short-hand macro that implements the `GraphQLType` trait for a given
|
||||||
type. By using this macro instead of implementing it manually, you gain type
|
type. By using this macro instead of implementing it manually, you gain type
|
||||||
safety and reduce repetitive declarations.
|
safety and reduce repetitive declarations.
|
||||||
|
@ -308,7 +314,6 @@ arg_name: ArgType
|
||||||
```
|
```
|
||||||
|
|
||||||
[1]: struct.Executor.html
|
[1]: struct.Executor.html
|
||||||
|
|
||||||
*/
|
*/
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! graphql_object {
|
macro_rules! graphql_object {
|
||||||
|
|
|
@ -26,63 +26,111 @@ struct Point {
|
||||||
x: i32,
|
x: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(Root: () |&self| {
|
#[crate::object_internal]
|
||||||
field simple() -> i32 { 0 }
|
impl Root {
|
||||||
field exec_arg(&executor) -> i32 { 0 }
|
fn simple() -> i32 {
|
||||||
field exec_arg_and_more(&executor, arg: i32) -> i32 { 0 }
|
0
|
||||||
|
}
|
||||||
|
fn exec_arg(executor: &Executor) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn exec_arg_and_more(executor: &Executor, arg: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
field single_arg(arg: i32) -> i32 { 0 }
|
fn single_arg(arg: i32) -> i32 {
|
||||||
field multi_args(
|
0
|
||||||
arg1: i32,
|
}
|
||||||
arg2: i32
|
|
||||||
) -> i32 { 0 }
|
|
||||||
field multi_args_trailing_comma(
|
|
||||||
arg1: i32,
|
|
||||||
arg2: i32,
|
|
||||||
) -> i32 { 0 }
|
|
||||||
|
|
||||||
field single_arg_descr(arg: i32 as "The arg") -> i32 { 0 }
|
fn multi_args(arg1: i32, arg2: i32) -> i32 {
|
||||||
field multi_args_descr(
|
0
|
||||||
arg1: i32 as "The first arg",
|
}
|
||||||
arg2: i32 as "The second arg"
|
|
||||||
) -> i32 { 0 }
|
|
||||||
field multi_args_descr_trailing_comma(
|
|
||||||
arg1: i32 as "The first arg",
|
|
||||||
arg2: i32 as "The second arg",
|
|
||||||
) -> i32 { 0 }
|
|
||||||
|
|
||||||
field attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 }
|
fn multi_args_trailing_comma(arg1: i32, arg2: i32) -> i32 {
|
||||||
field attr_arg_descr_collapse(
|
0
|
||||||
#[doc = "The arg"]
|
}
|
||||||
#[doc = "and more details"]
|
|
||||||
arg: i32,
|
|
||||||
) -> i32 { 0 }
|
|
||||||
|
|
||||||
field arg_with_default(arg = 123: i32) -> i32 { 0 }
|
#[graphql(arguments(arg(description = "The arg")))]
|
||||||
field multi_args_with_default(
|
fn single_arg_descr(arg: i32) -> i32 {
|
||||||
arg1 = 123: i32,
|
0
|
||||||
arg2 = 456: i32
|
}
|
||||||
) -> i32 { 0 }
|
|
||||||
field multi_args_with_default_trailing_comma(
|
|
||||||
arg1 = 123: i32,
|
|
||||||
arg2 = 456: i32,
|
|
||||||
) -> i32 { 0 }
|
|
||||||
|
|
||||||
field arg_with_default_descr(arg = 123: i32 as "The arg") -> i32 { 0 }
|
#[graphql(arguments(
|
||||||
field multi_args_with_default_descr(
|
arg1(description = "The first arg",),
|
||||||
arg1 = 123: i32 as "The first arg",
|
arg2(description = "The second arg")
|
||||||
arg2 = 456: i32 as "The second arg"
|
))]
|
||||||
) -> i32 { 0 }
|
fn multi_args_descr(arg1: i32, arg2: i32) -> i32 {
|
||||||
field multi_args_with_default_trailing_comma_descr(
|
0
|
||||||
arg1 = 123: i32 as "The first arg",
|
}
|
||||||
arg2 = 456: i32 as "The second arg",
|
|
||||||
) -> i32 { 0 }
|
|
||||||
|
|
||||||
field args_with_complex_default(
|
#[graphql(arguments(
|
||||||
arg1 = ("test".to_owned()): String as "A string default argument",
|
arg1(description = "The first arg",),
|
||||||
arg2 = (Point { x: 1 }): Point as "An input object default argument",
|
arg2(description = "The second arg")
|
||||||
) -> i32 { 0 }
|
))]
|
||||||
});
|
fn multi_args_descr_trailing_comma(arg1: i32, arg2: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented
|
||||||
|
// fn attr_arg_descr(#[doc = "The arg"] arg: i32) -> i32 { 0 }
|
||||||
|
// fn attr_arg_descr_collapse(
|
||||||
|
// #[doc = "The arg"]
|
||||||
|
// #[doc = "and more details"]
|
||||||
|
// arg: i32,
|
||||||
|
// ) -> i32 { 0 }
|
||||||
|
|
||||||
|
#[graphql(arguments(arg(default = 123,),))]
|
||||||
|
fn arg_with_default(arg: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(arg1(default = 123,), arg2(default = 456,)))]
|
||||||
|
fn multi_args_with_default(arg1: i32, arg2: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(arg1(default = 123,), arg2(default = 456,),))]
|
||||||
|
fn multi_args_with_default_trailing_comma(arg1: i32, arg2: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(arg(default = 123, description = "The arg")))]
|
||||||
|
fn arg_with_default_descr(arg: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(
|
||||||
|
arg1(default = 123, description = "The first arg"),
|
||||||
|
arg2(default = 456, description = "The second arg")
|
||||||
|
))]
|
||||||
|
fn multi_args_with_default_descr(arg1: i32, arg2: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(
|
||||||
|
arg1(default = 123, description = "The first arg",),
|
||||||
|
arg2(default = 456, description = "The second arg",)
|
||||||
|
))]
|
||||||
|
fn multi_args_with_default_trailing_comma_descr(arg1: i32, arg2: i32) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(
|
||||||
|
arguments(
|
||||||
|
arg1(
|
||||||
|
default = "test".to_string(),
|
||||||
|
description = "A string default argument",
|
||||||
|
),
|
||||||
|
arg2(
|
||||||
|
default = Point{ x: 1 },
|
||||||
|
description = "An input object default argument",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
fn args_with_complex_default(arg1: String, arg2: Point) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_args_info_query<F>(field_name: &str, f: F)
|
fn run_args_info_query<F>(field_name: &str, f: F)
|
||||||
where
|
where
|
||||||
|
@ -509,71 +557,73 @@ fn introspect_field_multi_args_descr_trailing_comma() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented
|
||||||
fn introspect_field_attr_arg_descr() {
|
// #[test]
|
||||||
run_args_info_query("attrArgDescr", |args| {
|
// fn introspect_field_attr_arg_descr() {
|
||||||
assert_eq!(args.len(), 1);
|
// run_args_info_query("attrArgDescr", |args| {
|
||||||
|
// assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
assert!(args.contains(&Value::object(
|
// assert!(args.contains(&Value::object(
|
||||||
vec![
|
// vec![
|
||||||
("name", Value::scalar("arg")),
|
// ("name", Value::scalar("arg")),
|
||||||
("description", Value::scalar("The arg")),
|
// ("description", Value::scalar("The arg")),
|
||||||
("defaultValue", Value::null()),
|
// ("defaultValue", Value::null()),
|
||||||
(
|
// (
|
||||||
"type",
|
// "type",
|
||||||
Value::object(
|
// Value::object(
|
||||||
vec![
|
// vec![
|
||||||
("name", Value::null()),
|
// ("name", Value::null()),
|
||||||
(
|
// (
|
||||||
"ofType",
|
// "ofType",
|
||||||
Value::object(
|
// Value::object(
|
||||||
vec![("name", Value::scalar("Int"))].into_iter().collect(),
|
// vec![("name", Value::scalar("Int"))].into_iter().collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
]
|
// ]
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.collect(),
|
// .collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
]
|
// ]
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.collect(),
|
// .collect(),
|
||||||
)));
|
// )));
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
// TODO: enable once [RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented
|
||||||
fn introspect_field_attr_arg_descr_collapse() {
|
// #[test]
|
||||||
run_args_info_query("attrArgDescrCollapse", |args| {
|
// fn introspect_field_attr_arg_descr_collapse() {
|
||||||
assert_eq!(args.len(), 1);
|
// run_args_info_query("attrArgDescrCollapse", |args| {
|
||||||
|
// assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
assert!(args.contains(&Value::object(
|
// assert!(args.contains(&Value::object(
|
||||||
vec![
|
// vec![
|
||||||
("name", Value::scalar("arg")),
|
// ("name", Value::scalar("arg")),
|
||||||
("description", Value::scalar("The arg\nand more details")),
|
// ("description", Value::scalar("The arg\nand more details")),
|
||||||
("defaultValue", Value::null()),
|
// ("defaultValue", Value::null()),
|
||||||
(
|
// (
|
||||||
"type",
|
// "type",
|
||||||
Value::object(
|
// Value::object(
|
||||||
vec![
|
// vec![
|
||||||
("name", Value::null()),
|
// ("name", Value::null()),
|
||||||
(
|
// (
|
||||||
"ofType",
|
// "ofType",
|
||||||
Value::object(
|
// Value::object(
|
||||||
vec![("name", Value::scalar("Int"))].into_iter().collect(),
|
// vec![("name", Value::scalar("Int"))].into_iter().collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
]
|
// ]
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.collect(),
|
// .collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
]
|
// ]
|
||||||
.into_iter()
|
// .into_iter()
|
||||||
.collect(),
|
// .collect(),
|
||||||
)));
|
// )));
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn introspect_field_arg_with_default() {
|
fn introspect_field_arg_with_default() {
|
||||||
|
|
|
@ -20,49 +20,87 @@ Syntax to validate:
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
graphql_object!(Root: () |&self| {
|
#[crate::object_internal(
|
||||||
field simple() -> i32 { 0 }
|
interfaces = [&Interface],
|
||||||
|
)]
|
||||||
field description() -> i32 as "Field description" { 0 }
|
impl Root {
|
||||||
|
fn simple() -> i32 {
|
||||||
field deprecated "Deprecation reason"
|
0
|
||||||
deprecated() -> i32 { 0 }
|
}
|
||||||
|
|
||||||
field deprecated "Deprecation reason"
|
|
||||||
deprecated_descr() -> i32 as "Field description" { 0 }
|
|
||||||
|
|
||||||
/// Field description
|
/// Field description
|
||||||
field attr_description() -> i32 { 0 }
|
fn description() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated]
|
||||||
|
fn deprecated_outer() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated(note = "Deprecation Reason")]
|
||||||
|
fn deprecated_outer_with_reason() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(deprecated = "Deprecation reason")]
|
||||||
|
fn deprecated() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(deprecated = "Deprecation reason", description = "Field description")]
|
||||||
|
fn deprecated_descr() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Field description
|
||||||
|
fn attr_description() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
/// Field description
|
/// Field description
|
||||||
/// with `collapse_docs` behavior
|
/// with `collapse_docs` behavior
|
||||||
field attr_description_collapse() -> i32 { 0 }
|
fn attr_description_collapse() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the i32 representation of 0.
|
/// Get the i32 representation of 0.
|
||||||
///
|
///
|
||||||
/// - This comment is longer.
|
/// - This comment is longer.
|
||||||
/// - These two lines are rendered as bullets by GraphiQL.
|
/// - These two lines are rendered as bullets by GraphiQL.
|
||||||
/// - subsection
|
/// - subsection
|
||||||
field attr_description_long() -> i32 { 0 }
|
fn attr_description_long() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
#[deprecated]
|
#[graphql(deprecated)]
|
||||||
field attr_deprecated() -> i32 { 0 }
|
fn attr_deprecated() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
#[deprecated(note = "Deprecation reason")]
|
#[graphql(deprecated = "Deprecation reason")]
|
||||||
field attr_deprecated_reason() -> i32 { 0 }
|
fn attr_deprecated_reason() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
/// Field description
|
/// Field description
|
||||||
#[deprecated(note = "Deprecation reason")]
|
#[graphql(deprecated = "Deprecation reason")]
|
||||||
field attr_deprecated_descr() -> i32 { 0 }
|
fn attr_deprecated_descr() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
field with_field_result() -> FieldResult<i32> { Ok(0) }
|
fn with_field_result() -> FieldResult<i32> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
field with_return() -> i32 { return 0; }
|
fn with_return() -> i32 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
field with_return_field_result() -> FieldResult<i32> { return Ok(0); }
|
fn with_return_field_result() -> FieldResult<i32> {
|
||||||
|
return Ok(0);
|
||||||
interfaces: [Interface]
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_interface!(Interface: () |&self| {
|
graphql_interface!(Interface: () |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
|
@ -247,6 +285,44 @@ fn introspect_interface_field_description() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn introspect_object_field_deprecated_outer() {
|
||||||
|
run_field_info_query("Root", "deprecatedOuter", |field| {
|
||||||
|
assert_eq!(
|
||||||
|
field.get_field_value("name"),
|
||||||
|
Some(&Value::scalar("deprecatedOuter"))
|
||||||
|
);
|
||||||
|
assert_eq!(field.get_field_value("description"), Some(&Value::null()));
|
||||||
|
assert_eq!(
|
||||||
|
field.get_field_value("isDeprecated"),
|
||||||
|
Some(&Value::scalar(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
field.get_field_value("deprecationReason"),
|
||||||
|
Some(&Value::null()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn introspect_object_field_deprecated_outer_with_reason() {
|
||||||
|
run_field_info_query("Root", "deprecatedOuterWithReason", |field| {
|
||||||
|
assert_eq!(
|
||||||
|
field.get_field_value("name"),
|
||||||
|
Some(&Value::scalar("deprecatedOuterWithReason"))
|
||||||
|
);
|
||||||
|
assert_eq!(field.get_field_value("description"), Some(&Value::null()));
|
||||||
|
assert_eq!(
|
||||||
|
field.get_field_value("isDeprecated"),
|
||||||
|
Some(&Value::scalar(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
field.get_field_value("deprecationReason"),
|
||||||
|
Some(&Value::scalar("Deprecation Reason")),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn introspect_object_field_deprecated() {
|
fn introspect_object_field_deprecated() {
|
||||||
run_field_info_query("Root", "deprecated", |field| {
|
run_field_info_query("Root", "deprecated", |field| {
|
||||||
|
|
269
juniper/src/macros/tests/impl_object.rs
Normal file
269
juniper/src/macros/tests/impl_object.rs
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
use super::util;
|
||||||
|
use crate::{graphql_value, EmptyMutation, RootNode};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Context {
|
||||||
|
flag1: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Context for Context {}
|
||||||
|
|
||||||
|
struct WithLifetime<'a> {
|
||||||
|
value: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::object_internal(Context=Context)]
|
||||||
|
impl<'a> WithLifetime<'a> {
|
||||||
|
fn value(&'a self) -> &'a str {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WithContext;
|
||||||
|
|
||||||
|
#[crate::object_internal(Context=Context)]
|
||||||
|
impl WithContext {
|
||||||
|
fn ctx(ctx: &Context) -> bool {
|
||||||
|
ctx.flag1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Query {
|
||||||
|
b: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::object_internal(
|
||||||
|
scalar = crate::DefaultScalarValue,
|
||||||
|
name = "Query",
|
||||||
|
context = Context,
|
||||||
|
)]
|
||||||
|
/// Query Description.
|
||||||
|
impl<'a> Query {
|
||||||
|
#[graphql(description = "With Self Description")]
|
||||||
|
fn with_self(&self) -> bool {
|
||||||
|
self.b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn independent() -> i32 {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_executor(_exec: &Executor<Context>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_executor_and_self(&self, _exec: &Executor<Context>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_context(_context: &Context) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_context_and_self(&self, _context: &Context) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(name = "renamed")]
|
||||||
|
fn has_custom_name() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(description = "attr")]
|
||||||
|
fn has_description_attr() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Doc description
|
||||||
|
fn has_description_doc_comment() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_argument(arg1: bool) -> bool {
|
||||||
|
arg1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(default_arg(default = true)))]
|
||||||
|
fn default_argument(default_arg: bool) -> bool {
|
||||||
|
default_arg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(arguments(arg(description = "my argument description")))]
|
||||||
|
fn arg_with_description(arg: bool) -> bool {
|
||||||
|
arg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_context_child(&self) -> WithContext {
|
||||||
|
WithContext
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_lifetime_child(&self) -> WithLifetime<'a> {
|
||||||
|
WithLifetime { value: "blub" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Mutation;
|
||||||
|
|
||||||
|
#[crate::object_internal(context = Context)]
|
||||||
|
impl Mutation {
|
||||||
|
fn empty() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_introspect() {
|
||||||
|
let res = util::run_info_query::<Query, Mutation, Context>("Query");
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
crate::graphql_value!({
|
||||||
|
"name": "Query",
|
||||||
|
"description": "Query Description.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "withSelf",
|
||||||
|
"description": "With Self Description",
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "independent",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withExecutor",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withExecutorAndSelf",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withContext",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withContextAndSelf",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "renamed",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasDescriptionAttr",
|
||||||
|
"description": "attr",
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasDescriptionDocComment",
|
||||||
|
"description": "Doc description",
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hasArgument",
|
||||||
|
"description": None,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg1",
|
||||||
|
"description": None,
|
||||||
|
"type": {
|
||||||
|
"name": None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "defaultArgument",
|
||||||
|
"description": None,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "defaultArg",
|
||||||
|
"description": None,
|
||||||
|
"type": {
|
||||||
|
"name": "Boolean",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "argWithDescription",
|
||||||
|
"description": None,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "arg",
|
||||||
|
"description": "my argument description",
|
||||||
|
"type": {
|
||||||
|
"name": None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withContextChild",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "withLifetimeChild",
|
||||||
|
"description": None,
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_query() {
|
||||||
|
let doc = r#"
|
||||||
|
query {
|
||||||
|
withSelf
|
||||||
|
independent
|
||||||
|
withExecutor
|
||||||
|
withExecutorAndSelf
|
||||||
|
withContext
|
||||||
|
withContextAndSelf
|
||||||
|
renamed
|
||||||
|
hasArgument(arg1: true)
|
||||||
|
defaultArgument
|
||||||
|
argWithDescription(arg: true)
|
||||||
|
withContextChild {
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
withLifetimeChild {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let schema = RootNode::new(Query { b: true }, EmptyMutation::<Context>::new());
|
||||||
|
let vars = std::collections::HashMap::new();
|
||||||
|
|
||||||
|
let (result, errs) = crate::execute(doc, None, &schema, &vars, &Context { flag1: true })
|
||||||
|
.expect("Execution failed");
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
graphql_value!({
|
||||||
|
"withSelf": true,
|
||||||
|
"independent": 100,
|
||||||
|
"withExecutor": true,
|
||||||
|
"withExecutorAndSelf": true,
|
||||||
|
"withContext": true,
|
||||||
|
"withContextAndSelf": true,
|
||||||
|
"renamed": true,
|
||||||
|
"hasArgument": true,
|
||||||
|
"defaultArgument": true,
|
||||||
|
"argWithDescription": true,
|
||||||
|
"withContextChild": { "ctx": true },
|
||||||
|
"withLifetimeChild": { "value": "blub" },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
|
@ -42,9 +42,12 @@ struct ResolversWithTrailingComma;
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
graphql_object!(Concrete: () |&self| {
|
#[crate::object_internal]
|
||||||
field simple() -> i32 { 0 }
|
impl Concrete {
|
||||||
});
|
fn simple() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| {
|
graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
|
@ -108,24 +111,40 @@ graphql_interface!(ResolversWithTrailingComma: () |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_object!(<'a> Root: () as "Root" |&self| {
|
#[crate::object_internal]
|
||||||
field custom_name() -> CustomName { CustomName {} }
|
impl<'a> Root {
|
||||||
|
fn custom_name() -> CustomName {
|
||||||
field with_lifetime() -> WithLifetime<'a> { WithLifetime { data: PhantomData } }
|
CustomName {}
|
||||||
field with_generics() -> WithGenerics<i32> { WithGenerics { data: 123 } }
|
|
||||||
|
|
||||||
field description_first() -> DescriptionFirst { DescriptionFirst {} }
|
|
||||||
field fields_first() -> FieldsFirst { FieldsFirst {} }
|
|
||||||
field interfaces_first() -> InterfacesFirst { InterfacesFirst {} }
|
|
||||||
|
|
||||||
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing {} }
|
|
||||||
field commas_on_meta() -> CommasOnMeta { CommasOnMeta {} }
|
|
||||||
|
|
||||||
field resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
|
|
||||||
ResolversWithTrailingComma {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
fn with_lifetime() -> WithLifetime<'a> {
|
||||||
|
WithLifetime { data: PhantomData }
|
||||||
|
}
|
||||||
|
fn with_generics() -> WithGenerics<i32> {
|
||||||
|
WithGenerics { data: 123 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description_first() -> DescriptionFirst {
|
||||||
|
DescriptionFirst {}
|
||||||
|
}
|
||||||
|
fn fields_first() -> FieldsFirst {
|
||||||
|
FieldsFirst {}
|
||||||
|
}
|
||||||
|
fn interfaces_first() -> InterfacesFirst {
|
||||||
|
InterfacesFirst {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commas_with_trailing() -> CommasWithTrailing {
|
||||||
|
CommasWithTrailing {}
|
||||||
|
}
|
||||||
|
fn commas_on_meta() -> CommasOnMeta {
|
||||||
|
CommasOnMeta {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
|
||||||
|
ResolversWithTrailingComma {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_type_info_query<F>(type_name: &str, f: F)
|
fn run_type_info_query<F>(type_name: &str, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
mod args;
|
mod args;
|
||||||
mod field;
|
mod field;
|
||||||
|
mod impl_object;
|
||||||
mod interface;
|
mod interface;
|
||||||
mod object;
|
mod object;
|
||||||
mod scalar;
|
mod scalar;
|
||||||
mod union;
|
mod union;
|
||||||
|
mod util;
|
||||||
|
|
|
@ -18,41 +18,29 @@ Syntax to validate:
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct Interface;
|
|
||||||
|
|
||||||
struct CustomName;
|
struct CustomName;
|
||||||
|
graphql_object!(CustomName: () as "ACustomNamedType" |&self| {
|
||||||
|
field simple() -> i32 { 0 }
|
||||||
|
});
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct WithLifetime<'a> {
|
struct WithLifetime<'a> {
|
||||||
data: PhantomData<&'a i32>,
|
data: PhantomData<&'a i32>,
|
||||||
}
|
}
|
||||||
|
graphql_object!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
|
||||||
|
field simple() -> i32 { 0 }
|
||||||
|
});
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct WithGenerics<T> {
|
struct WithGenerics<T> {
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DescriptionFirst;
|
|
||||||
struct FieldsFirst;
|
|
||||||
struct InterfacesFirst;
|
|
||||||
|
|
||||||
struct CommasWithTrailing;
|
|
||||||
struct CommasOnMeta;
|
|
||||||
|
|
||||||
struct Root;
|
|
||||||
|
|
||||||
graphql_object!(CustomName: () as "ACustomNamedType" |&self| {
|
|
||||||
field simple() -> i32 { 0 }
|
|
||||||
});
|
|
||||||
|
|
||||||
graphql_object!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
|
|
||||||
field simple() -> i32 { 0 }
|
|
||||||
});
|
|
||||||
|
|
||||||
graphql_object!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
|
graphql_object!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct Interface;
|
||||||
|
struct DescriptionFirst;
|
||||||
graphql_interface!(Interface: () |&self| {
|
graphql_interface!(Interface: () |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
|
|
||||||
|
@ -60,7 +48,6 @@ graphql_interface!(Interface: () |&self| {
|
||||||
DescriptionFirst => Some(DescriptionFirst {}),
|
DescriptionFirst => Some(DescriptionFirst {}),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_object!(DescriptionFirst: () |&self| {
|
graphql_object!(DescriptionFirst: () |&self| {
|
||||||
description: "A description"
|
description: "A description"
|
||||||
|
|
||||||
|
@ -69,6 +56,7 @@ graphql_object!(DescriptionFirst: () |&self| {
|
||||||
interfaces: [Interface]
|
interfaces: [Interface]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct FieldsFirst;
|
||||||
graphql_object!(FieldsFirst: () |&self| {
|
graphql_object!(FieldsFirst: () |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
|
|
||||||
|
@ -77,6 +65,7 @@ graphql_object!(FieldsFirst: () |&self| {
|
||||||
interfaces: [Interface]
|
interfaces: [Interface]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct InterfacesFirst;
|
||||||
graphql_object!(InterfacesFirst: ()|&self| {
|
graphql_object!(InterfacesFirst: ()|&self| {
|
||||||
interfaces: [Interface]
|
interfaces: [Interface]
|
||||||
|
|
||||||
|
@ -85,6 +74,7 @@ graphql_object!(InterfacesFirst: ()|&self| {
|
||||||
description: "A description"
|
description: "A description"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct CommasWithTrailing;
|
||||||
graphql_object!(CommasWithTrailing: () |&self| {
|
graphql_object!(CommasWithTrailing: () |&self| {
|
||||||
interfaces: [Interface],
|
interfaces: [Interface],
|
||||||
|
|
||||||
|
@ -93,6 +83,7 @@ graphql_object!(CommasWithTrailing: () |&self| {
|
||||||
description: "A description",
|
description: "A description",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct CommasOnMeta;
|
||||||
graphql_object!(CommasOnMeta: () |&self| {
|
graphql_object!(CommasOnMeta: () |&self| {
|
||||||
interfaces: [Interface],
|
interfaces: [Interface],
|
||||||
description: "A description",
|
description: "A description",
|
||||||
|
@ -100,6 +91,8 @@ graphql_object!(CommasOnMeta: () |&self| {
|
||||||
field simple() -> i32 { 0 }
|
field simple() -> i32 { 0 }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct Root;
|
||||||
|
|
||||||
struct InnerContext;
|
struct InnerContext;
|
||||||
impl Context for InnerContext {}
|
impl Context for InnerContext {}
|
||||||
|
|
||||||
|
|
|
@ -78,12 +78,21 @@ graphql_scalar!(ScalarDescription {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_object!(Root: () |&self| {
|
#[crate::object_internal]
|
||||||
field default_name() -> DefaultName { DefaultName(0) }
|
impl Root {
|
||||||
field other_order() -> OtherOrder { OtherOrder(0) }
|
fn default_name() -> DefaultName {
|
||||||
field named() -> Named { Named(0) }
|
DefaultName(0)
|
||||||
field scalar_description() -> ScalarDescription { ScalarDescription(0) }
|
}
|
||||||
});
|
fn other_order() -> OtherOrder {
|
||||||
|
OtherOrder(0)
|
||||||
|
}
|
||||||
|
fn named() -> Named {
|
||||||
|
Named(0)
|
||||||
|
}
|
||||||
|
fn scalar_description() -> ScalarDescription {
|
||||||
|
ScalarDescription(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_type_info_query<F>(doc: &str, f: F)
|
fn run_type_info_query<F>(doc: &str, f: F)
|
||||||
where
|
where
|
||||||
|
|
|
@ -46,9 +46,12 @@ enum ResolversWithTrailingComma {
|
||||||
|
|
||||||
struct Root;
|
struct Root;
|
||||||
|
|
||||||
graphql_object!(Concrete: () |&self| {
|
#[crate::object_internal]
|
||||||
field simple() -> i32 { 123 }
|
impl Concrete {
|
||||||
});
|
fn simple() -> i32 {
|
||||||
|
123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| {
|
graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| {
|
||||||
instance_resolvers: |&_| {
|
instance_resolvers: |&_| {
|
||||||
|
@ -56,13 +59,13 @@ graphql_union!(CustomName: () as "ACustomNamedUnion" |&self| {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_union!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
|
graphql_union!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
|
||||||
instance_resolvers: |&_| {
|
instance_resolvers: |&_| {
|
||||||
Concrete => match *self { WithLifetime::Int(_) => Some(Concrete) }
|
Concrete => match *self { WithLifetime::Int(_) => Some(Concrete) }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_union!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
|
graphql_union!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
|
||||||
instance_resolvers: |&_| {
|
instance_resolvers: |&_| {
|
||||||
Concrete => match *self { WithGenerics::Generic(_) => Some(Concrete) }
|
Concrete => match *self { WithGenerics::Generic(_) => Some(Concrete) }
|
||||||
}
|
}
|
||||||
|
@ -96,17 +99,30 @@ graphql_union!(ResolversWithTrailingComma: () |&self| {
|
||||||
description: "A description"
|
description: "A description"
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_object!(<'a> Root: () as "Root" |&self| {
|
#[crate::object_internal]
|
||||||
field custom_name() -> CustomName { CustomName::Concrete(Concrete) }
|
impl<'a> Root {
|
||||||
field with_lifetime() -> WithLifetime<'a> { WithLifetime::Int(PhantomData) }
|
fn custom_name() -> CustomName {
|
||||||
field with_generics() -> WithGenerics<i32> { WithGenerics::Generic(123) }
|
CustomName::Concrete(Concrete)
|
||||||
field description_first() -> DescriptionFirst { DescriptionFirst::Concrete(Concrete) }
|
}
|
||||||
field resolvers_first() -> ResolversFirst { ResolversFirst::Concrete(Concrete) }
|
fn with_lifetime() -> WithLifetime<'a> {
|
||||||
field commas_with_trailing() -> CommasWithTrailing { CommasWithTrailing::Concrete(Concrete) }
|
WithLifetime::Int(PhantomData)
|
||||||
field resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
|
}
|
||||||
|
fn with_generics() -> WithGenerics<i32> {
|
||||||
|
WithGenerics::Generic(123)
|
||||||
|
}
|
||||||
|
fn description_first() -> DescriptionFirst {
|
||||||
|
DescriptionFirst::Concrete(Concrete)
|
||||||
|
}
|
||||||
|
fn resolvers_first() -> ResolversFirst {
|
||||||
|
ResolversFirst::Concrete(Concrete)
|
||||||
|
}
|
||||||
|
fn commas_with_trailing() -> CommasWithTrailing {
|
||||||
|
CommasWithTrailing::Concrete(Concrete)
|
||||||
|
}
|
||||||
|
fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
|
||||||
ResolversWithTrailingComma::Concrete(Concrete)
|
ResolversWithTrailingComma::Concrete(Concrete)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
fn run_type_info_query<F>(type_name: &str, f: F)
|
fn run_type_info_query<F>(type_name: &str, f: F)
|
||||||
where
|
where
|
||||||
|
|
54
juniper/src/macros/tests/util.rs
Normal file
54
juniper/src/macros/tests/util.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::{DefaultScalarValue, GraphQLType, RootNode, Value, Variables};
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
pub fn run_query<Query, Mutation, Context>(query: &str) -> Value
|
||||||
|
where
|
||||||
|
Query: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Mutation: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Context: Default,
|
||||||
|
{
|
||||||
|
let schema = RootNode::new(Query::default(), Mutation::default());
|
||||||
|
let (result, errs) =
|
||||||
|
crate::execute(query, None, &schema, &Variables::new(), &Context::default())
|
||||||
|
.expect("Execution failed");
|
||||||
|
|
||||||
|
assert_eq!(errs, []);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_info_query<Query, Mutation, Context>(type_name: &str) -> Value
|
||||||
|
where
|
||||||
|
Query: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Mutation: GraphQLType<DefaultScalarValue, TypeInfo = (), Context = Context> + Default,
|
||||||
|
Context: Default,
|
||||||
|
{
|
||||||
|
let query = format!(
|
||||||
|
r#"
|
||||||
|
{{
|
||||||
|
__type(name: "{}") {{
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
fields {{
|
||||||
|
name
|
||||||
|
description
|
||||||
|
args {{
|
||||||
|
name
|
||||||
|
description
|
||||||
|
type {{
|
||||||
|
name
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
type_name
|
||||||
|
);
|
||||||
|
let result = run_query::<Query, Mutation, Context>(&query);
|
||||||
|
result
|
||||||
|
.as_object_value()
|
||||||
|
.expect("Result is not an object")
|
||||||
|
.get_field_value("__type")
|
||||||
|
.expect("__type field missing")
|
||||||
|
.clone()
|
||||||
|
}
|
|
@ -31,27 +31,31 @@ struct Foo {
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
|
||||||
graphql_object!(Query: () where Scalar = <S> |&self| {
|
#[crate::object_internal(Scalar = S)]
|
||||||
field int_field() -> i32 {
|
impl<'a, S> Query
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
|
{
|
||||||
|
fn int_field() -> i32 {
|
||||||
42
|
42
|
||||||
}
|
}
|
||||||
|
|
||||||
field float_field() -> f64 {
|
fn float_field() -> f64 {
|
||||||
3.14
|
3.14
|
||||||
}
|
}
|
||||||
|
|
||||||
field string_field() -> String {
|
fn string_field() -> String {
|
||||||
"".into()
|
"".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
field bool_field() -> bool {
|
fn bool_field() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
field enum_field(_foo: Foo) -> Enum {
|
fn enum_field(_foo: Foo) -> Enum {
|
||||||
Enum::EnumValue
|
Enum::EnumValue
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
fn scalar_meta<T>(name: &'static str) -> MetaType<DefaultScalarValue>
|
fn scalar_meta<T>(name: &'static str) -> MetaType<DefaultScalarValue>
|
||||||
where
|
where
|
||||||
|
|
|
@ -73,52 +73,68 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
graphql_object!(<'a> SchemaType<'a, S>: SchemaType<'a, S> as "__Schema"
|
#[crate::object_internal(
|
||||||
where Scalar = <S: 'a> |&self|
|
name = "__Schema"
|
||||||
|
Context = SchemaType<'a, S>,
|
||||||
|
Scalar = S,
|
||||||
|
)]
|
||||||
|
impl<'a, S> SchemaType<'a, S>
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
{
|
{
|
||||||
field types() -> Vec<TypeType<S>> {
|
fn types(&self) -> Vec<TypeType<S>> {
|
||||||
self.type_list()
|
self.type_list()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|t| t.to_concrete().map(|t| t.name() != Some("_EmptyMutation")).unwrap_or(false))
|
.filter(|t| {
|
||||||
.collect()
|
t.to_concrete()
|
||||||
|
.map(|t| t.name() != Some("_EmptyMutation"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
field query_type() -> TypeType<S> {
|
fn query_type(&self) -> TypeType<S> {
|
||||||
self.query_type()
|
self.query_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
field mutation_type() -> Option<TypeType<S>> {
|
fn mutation_type(&self) -> Option<TypeType<S>> {
|
||||||
self.mutation_type()
|
self.mutation_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Included for compatibility with the introspection query in GraphQL.js
|
// Included for compatibility with the introspection query in GraphQL.js
|
||||||
field subscription_type() -> Option<TypeType<S>> {
|
fn subscription_type(&self) -> Option<TypeType<S>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
field directives() -> Vec<&DirectiveType<S>> {
|
fn directives(&self) -> Vec<&DirectiveType<S>> {
|
||||||
self.directive_list()
|
self.directive_list()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type"
|
#[crate::object_internal(
|
||||||
where Scalar = <S: 'a> |&self|
|
name = "__Type"
|
||||||
|
Context = SchemaType<'a, S>,
|
||||||
|
Scalar = S,
|
||||||
|
)]
|
||||||
|
impl<'a, S> TypeType<'a, S>
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
{
|
{
|
||||||
field name() -> Option<&str> {
|
fn name(&self) -> Option<&str> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(t) => t.name(),
|
TypeType::Concrete(t) => t.name(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field description() -> Option<&String> {
|
fn description(&self) -> Option<&String> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(t) => t.description(),
|
TypeType::Concrete(t) => t.description(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field kind() -> TypeKind {
|
fn kind(&self) -> TypeKind {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(t) => t.type_kind(),
|
TypeType::Concrete(t) => t.type_kind(),
|
||||||
TypeType::List(_) => TypeKind::List,
|
TypeType::List(_) => TypeKind::List,
|
||||||
|
@ -126,190 +142,238 @@ graphql_object!(<'a> TypeType<'a, S>: SchemaType<'a, S> as "__Type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field fields(include_deprecated = false: bool) -> Option<Vec<&Field<S>>> {
|
#[graphql(arguments(include_deprecated(default = false)))]
|
||||||
|
fn fields(&self, include_deprecated: bool) -> Option<Vec<&Field<S>>> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. })) |
|
TypeType::Concrete(&MetaType::Interface(InterfaceMeta { ref fields, .. }))
|
||||||
TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) =>
|
| TypeType::Concrete(&MetaType::Object(ObjectMeta { ref fields, .. })) => Some(
|
||||||
Some(fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
|
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
|
||||||
.filter(|f| !f.name.starts_with("__"))
|
.filter(|f| !f.name.starts_with("__"))
|
||||||
.collect()),
|
.collect(),
|
||||||
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field of_type() -> Option<&Box<TypeType<S>>> {
|
fn of_type(&self) -> Option<&Box<TypeType<S>>> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(_) => None,
|
TypeType::Concrete(_) => None,
|
||||||
TypeType::List(ref l) | TypeType::NonNull(ref l) => Some(l),
|
TypeType::List(ref l) | TypeType::NonNull(ref l) => Some(l),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field input_fields() -> Option<&Vec<Argument<S>>> {
|
fn input_fields(&self) -> Option<&Vec<Argument<S>>> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(&MetaType::InputObject(InputObjectMeta { ref input_fields, .. })) =>
|
TypeType::Concrete(&MetaType::InputObject(InputObjectMeta {
|
||||||
Some(input_fields),
|
ref input_fields,
|
||||||
|
..
|
||||||
|
})) => Some(input_fields),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field interfaces(&executor) -> Option<Vec<TypeType<S>>> {
|
fn interfaces(&self, schema: &SchemaType<'a, S>) -> Option<Vec<TypeType<S>>> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(&MetaType::Object(ObjectMeta { ref interface_names, .. })) => {
|
TypeType::Concrete(&MetaType::Object(ObjectMeta {
|
||||||
let schema = executor.context();
|
ref interface_names,
|
||||||
Some(interface_names
|
..
|
||||||
|
})) => Some(
|
||||||
|
interface_names
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|n| schema.type_by_name(n))
|
.filter_map(|n| schema.type_by_name(n))
|
||||||
.collect())
|
.collect(),
|
||||||
}
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field possible_types(&executor) -> Option<Vec<TypeType<S>>> {
|
fn possible_types(&self, schema: &SchemaType<'a, S>) -> Option<Vec<TypeType<S>>> {
|
||||||
let schema = executor.context();
|
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(&MetaType::Union(UnionMeta { ref of_type_names, .. })) => {
|
TypeType::Concrete(&MetaType::Union(UnionMeta {
|
||||||
Some(of_type_names
|
ref of_type_names, ..
|
||||||
|
})) => Some(
|
||||||
|
of_type_names
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|tn| schema.type_by_name(tn))
|
.filter_map(|tn| schema.type_by_name(tn))
|
||||||
.collect())
|
.collect(),
|
||||||
}
|
),
|
||||||
TypeType::Concrete(&MetaType::Interface(InterfaceMeta{name: ref iface_name, .. })) => {
|
TypeType::Concrete(&MetaType::Interface(InterfaceMeta {
|
||||||
Some(schema.concrete_type_list()
|
name: ref iface_name,
|
||||||
|
..
|
||||||
|
})) => Some(
|
||||||
|
schema
|
||||||
|
.concrete_type_list()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|&ct|
|
.filter_map(|&ct| {
|
||||||
if let MetaType::Object(ObjectMeta{
|
if let MetaType::Object(ObjectMeta {
|
||||||
ref name,
|
ref name,
|
||||||
ref interface_names,
|
ref interface_names,
|
||||||
..
|
..
|
||||||
}) = *ct {
|
}) = *ct
|
||||||
|
{
|
||||||
if interface_names.contains(&iface_name.to_string()) {
|
if interface_names.contains(&iface_name.to_string()) {
|
||||||
schema.type_by_name(name)
|
schema.type_by_name(name)
|
||||||
} else { None }
|
} else {
|
||||||
} else { None }
|
None
|
||||||
)
|
}
|
||||||
.collect())
|
} else {
|
||||||
}
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field enum_values(include_deprecated = false: bool) -> Option<Vec<&EnumValue>> {
|
#[graphql(arguments(include_deprecated(default = false)))]
|
||||||
|
fn enum_values(&self, include_deprecated: bool) -> Option<Vec<&EnumValue>> {
|
||||||
match *self {
|
match *self {
|
||||||
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) =>
|
TypeType::Concrete(&MetaType::Enum(EnumMeta { ref values, .. })) => Some(
|
||||||
Some(values
|
values
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
|
.filter(|f| include_deprecated || !f.deprecation_status.is_deprecated())
|
||||||
.collect()),
|
.collect(),
|
||||||
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(<'a> Field<'a, S>: SchemaType<'a, S> as "__Field"
|
#[crate::object_internal(
|
||||||
where Scalar = <S: 'a> |&self|
|
name = "__Field",
|
||||||
|
Context = SchemaType<'a, S>,
|
||||||
|
Scalar = S,
|
||||||
|
)]
|
||||||
|
impl<'a, S> Field<'a, S>
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
{
|
{
|
||||||
field name() -> &String {
|
fn name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
field description() -> &Option<String> {
|
fn description(&self) -> &Option<String> {
|
||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
field args() -> Vec<&Argument<S>> {
|
fn args(&self) -> Vec<&Argument<S>> {
|
||||||
self.arguments.as_ref().map_or_else(Vec::new, |v| v.iter().collect())
|
self.arguments
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(Vec::new, |v| v.iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
field type(&executor) -> TypeType<S> {
|
#[graphql(name = "type")]
|
||||||
executor.context().make_type(&self.field_type)
|
fn _type(&self, context: &SchemaType<'a, S>) -> TypeType<S> {
|
||||||
|
context.make_type(&self.field_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
field is_deprecated() -> bool {
|
fn is_deprecated(&self) -> bool {
|
||||||
self.deprecation_status.is_deprecated()
|
self.deprecation_status.is_deprecated()
|
||||||
}
|
}
|
||||||
|
|
||||||
field deprecation_reason() -> Option<&String> {
|
fn deprecation_reason(&self) -> Option<&String> {
|
||||||
self.deprecation_status.reason()
|
self.deprecation_status.reason()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(<'a> Argument<'a, S>: SchemaType<'a, S> as "__InputValue"
|
#[crate::object_internal(
|
||||||
where Scalar = <S: 'a> |&self|
|
name = "__InputValue",
|
||||||
|
Context = SchemaType<'a, S>,
|
||||||
|
Scalar = S,
|
||||||
|
)]
|
||||||
|
impl<'a, S> Argument<'a, S>
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
{
|
{
|
||||||
field name() -> &String {
|
fn name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
field description() -> &Option<String> {
|
fn description(&self) -> &Option<String> {
|
||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
field type(&executor) -> TypeType<S> {
|
#[graphql(name = "type")]
|
||||||
executor.context().make_type(&self.arg_type)
|
fn _type(&self, context: &SchemaType<'a, S>) -> TypeType<S> {
|
||||||
|
context.make_type(&self.arg_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
field default_value() -> Option<String> {
|
fn default_value(&self) -> Option<String> {
|
||||||
self.default_value.as_ref().map(|v| format!("{}", v))
|
self.default_value.as_ref().map(|v| format!("{}", v))
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(EnumValue: () as "__EnumValue" where Scalar = <S> |&self| {
|
#[crate::object_internal(
|
||||||
field name() -> &String {
|
name = "__EnumValue",
|
||||||
|
Scalar = S,
|
||||||
|
)]
|
||||||
|
impl<'a, S> EnumValue
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
|
{
|
||||||
|
fn name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
field description() -> &Option<String> {
|
fn description(&self) -> &Option<String> {
|
||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
field is_deprecated() -> bool {
|
fn is_deprecated(&self) -> bool {
|
||||||
self.deprecation_status.is_deprecated()
|
self.deprecation_status.is_deprecated()
|
||||||
}
|
}
|
||||||
|
|
||||||
field deprecation_reason() -> Option<&String> {
|
fn deprecation_reason(&self) -> Option<&String> {
|
||||||
self.deprecation_status.reason()
|
self.deprecation_status.reason()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(<'a> DirectiveType<'a, S>: SchemaType<'a, S> as "__Directive"
|
#[crate::object_internal(
|
||||||
where Scalar = <S: 'a> |&self|
|
name = "__Directive",
|
||||||
|
Context = SchemaType<'a, S>,
|
||||||
|
Scalar = S,
|
||||||
|
)]
|
||||||
|
impl<'a, S> DirectiveType<'a, S>
|
||||||
|
where
|
||||||
|
S: crate::ScalarValue + 'a,
|
||||||
{
|
{
|
||||||
field name() -> &String {
|
fn name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
field description() -> &Option<String> {
|
fn description(&self) -> &Option<String> {
|
||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
field locations() -> &Vec<DirectiveLocation> {
|
fn locations(&self) -> &Vec<DirectiveLocation> {
|
||||||
&self.locations
|
&self.locations
|
||||||
}
|
}
|
||||||
|
|
||||||
field args() -> &Vec<Argument<S>> {
|
fn args(&self) -> &Vec<Argument<S>> {
|
||||||
&self.arguments
|
&self.arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Included for compatibility with the introspection query in GraphQL.js
|
// Included for compatibility with the introspection query in GraphQL.js
|
||||||
field deprecated "Use the locations array instead"
|
#[graphql(deprecated = "Use the locations array instead")]
|
||||||
on_operation() -> bool {
|
fn on_operation(&self) -> bool {
|
||||||
self.locations.contains(&DirectiveLocation::Query)
|
self.locations.contains(&DirectiveLocation::Query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Included for compatibility with the introspection query in GraphQL.js
|
// Included for compatibility with the introspection query in GraphQL.js
|
||||||
field deprecated "Use the locations array instead"
|
#[graphql(deprecated = "Use the locations array instead")]
|
||||||
on_fragment() -> bool {
|
fn on_fragment(&self) -> bool {
|
||||||
self.locations.contains(&DirectiveLocation::FragmentDefinition) ||
|
self.locations
|
||||||
self.locations.contains(&DirectiveLocation::InlineFragment) ||
|
.contains(&DirectiveLocation::FragmentDefinition)
|
||||||
self.locations.contains(&DirectiveLocation::FragmentSpread)
|
|| self.locations.contains(&DirectiveLocation::InlineFragment)
|
||||||
|
|| self.locations.contains(&DirectiveLocation::FragmentSpread)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Included for compatibility with the introspection query in GraphQL.js
|
// Included for compatibility with the introspection query in GraphQL.js
|
||||||
field deprecated "Use the locations array instead"
|
#[graphql(deprecated = "Use the locations array instead")]
|
||||||
on_field() -> bool {
|
fn on_field(&self) -> bool {
|
||||||
self.locations.contains(&DirectiveLocation::Field)
|
self.locations.contains(&DirectiveLocation::Field)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -29,80 +29,93 @@ graphql_interface!(<'a> &'a Character: Database as "Character" |&self| {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
graphql_object!(<'a> &'a Human: Database as "Human" |&self| {
|
#[crate::object_internal(
|
||||||
description: "A humanoid creature in the Star Wars universe."
|
Context = Database,
|
||||||
|
Scalar = crate::DefaultScalarValue,
|
||||||
interfaces: [&Character]
|
interfaces = [&dyn Character],
|
||||||
|
)]
|
||||||
field id() -> &str as "The id of the human"{
|
/// A humanoid creature in the Star Wars universe.
|
||||||
|
impl<'a> &'a Human {
|
||||||
|
/// The id of the human
|
||||||
|
fn id(&self) -> &str {
|
||||||
self.id()
|
self.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
field name() -> Option<&str> as "The name of the human" {
|
/// The name of the human
|
||||||
|
fn name(&self) -> Option<&str> {
|
||||||
Some(self.name())
|
Some(self.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
field friends(&executor) -> Vec<&Character>
|
/// The friends of the human
|
||||||
as "The friends of the human" {
|
fn friends(&self, ctx: &Database) -> Vec<&Character> {
|
||||||
executor.context().get_friends(self.as_character())
|
ctx.get_friends(self.as_character())
|
||||||
}
|
}
|
||||||
|
|
||||||
field appears_in() -> &[Episode] as "Which movies they appear in" {
|
/// Which movies they appear in
|
||||||
|
fn appears_in(&self) -> &[Episode] {
|
||||||
self.appears_in()
|
self.appears_in()
|
||||||
}
|
}
|
||||||
|
|
||||||
field home_planet() -> &Option<String> as "The home planet of the human" {
|
/// The home planet of the human
|
||||||
|
fn home_planet(&self) -> &Option<String> {
|
||||||
self.home_planet()
|
self.home_planet()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(<'a> &'a Droid: Database as "Droid" |&self| {
|
#[crate::object_internal(
|
||||||
description: "A mechanical creature in the Star Wars universe."
|
Context = Database,
|
||||||
|
Scalar = crate::DefaultScalarValue,
|
||||||
interfaces: [&Character]
|
interfaces = [&dyn Character],
|
||||||
|
)]
|
||||||
field id() -> &str as "The id of the droid" {
|
/// A mechanical creature in the Star Wars universe.
|
||||||
|
impl<'a> &'a Droid {
|
||||||
|
/// The id of the droid
|
||||||
|
fn id(&self) -> &str {
|
||||||
self.id()
|
self.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
field name() -> Option<&str> as "The name of the droid" {
|
/// The name of the droid
|
||||||
|
fn name(&self) -> Option<&str> {
|
||||||
Some(self.name())
|
Some(self.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
field friends(&executor) -> Vec<&Character>
|
/// The friends of the droid
|
||||||
as "The friends of the droid" {
|
fn friends(&self, ctx: &Database) -> Vec<&Character> {
|
||||||
executor.context().get_friends(self.as_character())
|
ctx.get_friends(self.as_character())
|
||||||
}
|
}
|
||||||
|
|
||||||
field appears_in() -> &[Episode] as "Which movies they appear in" {
|
/// Which movies they appear in
|
||||||
|
fn appears_in(&self) -> &[Episode] {
|
||||||
self.appears_in()
|
self.appears_in()
|
||||||
}
|
}
|
||||||
|
|
||||||
field primary_function() -> &Option<String> as "The primary function of the droid" {
|
/// The primary function of the droid
|
||||||
|
fn primary_function(&self) -> &Option<String> {
|
||||||
self.primary_function()
|
self.primary_function()
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
graphql_object!(Database: Database as "Query" |&self| {
|
#[crate::object_internal(
|
||||||
description: "The root query object of the schema"
|
name = "Query",
|
||||||
|
Context = Database,
|
||||||
field human(
|
Scalar = crate::DefaultScalarValue,
|
||||||
id: String as "id of the human"
|
)]
|
||||||
) -> Option<&Human> {
|
/// The root query object of the schema
|
||||||
|
impl Database {
|
||||||
|
#[graphql(arguments(id(description = "id of the human")))]
|
||||||
|
fn human(&self, id: String) -> Option<&Human> {
|
||||||
self.get_human(&id)
|
self.get_human(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
field droid(
|
#[graphql(arguments(id(description = "id of the droid")))]
|
||||||
id: String as "id of the droid"
|
fn droid(&self, id: String) -> Option<&Droid> {
|
||||||
) -> Option<&Droid> {
|
|
||||||
self.get_droid(&id)
|
self.get_droid(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
field hero(
|
#[graphql(arguments(episode(
|
||||||
episode: Option<Episode> as
|
description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode"
|
||||||
"If omitted, returns the hero of the whole saga. If provided, returns \
|
)))]
|
||||||
the hero of that particular episode"
|
fn hero(&self, episode: Option<Episode>) -> Option<&Character> {
|
||||||
) -> Option<&Character> {
|
|
||||||
Some(self.get_hero(episode).as_character())
|
Some(self.get_hero(episode).as_character())
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -344,56 +344,56 @@ pub(crate) fn schema_introspection_result() -> value::Value {
|
||||||
{
|
{
|
||||||
"kind": "ENUM",
|
"kind": "ENUM",
|
||||||
"name": "__TypeKind",
|
"name": "__TypeKind",
|
||||||
"description": "GraphQL type kind\nThe GraphQL specification defines a number of type kinds - the meta type of a type.",
|
"description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.",
|
||||||
"fields": Null,
|
"fields": Null,
|
||||||
"inputFields": Null,
|
"inputFields": Null,
|
||||||
"interfaces": Null,
|
"interfaces": Null,
|
||||||
"enumValues": [
|
"enumValues": [
|
||||||
{
|
{
|
||||||
"name": "SCALAR",
|
"name": "SCALAR",
|
||||||
"description": "## Scalar types\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.",
|
"description": "## Scalar types\n\nScalar types appear as the leaf nodes of GraphQL queries. Strings, numbers, and booleans are the built in types, and while it's possible to define your own, it's relatively uncommon.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "OBJECT",
|
"name": "OBJECT",
|
||||||
"description": "## Object types\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.",
|
"description": "## Object types\n\nThe most common type to be implemented by users. Objects have fields and can implement interfaces.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "INTERFACE",
|
"name": "INTERFACE",
|
||||||
"description": "## Interface types\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.",
|
"description": "## Interface types\n\nInterface types are used to represent overlapping fields between multiple types, and can be queried for their concrete type.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "UNION",
|
"name": "UNION",
|
||||||
"description": "## Union types\nUnions are similar to interfaces but can not contain any fields on their own.",
|
"description": "## Union types\n\nUnions are similar to interfaces but can not contain any fields on their own.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ENUM",
|
"name": "ENUM",
|
||||||
"description": "## Enum types\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.",
|
"description": "## Enum types\n\nLike scalars, enum types appear as the leaf nodes of GraphQL queries.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "INPUT_OBJECT",
|
"name": "INPUT_OBJECT",
|
||||||
"description": "## Input objects\nRepresents complex values provided in queries _into_ the system.",
|
"description": "## Input objects\n\nRepresents complex values provided in queries _into_ the system.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "LIST",
|
"name": "LIST",
|
||||||
"description": "## List types\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.",
|
"description": "## List types\n\nRepresent lists of other types. This library provides implementations for vectors and slices, but other Rust types can be extended to serve as GraphQL lists.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "NON_NULL",
|
"name": "NON_NULL",
|
||||||
"description": "## Non-null types\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.",
|
"description": "## Non-null types\n\nIn GraphQL, nullable types are the default. By putting a `!` after a type, it becomes non-nullable.",
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": Null
|
"deprecationReason": Null
|
||||||
}
|
}
|
||||||
|
@ -827,6 +827,7 @@ pub(crate) fn schema_introspection_result() -> value::Value {
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
"description": Null,
|
||||||
"description": "id of the droid",
|
"description": "id of the droid",
|
||||||
"type": {
|
"type": {
|
||||||
"kind": "NON_NULL",
|
"kind": "NON_NULL",
|
||||||
|
|
|
@ -12,33 +12,33 @@ use crate::schema::meta::{Argument, MetaType};
|
||||||
|
|
||||||
/// GraphQL type kind
|
/// GraphQL type kind
|
||||||
///
|
///
|
||||||
/// The GraphQL specification defines a number of type kinds - the meta type
|
/// The GraphQL specification defines a number of type kinds - the meta type\
|
||||||
/// of a type.
|
/// of a type.
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)]
|
#[derive(Clone, Eq, PartialEq, Debug, GraphQLEnum)]
|
||||||
#[graphql(name = "__TypeKind")]
|
#[graphql(name = "__TypeKind")]
|
||||||
pub enum TypeKind {
|
pub enum TypeKind {
|
||||||
/// ## Scalar types
|
/// ## Scalar types
|
||||||
///
|
///
|
||||||
/// Scalar types appear as the leaf nodes of GraphQL queries. Strings,
|
/// Scalar types appear as the leaf nodes of GraphQL queries. Strings,\
|
||||||
/// numbers, and booleans are the built in types, and while it's possible
|
/// numbers, and booleans are the built in types, and while it's possible\
|
||||||
/// to define your own, it's relatively uncommon.
|
/// to define your own, it's relatively uncommon.
|
||||||
Scalar,
|
Scalar,
|
||||||
|
|
||||||
/// ## Object types
|
/// ## Object types
|
||||||
///
|
///
|
||||||
/// The most common type to be implemented by users. Objects have fields
|
/// The most common type to be implemented by users. Objects have fields\
|
||||||
/// and can implement interfaces.
|
/// and can implement interfaces.
|
||||||
Object,
|
Object,
|
||||||
|
|
||||||
/// ## Interface types
|
/// ## Interface types
|
||||||
///
|
///
|
||||||
/// Interface types are used to represent overlapping fields between
|
/// Interface types are used to represent overlapping fields between\
|
||||||
/// multiple types, and can be queried for their concrete type.
|
/// multiple types, and can be queried for their concrete type.
|
||||||
Interface,
|
Interface,
|
||||||
|
|
||||||
/// ## Union types
|
/// ## Union types
|
||||||
///
|
///
|
||||||
/// Unions are similar to interfaces but can not contain any fields on
|
/// Unions are similar to interfaces but can not contain any fields on\
|
||||||
/// their own.
|
/// their own.
|
||||||
Union,
|
Union,
|
||||||
|
|
||||||
|
@ -55,14 +55,14 @@ pub enum TypeKind {
|
||||||
|
|
||||||
/// ## List types
|
/// ## List types
|
||||||
///
|
///
|
||||||
/// Represent lists of other types. This library provides implementations
|
/// Represent lists of other types. This library provides implementations\
|
||||||
/// for vectors and slices, but other Rust types can be extended to serve
|
/// for vectors and slices, but other Rust types can be extended to serve\
|
||||||
/// as GraphQL lists.
|
/// as GraphQL lists.
|
||||||
List,
|
List,
|
||||||
|
|
||||||
/// ## Non-null types
|
/// ## Non-null types
|
||||||
///
|
///
|
||||||
/// In GraphQL, nullable types are the default. By putting a `!` after a
|
/// In GraphQL, nullable types are the default. By putting a `!` after a\
|
||||||
/// type, it becomes non-nullable.
|
/// type, it becomes non-nullable.
|
||||||
#[graphql(name = "NON_NULL")]
|
#[graphql(name = "NON_NULL")]
|
||||||
NonNull,
|
NonNull,
|
||||||
|
|
|
@ -16,10 +16,13 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "0.4"
|
proc-macro2 = "0.4"
|
||||||
syn = { version = "0.14", features = ["full", "extra-traits"] }
|
syn = { version = "0.15.28", features = ["full", "extra-traits", "parsing"] }
|
||||||
quote = "0.6"
|
quote = "0.6"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1.0.0"
|
lazy_static = "1.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
juniper = { version = "0.11.1", path = "../juniper" }
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "graphql-rust/juniper" }
|
travis-ci = { repository = "graphql-rust/juniper" }
|
||||||
|
|
|
@ -1,143 +1,13 @@
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{self, parse_quote, Data, DeriveInput, Field, Fields, Ident};
|
use syn::{self, Data, Fields};
|
||||||
|
|
||||||
use crate::util::*;
|
use crate::util;
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
pub fn build_derive_object(ast: syn::DeriveInput, is_internal: bool) -> TokenStream {
|
||||||
struct ObjAttrs {
|
let struct_fields = match ast.data {
|
||||||
name: Option<String>,
|
Data::Struct(data) => match data.fields {
|
||||||
description: Option<String>,
|
Fields::Named(fields) => fields.named,
|
||||||
context: Option<Ident>,
|
|
||||||
scalar: Option<Ident>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjAttrs {
|
|
||||||
fn from_input(input: &DeriveInput) -> ObjAttrs {
|
|
||||||
let mut res = ObjAttrs::default();
|
|
||||||
|
|
||||||
// Check doc comments for description.
|
|
||||||
res.description = get_doc_comment(&input.attrs);
|
|
||||||
|
|
||||||
// Check attributes for name and description.
|
|
||||||
if let Some(items) = get_graphql_attr(&input.attrs) {
|
|
||||||
for item in items {
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "name", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
if is_valid_name(&*val) {
|
|
||||||
res.name = Some(val);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not",
|
|
||||||
&*val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "description", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.description = Some(val);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(scalar)) =
|
|
||||||
keyed_item_value(&item, "scalar", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.scalar = Some(Ident::new(&scalar as &str, Span::call_site()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(ctx)) =
|
|
||||||
keyed_item_value(&item, "Context", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.context = Some(Ident::new(&ctx as &str, Span::call_site()));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
panic!(format!(
|
|
||||||
"Unknown struct attribute for #[derive(GraphQLObject)]: {:?}",
|
|
||||||
item
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ObjFieldAttrs {
|
|
||||||
name: Option<String>,
|
|
||||||
description: Option<String>,
|
|
||||||
deprecation: Option<DeprecationAttr>,
|
|
||||||
skip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjFieldAttrs {
|
|
||||||
fn from_input(variant: &Field) -> ObjFieldAttrs {
|
|
||||||
let mut res = ObjFieldAttrs::default();
|
|
||||||
|
|
||||||
// Check doc comments for description.
|
|
||||||
res.description = get_doc_comment(&variant.attrs);
|
|
||||||
|
|
||||||
// Check builtin deprecated attribute for deprecation.
|
|
||||||
res.deprecation = get_deprecated(&variant.attrs);
|
|
||||||
|
|
||||||
// Check attributes.
|
|
||||||
if let Some(items) = get_graphql_attr(&variant.attrs) {
|
|
||||||
for item in items {
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "name", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
if is_valid_name(&*val) {
|
|
||||||
res.name = Some(val);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not",
|
|
||||||
&*val
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "description", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.description = Some(val);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(AttributeValue::String(val)) =
|
|
||||||
keyed_item_value(&item, "deprecation", AttributeValidation::String)
|
|
||||||
{
|
|
||||||
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match keyed_item_value(&item, "deprecated", AttributeValidation::String) {
|
|
||||||
Some(AttributeValue::String(val)) => {
|
|
||||||
res.deprecation = Some(DeprecationAttr { reason: Some(val) });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Some(AttributeValue::Bare) => {
|
|
||||||
res.deprecation = Some(DeprecationAttr { reason: None });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
if let Some(_) = keyed_item_value(&item, "skip", AttributeValidation::Bare) {
|
|
||||||
res.skip = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
panic!(format!(
|
|
||||||
"Unknown field attribute for #[derive(GraphQLObject)]: {:?}",
|
|
||||||
item
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream {
|
|
||||||
let fields = match ast.data {
|
|
||||||
Data::Struct(ref data) => match data.fields {
|
|
||||||
Fields::Named(ref fields) => fields.named.iter().collect::<Vec<_>>(),
|
|
||||||
_ => {
|
_ => {
|
||||||
panic!("#[derive(GraphQLObject)] may only be used on regular structs with fields");
|
panic!("#[derive(GraphQLObject)] may only be used on regular structs with fields");
|
||||||
}
|
}
|
||||||
|
@ -148,140 +18,63 @@ pub fn impl_object(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse attributes.
|
// Parse attributes.
|
||||||
let ident = &ast.ident;
|
let attrs = match util::ObjectAttributes::from_attrs(&ast.attrs) {
|
||||||
let generics = &ast.generics;
|
Ok(a) => a,
|
||||||
let ident_name = ident.to_string();
|
Err(e) => {
|
||||||
let attrs = ObjAttrs::from_input(ast);
|
panic!("Invalid #[graphql(...)] attribute: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if attrs.interfaces.len() > 0 {
|
||||||
|
panic!("Invalid #[graphql(...)] attribute 'interfaces': #[derive(GraphQLObject) does not support 'interfaces'");
|
||||||
|
}
|
||||||
let name = attrs.name.unwrap_or(ast.ident.to_string());
|
let name = attrs.name.unwrap_or(ast.ident.to_string());
|
||||||
let build_description = match attrs.description {
|
|
||||||
Some(s) => quote! { builder.description(#s) },
|
|
||||||
None => quote! { builder },
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut meta_fields = TokenStream::new();
|
let fields = struct_fields.into_iter().filter_map(|field| {
|
||||||
let mut resolvers = TokenStream::new();
|
let field_attrs = match util::FieldAttributes::from_attrs(
|
||||||
|
field.attrs,
|
||||||
|
util::FieldAttributeParseMode::Object,
|
||||||
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(e) => panic!("Invalid #[graphql] attribute: \n{}", e),
|
||||||
|
};
|
||||||
|
|
||||||
for field in fields {
|
|
||||||
let field_ty = &field.ty;
|
|
||||||
let field_attrs = ObjFieldAttrs::from_input(field);
|
|
||||||
let field_ident = field.ident.as_ref().unwrap();
|
|
||||||
|
|
||||||
// Check if we should skip this field.
|
|
||||||
if field_attrs.skip {
|
if field_attrs.skip {
|
||||||
continue;
|
None
|
||||||
|
} else {
|
||||||
|
let field_name = field.ident.unwrap();
|
||||||
|
let name = field_attrs
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| util::to_camel_case(&field_name.to_string()));
|
||||||
|
|
||||||
|
let resolver_code = quote!(
|
||||||
|
&self . #field_name
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(util::GraphQLTypeDefinitionField {
|
||||||
|
name,
|
||||||
|
_type: field.ty,
|
||||||
|
args: Vec::new(),
|
||||||
|
description: field_attrs.description,
|
||||||
|
deprecation: field_attrs.deprecation,
|
||||||
|
resolver_code,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Build value.
|
let definition = util::GraphQLTypeDefiniton {
|
||||||
let name = match field_attrs.name {
|
name,
|
||||||
Some(ref name) => {
|
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
|
||||||
// Custom name specified.
|
context: attrs.context,
|
||||||
name.to_string()
|
scalar: attrs.scalar,
|
||||||
}
|
description: attrs.description,
|
||||||
None => {
|
fields: fields.collect(),
|
||||||
// Note: auto camel casing when no custom name specified.
|
generics: ast.generics.clone(),
|
||||||
crate::util::to_camel_case(&field_ident.to_string())
|
interfaces: None,
|
||||||
}
|
include_type_generics: true,
|
||||||
};
|
generic_scalar: true,
|
||||||
let build_description = match field_attrs.description {
|
|
||||||
Some(s) => quote! { field.description(#s) },
|
|
||||||
None => quote! { field },
|
|
||||||
};
|
|
||||||
|
|
||||||
let build_deprecation = match field_attrs.deprecation {
|
|
||||||
Some(DeprecationAttr { reason: Some(s) }) => quote! { field.deprecated(Some(#s)) },
|
|
||||||
Some(DeprecationAttr { reason: None }) => quote! { field.deprecated(None) },
|
|
||||||
None => quote! { field },
|
|
||||||
};
|
|
||||||
|
|
||||||
meta_fields.extend(quote! {
|
|
||||||
{
|
|
||||||
let field = registry.field::<#field_ty>(#name, &());
|
|
||||||
let field = #build_description;
|
|
||||||
let field = #build_deprecation;
|
|
||||||
field
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build from_input clause.
|
|
||||||
|
|
||||||
resolvers.extend(quote! {
|
|
||||||
#name => executor.resolve_with_ctx(&(), &self.#field_ident),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let (_, ty_generics, _) = generics.split_for_impl();
|
|
||||||
|
|
||||||
let mut generics = generics.clone();
|
|
||||||
|
|
||||||
if attrs.scalar.is_none() {
|
|
||||||
generics.params.push(parse_quote!(__S));
|
|
||||||
{
|
|
||||||
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
|
|
||||||
where_clause
|
|
||||||
.predicates
|
|
||||||
.push(parse_quote!(__S: juniper::ScalarValue));
|
|
||||||
where_clause
|
|
||||||
.predicates
|
|
||||||
.push(parse_quote!(for<'__b> &'__b __S: juniper::ScalarRefValue<'__b>));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let scalar = attrs
|
|
||||||
.scalar
|
|
||||||
.unwrap_or_else(|| Ident::new("__S", Span::call_site()));
|
|
||||||
|
|
||||||
let ctx = attrs
|
|
||||||
.context
|
|
||||||
.map(|ident| quote!( #ident ))
|
|
||||||
.unwrap_or(quote!(()));
|
|
||||||
|
|
||||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
|
||||||
|
|
||||||
let body = quote! {
|
|
||||||
impl#impl_generics juniper::GraphQLType<#scalar> for #ident #ty_generics
|
|
||||||
#where_clause
|
|
||||||
{
|
|
||||||
type Context = #ctx;
|
|
||||||
type TypeInfo = ();
|
|
||||||
|
|
||||||
fn name(_: &()) -> Option<&str> {
|
|
||||||
Some(#name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn concrete_type_name(&self, _: &Self::Context, _: &()) -> String {
|
|
||||||
#name.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn meta<'r>(
|
|
||||||
_: &(),
|
|
||||||
registry: &mut juniper::Registry<'r, #scalar>
|
|
||||||
) -> juniper::meta::MetaType<'r, #scalar>
|
|
||||||
where #scalar: 'r
|
|
||||||
{
|
|
||||||
let fields = &[
|
|
||||||
#(#meta_fields)*
|
|
||||||
];
|
|
||||||
let builder = registry.build_object_type::<#ident>(&(), fields);
|
|
||||||
let builder = #build_description;
|
|
||||||
builder.into_meta()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_field(
|
|
||||||
&self,
|
|
||||||
_: &(),
|
|
||||||
field_name: &str,
|
|
||||||
_: &juniper::Arguments<#scalar>,
|
|
||||||
executor: &juniper::Executor<Self::Context, #scalar>
|
|
||||||
) -> juniper::ExecutionResult<#scalar>
|
|
||||||
{
|
|
||||||
|
|
||||||
match field_name {
|
|
||||||
#(#resolvers)*
|
|
||||||
_ => panic!("Field {} not found on type {}", field_name, #ident_name),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
body
|
|
||||||
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
|
definition.into_tokens(juniper_crate_name)
|
||||||
}
|
}
|
||||||
|
|
229
juniper_codegen/src/impl_object.rs
Normal file
229
juniper_codegen/src/impl_object.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
use crate::util;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
/// Generate code for the juniper::object macro.
|
||||||
|
pub fn build_object(args: TokenStream, body: TokenStream, is_internal: bool) -> TokenStream {
|
||||||
|
let impl_attrs = match syn::parse::<util::ObjectAttributes>(args) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Invalid attributes:\n{}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let item = match syn::parse::<syn::Item>(body) {
|
||||||
|
Ok(item) => item,
|
||||||
|
Err(err) => {
|
||||||
|
panic!("Parsing error:\n{}", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut _impl = match item {
|
||||||
|
syn::Item::Impl(_impl) => _impl,
|
||||||
|
_ => {
|
||||||
|
panic!("#[juniper::object] can only be applied to impl blocks");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match _impl.trait_ {
|
||||||
|
Some((_, ref path, _)) => {
|
||||||
|
let name = path
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.map(|segment| segment.ident.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(".");
|
||||||
|
if !(name == "GraphQLObject" || name == "juniper.GraphQLObject") {
|
||||||
|
panic!("The impl block must implement the 'GraphQLObject' trait");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// panic!("The impl block must implement the 'GraphQLObject' trait");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = match impl_attrs.name.as_ref() {
|
||||||
|
Some(type_name) => type_name.clone(),
|
||||||
|
None => match &*_impl.self_ty {
|
||||||
|
syn::Type::Path(ref type_path) => type_path
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.ident
|
||||||
|
.to_string(),
|
||||||
|
syn::Type::Reference(ref reference) => match &*reference.elem {
|
||||||
|
syn::Type::Path(ref type_path) => type_path
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.ident
|
||||||
|
.to_string(),
|
||||||
|
_ => {
|
||||||
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
panic!("Could not determine a name for the object type: specify one with #[juniper::object(name = \"SomeName\")");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_type = *_impl.self_ty.clone();
|
||||||
|
|
||||||
|
let description = impl_attrs
|
||||||
|
.description
|
||||||
|
.or(util::get_doc_comment(&_impl.attrs));
|
||||||
|
|
||||||
|
let mut definition = util::GraphQLTypeDefiniton {
|
||||||
|
name,
|
||||||
|
_type: target_type.clone(),
|
||||||
|
context: impl_attrs.context,
|
||||||
|
scalar: impl_attrs.scalar,
|
||||||
|
description,
|
||||||
|
fields: Vec::new(),
|
||||||
|
generics: _impl.generics.clone(),
|
||||||
|
interfaces: if impl_attrs.interfaces.len() > 0 {
|
||||||
|
Some(impl_attrs.interfaces)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
include_type_generics: false,
|
||||||
|
generic_scalar: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in _impl.items {
|
||||||
|
match item {
|
||||||
|
syn::ImplItem::Method(method) => {
|
||||||
|
let _type = match &method.sig.decl.output {
|
||||||
|
syn::ReturnType::Type(_, ref t) => (**t).clone(),
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
panic!(
|
||||||
|
"Invalid field method {}: must return a value",
|
||||||
|
method.sig.ident
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let attrs = match util::FieldAttributes::from_attrs(
|
||||||
|
method.attrs,
|
||||||
|
util::FieldAttributeParseMode::Impl,
|
||||||
|
) {
|
||||||
|
Ok(attrs) => attrs,
|
||||||
|
Err(err) => panic!(
|
||||||
|
"Invalid #[graphql(...)] attribute on field {}:\n{}",
|
||||||
|
method.sig.ident, err
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut args = Vec::new();
|
||||||
|
let mut resolve_parts = Vec::new();
|
||||||
|
|
||||||
|
for arg in method.sig.decl.inputs {
|
||||||
|
match arg {
|
||||||
|
_self @ syn::FnArg::SelfRef(_) => {
|
||||||
|
// Can be ignored.
|
||||||
|
// "self" will already be in scope.
|
||||||
|
// resolve_args.push(quote!(self));
|
||||||
|
}
|
||||||
|
syn::FnArg::SelfValue(_) => {
|
||||||
|
panic!(
|
||||||
|
"Invalid method receiver {}(self, ...): did you mean '&self'?",
|
||||||
|
method.sig.ident
|
||||||
|
);
|
||||||
|
}
|
||||||
|
syn::FnArg::Captured(ref captured) => {
|
||||||
|
let arg_ident = match &captured.pat {
|
||||||
|
syn::Pat::Ident(ref pat_ident) => &pat_ident.ident,
|
||||||
|
_ => {
|
||||||
|
panic!("Invalid token for function argument");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let arg_name = arg_ident.to_string();
|
||||||
|
|
||||||
|
let context_type = definition.context.as_ref();
|
||||||
|
|
||||||
|
// Check for executor arguments.
|
||||||
|
if util::type_is_identifier_ref(&captured.ty, "Executor") {
|
||||||
|
resolve_parts.push(quote!(let #arg_ident = executor;));
|
||||||
|
}
|
||||||
|
// Make sure executor is specified as a reference.
|
||||||
|
else if util::type_is_identifier(&captured.ty, "Executor") {
|
||||||
|
panic!("Invalid executor argument: to access the Executor, you need to specify the type as a reference.\nDid you mean &Executor?");
|
||||||
|
}
|
||||||
|
// Check for executor arg.
|
||||||
|
else if context_type
|
||||||
|
.clone()
|
||||||
|
.map(|ctx| util::type_is_ref_of(&captured.ty, ctx))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
resolve_parts.push(quote!( let #arg_ident = executor.context(); ));
|
||||||
|
}
|
||||||
|
// Make sure the user does not specify the Context
|
||||||
|
// without a reference. (&Context)
|
||||||
|
else if context_type
|
||||||
|
.clone()
|
||||||
|
.map(|ctx| ctx == &captured.ty)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
panic!(
|
||||||
|
"Invalid context argument: to access the context, you need to specify the type as a reference.\nDid you mean &{}?",
|
||||||
|
quote!(captured.ty),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let ty = &captured.ty;
|
||||||
|
// TODO: respect graphql attribute overwrite.
|
||||||
|
let final_name = util::to_camel_case(&arg_name);
|
||||||
|
resolve_parts.push(quote!(
|
||||||
|
let #arg_ident = args
|
||||||
|
.get::<#ty>(#final_name)
|
||||||
|
.expect(&format!("Internal error: missing argument {} - validation must have failed", #final_name));
|
||||||
|
));
|
||||||
|
args.push(util::GraphQLTypeDefinitionFieldArg {
|
||||||
|
description: attrs.argument(&arg_name).and_then(|arg| {
|
||||||
|
arg.description.as_ref().map(|d| d.value())
|
||||||
|
}),
|
||||||
|
default: attrs
|
||||||
|
.argument(&arg_name)
|
||||||
|
.and_then(|arg| arg.default.clone()),
|
||||||
|
_type: ty.clone(),
|
||||||
|
name: final_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid argument type in method {}", method.sig.ident),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = &method.block;
|
||||||
|
let return_ty = &method.sig.decl.output;
|
||||||
|
let resolver_code = quote!(
|
||||||
|
(|| #return_ty {
|
||||||
|
#( #resolve_parts )*
|
||||||
|
#body
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
|
||||||
|
let name = attrs
|
||||||
|
.name
|
||||||
|
.unwrap_or(util::to_camel_case(&method.sig.ident.to_string()));
|
||||||
|
|
||||||
|
definition.fields.push(util::GraphQLTypeDefinitionField {
|
||||||
|
name,
|
||||||
|
_type,
|
||||||
|
args,
|
||||||
|
description: attrs.description,
|
||||||
|
deprecation: attrs.deprecation,
|
||||||
|
resolver_code,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Invalid item for GraphQL Object: only type declarations and methods are allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let juniper_crate_name = if is_internal { "crate" } else { "juniper" };
|
||||||
|
definition.into_tokens(juniper_crate_name).into()
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ mod derive_enum;
|
||||||
mod derive_input_object;
|
mod derive_input_object;
|
||||||
mod derive_object;
|
mod derive_object;
|
||||||
mod derive_scalar_value;
|
mod derive_scalar_value;
|
||||||
|
mod impl_object;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
@ -49,7 +50,7 @@ pub fn derive_input_object_internal(input: TokenStream) -> TokenStream {
|
||||||
#[proc_macro_derive(GraphQLObject, attributes(graphql))]
|
#[proc_macro_derive(GraphQLObject, attributes(graphql))]
|
||||||
pub fn derive_object(input: TokenStream) -> TokenStream {
|
pub fn derive_object(input: TokenStream) -> TokenStream {
|
||||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
let gen = derive_object::impl_object(&ast);
|
let gen = derive_object::build_derive_object(ast, false);
|
||||||
gen.into()
|
gen.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,3 +74,252 @@ pub fn derive_scalar_value_internal(input: TokenStream) -> TokenStream {
|
||||||
let gen = derive_scalar_value::impl_scalar_value(&ast, true);
|
let gen = derive_scalar_value::impl_scalar_value(&ast, true);
|
||||||
gen.into()
|
gen.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The `object` proc macro is the primary way of defining GraphQL resolvers
|
||||||
|
that can not be implemented with the GraphQLObject derive.
|
||||||
|
|
||||||
|
It enables you to write GraphQL field resolvers for a type by declaring a
|
||||||
|
regular Rust `impl` block. Under the hood, the procedural macro implements
|
||||||
|
the GraphQLType trait.
|
||||||
|
|
||||||
|
`object` comes with many features that allow customization of
|
||||||
|
your fields, all of which are detailed below.
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
This simple example will show you the most basic use of `object`.
|
||||||
|
More advanced use cases are introduced step by step.
|
||||||
|
|
||||||
|
```
|
||||||
|
// So we can declare it as a plain struct without any members.
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
// We prefix the impl Block with the procedural macro.
|
||||||
|
#[juniper::object]
|
||||||
|
impl Query {
|
||||||
|
|
||||||
|
// A **warning**: only GraphQL fields can be specified in this impl block.
|
||||||
|
// If you want to define normal methods on the struct,
|
||||||
|
// you have to do so in a separate, normal `impl` block.
|
||||||
|
|
||||||
|
|
||||||
|
// This defines a simple, static field which does not require any context.
|
||||||
|
// You can return any value that implements the `GraphQLType` trait.
|
||||||
|
// This trait is implemented for:
|
||||||
|
// - basic scalar types like bool, &str, String, i32, f64
|
||||||
|
// - GraphQL compatible wrappers like Option<_>, Vec<_>.
|
||||||
|
// - types which use the `#derive[juniper::GraphQLObject]`
|
||||||
|
// - `object` structs.
|
||||||
|
//
|
||||||
|
// An important note regarding naming:
|
||||||
|
// By default, field names will be converted to camel case.
|
||||||
|
// For your GraphQL queries, the field will be available as `apiVersion`.
|
||||||
|
//
|
||||||
|
// You can also manually customize the field name if required. (See below)
|
||||||
|
fn api_version() -> &'static str {
|
||||||
|
"0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// This field takes two arguments.
|
||||||
|
// GraphQL arguments are just regular function parameters.
|
||||||
|
// **Note**: in Juniper, arguments are non-nullable by default.
|
||||||
|
// for optional arguments, you have to specify them with Option<T>.
|
||||||
|
fn add(a: f64, b: f64, c: Option<f64>) -> f64 {
|
||||||
|
a + b + c.unwrap_or(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing self
|
||||||
|
|
||||||
|
```
|
||||||
|
struct Person {
|
||||||
|
first_name: String,
|
||||||
|
last_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Person {
|
||||||
|
// The full name method is useful outside of GraphQL,
|
||||||
|
// so we define it as a normal method.
|
||||||
|
fn build_full_name(&self) -> String {
|
||||||
|
format!("{} {}", self.first_name, self.last_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[juniper::object]
|
||||||
|
impl Person {
|
||||||
|
fn first_name(&self) -> &str {
|
||||||
|
&self.first_name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_name(&self) -> &str {
|
||||||
|
&self.last_name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_name(&self) -> String {
|
||||||
|
self.build_full_name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context (+ Executor)
|
||||||
|
|
||||||
|
You can specify a context that will be available across
|
||||||
|
all your resolvers during query execution.
|
||||||
|
|
||||||
|
The Context can be injected into your resolvers by just
|
||||||
|
specifying an argument with the same type as the context
|
||||||
|
(but as a reference).
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# #[derive(juniper::GraphQLObject)] struct User { id: i32 }
|
||||||
|
# struct DbPool;
|
||||||
|
# impl DbPool { fn user(&self, id: i32) -> Option<User> { unimplemented!() } }
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
db: DbPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark our struct for juniper.
|
||||||
|
impl juniper::Context for Context {}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[juniper::object(
|
||||||
|
// Here we specify the context type for this object.
|
||||||
|
Context = Context,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
// Context is injected by specifying a argument
|
||||||
|
// as a reference to the Context.
|
||||||
|
fn user(context: &Context, id: i32) -> Option<User> {
|
||||||
|
context.db.user(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can also gain access to the executor, which
|
||||||
|
// allows you to do look aheads.
|
||||||
|
fn with_executor(executor: &Executor) -> bool {
|
||||||
|
let info = executor.look_ahead();
|
||||||
|
// ...
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization (Documentation, Renaming, ...)
|
||||||
|
|
||||||
|
```
|
||||||
|
struct InternalQuery;
|
||||||
|
|
||||||
|
// Doc comments can be used to specify graphql documentation.
|
||||||
|
/// GRAPHQL DOCUMENTATION.
|
||||||
|
/// More info for GraphQL users....
|
||||||
|
#[juniper::object(
|
||||||
|
// You can rename the type for GraphQL by specifying the name here.
|
||||||
|
name = "Query",
|
||||||
|
// You can also specify a description here.
|
||||||
|
// If present, doc comments will be ignored.
|
||||||
|
description = "...",
|
||||||
|
)]
|
||||||
|
impl InternalQuery {
|
||||||
|
// Documentation doc comments also work on fields.
|
||||||
|
/// GraphQL description...
|
||||||
|
fn field_with_description() -> bool { true }
|
||||||
|
|
||||||
|
// Fields can also be customized with the #[graphql] attribute.
|
||||||
|
#[graphql(
|
||||||
|
// overwrite the public name
|
||||||
|
name = "actualFieldName",
|
||||||
|
// Can be used instead of doc comments.
|
||||||
|
description = "field description",
|
||||||
|
)]
|
||||||
|
fn internal_name() -> bool { true }
|
||||||
|
|
||||||
|
// Fields can be deprecated too.
|
||||||
|
#[graphql(
|
||||||
|
deprecated = "deprecatin info...",
|
||||||
|
// Note: just "deprecated," without a description works too.
|
||||||
|
)]
|
||||||
|
fn deprecated_field_simple() -> bool { true }
|
||||||
|
|
||||||
|
|
||||||
|
// Customizing field arguments is a little awkward right now.
|
||||||
|
// This will improve once [RFC 2564](https://github.com/rust-lang/rust/issues/60406)
|
||||||
|
// is implemented, which will allow attributes on function parameters.
|
||||||
|
|
||||||
|
#[graphql(
|
||||||
|
arguments(
|
||||||
|
arg1(
|
||||||
|
// You can specify default values.
|
||||||
|
// A default can be any valid expression that yields the right type.
|
||||||
|
default = true,
|
||||||
|
description = "Argument description....",
|
||||||
|
),
|
||||||
|
arg2(
|
||||||
|
default = false,
|
||||||
|
description = "arg2 description...",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
fn args(arg1: bool, arg2: bool) -> bool {
|
||||||
|
arg1 && arg2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifetimes, Generics and custom Scalars
|
||||||
|
|
||||||
|
Lifetimes work just like you'd expect.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
struct WithLifetime<'a> {
|
||||||
|
value: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[juniper::object]
|
||||||
|
impl<'a> WithLifetime<'a> {
|
||||||
|
fn value(&self) -> &str {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Juniper has support for custom scalars.
|
||||||
|
Mostly you will only need the default scalar type juniper::DefaultScalarValue.
|
||||||
|
|
||||||
|
You can easily specify a custom scalar though.
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# type MyCustomScalar = juniper::DefaultScalarValue;
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[juniper::object(
|
||||||
|
Scalar = MyCustomScalar,
|
||||||
|
)]
|
||||||
|
impl Query {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn object(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let gen = impl_object::build_object(args, input, false);
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A proc macro for defining a GraphQL object.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn object_internal(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let gen = impl_object::build_object(args, input, true);
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,44 @@
|
||||||
|
use quote::quote;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
|
use std::collections::HashMap;
|
||||||
|
use syn::{
|
||||||
|
parse, parse_quote, punctuated::Punctuated, Attribute, Lit, Meta, MetaList, MetaNameValue,
|
||||||
|
NestedMeta, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Compares a path to a one-segment string value,
|
||||||
|
/// return true if equal.
|
||||||
|
pub fn path_eq_single(path: &syn::Path, value: &str) -> bool {
|
||||||
|
path.segments.len() == 1 && path.segments[0].ident == value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a type is a reference to another type.
|
||||||
|
pub fn type_is_ref_of(ty: &syn::Type, target: &syn::Type) -> bool {
|
||||||
|
match ty {
|
||||||
|
syn::Type::Reference(_ref) => &*_ref.elem == target,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a Type is a simple identifier.
|
||||||
|
pub fn type_is_identifier(ty: &syn::Type, name: &str) -> bool {
|
||||||
|
match ty {
|
||||||
|
syn::Type::Path(ref type_path) => path_eq_single(&type_path.path, name),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a Type is a reference to a given identifier.
|
||||||
|
pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool {
|
||||||
|
match ty {
|
||||||
|
syn::Type::Reference(_ref) => type_is_identifier(&*_ref.elem, name),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum AttributeValidation {
|
pub enum AttributeValidation {
|
||||||
Any,
|
Any,
|
||||||
Bare,
|
// Bare,
|
||||||
String,
|
String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,10 +47,17 @@ pub enum AttributeValue {
|
||||||
String(String),
|
String(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DeprecationAttr {
|
pub struct DeprecationAttr {
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_graphql_attr(attrs: &Vec<Attribute>) -> Option<&Attribute> {
|
||||||
|
attrs
|
||||||
|
.iter()
|
||||||
|
.find(|attr| path_eq_single(&attr.path, "graphql"))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_deprecated(attrs: &Vec<Attribute>) -> Option<DeprecationAttr> {
|
pub fn get_deprecated(attrs: &Vec<Attribute>) -> Option<DeprecationAttr> {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
match attr.interpret_meta() {
|
match attr.interpret_meta() {
|
||||||
|
@ -34,14 +76,23 @@ pub fn get_deprecated(attrs: &Vec<Attribute>) -> Option<DeprecationAttr> {
|
||||||
fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
||||||
for meta in &list.nested {
|
for meta in &list.nested {
|
||||||
match meta {
|
match meta {
|
||||||
&NestedMeta::Meta(Meta::NameValue(ref nv)) if nv.ident == "note" => match &nv.lit {
|
&NestedMeta::Meta(Meta::NameValue(ref nv)) => {
|
||||||
&Lit::Str(ref strlit) => {
|
if nv.ident == "note" {
|
||||||
return DeprecationAttr {
|
match &nv.lit {
|
||||||
reason: Some(strlit.value().to_string()),
|
&Lit::Str(ref strlit) => {
|
||||||
};
|
return DeprecationAttr {
|
||||||
|
reason: Some(strlit.value()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => panic!("deprecated attribute note value only has string literal"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Unrecognized setting on #[deprecated(..)] attribute: {}",
|
||||||
|
nv.ident
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => panic!("deprecated attribute note value only has string literal"),
|
}
|
||||||
},
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,35 +111,57 @@ pub fn get_doc_comment(attrs: &Vec<Attribute>) -> Option<String> {
|
||||||
|
|
||||||
// Concatenates doc strings into one string.
|
// Concatenates doc strings into one string.
|
||||||
fn join_doc_strings(docs: &Vec<String>) -> String {
|
fn join_doc_strings(docs: &Vec<String>) -> String {
|
||||||
let s: String = docs
|
// Note: this is guaranteed since this function is only called
|
||||||
.iter()
|
// from get_doc_strings().
|
||||||
// Trim any extra spaces.
|
debug_assert!(docs.len() > 0);
|
||||||
.map(|x| x.trim().to_string())
|
|
||||||
// Convert empty comments to newlines.
|
let last_index = docs.len() - 1;
|
||||||
.map(|x| if x == "" { "\n".to_string() } else { x.clone() })
|
docs.iter()
|
||||||
.collect::<Vec<String>>()
|
.map(|s| s.as_str().trim_end())
|
||||||
.join(" ");
|
// Trim leading space.
|
||||||
// Clean up spacing on empty lines.
|
.map(|s| {
|
||||||
s.replace(" \n ", "\n")
|
if s.chars().next() == Some(' ') {
|
||||||
|
&s[1..]
|
||||||
|
} else {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Add newline, exept when string ends in a continuation backslash or is the last line.
|
||||||
|
.enumerate()
|
||||||
|
.fold(String::new(), |mut buffer, (index, s)| {
|
||||||
|
if index == last_index {
|
||||||
|
buffer.push_str(s);
|
||||||
|
} else if s.ends_with('\\') {
|
||||||
|
buffer.push_str(s.trim_end_matches('\\'));
|
||||||
|
buffer.push(' ');
|
||||||
|
} else {
|
||||||
|
buffer.push_str(s);
|
||||||
|
buffer.push('\n');
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets doc strings from doc comment attributes.
|
// Gets doc strings from doc comment attributes.
|
||||||
fn get_doc_strings(items: &Vec<MetaNameValue>) -> Option<Vec<String>> {
|
fn get_doc_strings(items: &Vec<MetaNameValue>) -> Option<Vec<String>> {
|
||||||
let mut docs = Vec::new();
|
let comments = items
|
||||||
for item in items {
|
.iter()
|
||||||
if item.ident == "doc" {
|
.filter_map(|item| {
|
||||||
match item.lit {
|
if item.ident == "doc" {
|
||||||
Lit::Str(ref strlit) => {
|
match item.lit {
|
||||||
docs.push(strlit.value().to_string());
|
Lit::Str(ref strlit) => Some(strlit.value().to_string()),
|
||||||
|
_ => panic!("doc attributes only have string literal"),
|
||||||
}
|
}
|
||||||
_ => panic!("doc attributes only have string literal"),
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if comments.len() > 0 {
|
||||||
|
Some(comments)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
if !docs.is_empty() {
|
|
||||||
return Some(docs);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets doc comment attributes.
|
// Gets doc comment attributes.
|
||||||
|
@ -130,12 +203,12 @@ pub fn keyed_item_value(
|
||||||
match &nameval.lit {
|
match &nameval.lit {
|
||||||
// We have a string attribute value.
|
// We have a string attribute value.
|
||||||
&Lit::Str(ref strlit) => match validation {
|
&Lit::Str(ref strlit) => match validation {
|
||||||
AttributeValidation::Bare => {
|
// AttributeValidation::Bare => {
|
||||||
panic!(format!(
|
// panic!(format!(
|
||||||
"Invalid format for attribute \"{:?}\": expected a bare attribute without a value",
|
// "Invalid format for attribute \"{:?}\": expected a bare attribute without a value",
|
||||||
item
|
// item
|
||||||
));
|
// ));
|
||||||
}
|
// }
|
||||||
_ => Some(AttributeValue::String(strlit.value())),
|
_ => Some(AttributeValue::String(strlit.value())),
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -212,6 +285,567 @@ pub fn is_valid_name(field_name: &str) -> bool {
|
||||||
GRAPHQL_NAME_SPEC.is_match(field_name)
|
GRAPHQL_NAME_SPEC.is_match(field_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ObjectAttributes {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub context: Option<syn::Type>,
|
||||||
|
pub scalar: Option<syn::Type>,
|
||||||
|
pub interfaces: Vec<syn::Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl syn::parse::Parse for ObjectAttributes {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
|
||||||
|
let mut output = Self {
|
||||||
|
name: None,
|
||||||
|
description: None,
|
||||||
|
context: None,
|
||||||
|
scalar: None,
|
||||||
|
interfaces: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip potential parantheses which are present for regular attributes but not for proc macro
|
||||||
|
// attributes.
|
||||||
|
let inner = (|| {
|
||||||
|
let mut content;
|
||||||
|
syn::parenthesized!(content in input);
|
||||||
|
Ok(content)
|
||||||
|
})();
|
||||||
|
let input = match inner.as_ref() {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(_) => input,
|
||||||
|
};
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
let ident: syn::Ident = input.parse()?;
|
||||||
|
match ident.to_string().as_str() {
|
||||||
|
"name" => {
|
||||||
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
let val = input.parse::<syn::LitStr>()?;
|
||||||
|
output.name = Some(val.value());
|
||||||
|
}
|
||||||
|
"description" => {
|
||||||
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
let val = input.parse::<syn::LitStr>()?;
|
||||||
|
output.description = Some(val.value());
|
||||||
|
}
|
||||||
|
"context" | "Context" => {
|
||||||
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
// TODO: remove legacy support for string based Context.
|
||||||
|
let ctx = if let Ok(val) = input.parse::<syn::LitStr>() {
|
||||||
|
eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated");
|
||||||
|
eprintln!("Use a normal type instead - example: 'Context = MyContextType'");
|
||||||
|
syn::parse_str::<syn::Type>(&val.value())?
|
||||||
|
} else {
|
||||||
|
input.parse::<syn::Type>()?
|
||||||
|
};
|
||||||
|
output.context = Some(ctx);
|
||||||
|
}
|
||||||
|
"scalar" | "Scalar" => {
|
||||||
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
let val = input.parse::<syn::Type>()?;
|
||||||
|
output.scalar = Some(val);
|
||||||
|
}
|
||||||
|
"interfaces" => {
|
||||||
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
let mut content;
|
||||||
|
syn::bracketed!(content in input);
|
||||||
|
output.interfaces =
|
||||||
|
syn::punctuated::Punctuated::<syn::Type, syn::Token![,]>::parse_terminated(
|
||||||
|
&content,
|
||||||
|
)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(input.error(format!("Unknown attribute: {}", other)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if input.lookahead1().peek(syn::Token![,]) {
|
||||||
|
input.parse::<syn::Token![,]>()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectAttributes {
|
||||||
|
pub fn from_attrs(attrs: &Vec<syn::Attribute>) -> syn::parse::Result<Self> {
|
||||||
|
let attr_opt = find_graphql_attr(attrs);
|
||||||
|
if let Some(attr) = attr_opt {
|
||||||
|
// Need to unwrap outer (), which are not present for proc macro attributes,
|
||||||
|
// but are present for regular ones.
|
||||||
|
|
||||||
|
let mut a = syn::parse::<Self>(attr.tts.clone().into())?;
|
||||||
|
if a.description.is_none() {
|
||||||
|
a.description = get_doc_comment(attrs);
|
||||||
|
}
|
||||||
|
Ok(a)
|
||||||
|
} else {
|
||||||
|
let mut a = Self::default();
|
||||||
|
a.description = get_doc_comment(attrs);
|
||||||
|
Ok(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FieldAttributeArgument {
|
||||||
|
pub name: syn::Ident,
|
||||||
|
pub default: Option<syn::Expr>,
|
||||||
|
pub description: Option<syn::LitStr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parse::Parse for FieldAttributeArgument {
|
||||||
|
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
|
||||||
|
let name = input.parse()?;
|
||||||
|
|
||||||
|
let mut arg = Self {
|
||||||
|
name,
|
||||||
|
default: None,
|
||||||
|
description: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut content;
|
||||||
|
syn::parenthesized!(content in input);
|
||||||
|
|
||||||
|
while !content.is_empty() {
|
||||||
|
let name = content.parse::<syn::Ident>()?;
|
||||||
|
content.parse::<Token![=]>()?;
|
||||||
|
|
||||||
|
match name.to_string().as_str() {
|
||||||
|
"description" => {
|
||||||
|
arg.description = Some(content.parse()?);
|
||||||
|
}
|
||||||
|
"default" => {
|
||||||
|
arg.default = Some(content.parse()?);
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(content.error(format!("Invalid attribute argument key {}", other)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard trailing comma.
|
||||||
|
content.parse::<Token![,]>().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
pub enum FieldAttributeParseMode {
|
||||||
|
Object,
|
||||||
|
Impl,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FieldAttribute {
|
||||||
|
Name(syn::LitStr),
|
||||||
|
Description(syn::LitStr),
|
||||||
|
Deprecation(DeprecationAttr),
|
||||||
|
Skip(syn::Ident),
|
||||||
|
Arguments(HashMap<String, FieldAttributeArgument>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parse::Parse for FieldAttribute {
|
||||||
|
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
|
||||||
|
let ident = input.parse::<syn::Ident>()?;
|
||||||
|
|
||||||
|
match ident.to_string().as_str() {
|
||||||
|
"name" => {
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
let lit = input.parse::<syn::LitStr>()?;
|
||||||
|
let raw = lit.value();
|
||||||
|
if !is_valid_name(&raw) {
|
||||||
|
Err(input.error(format!(
|
||||||
|
"Invalid #[graphql(name = ...)] attribute: \n\
|
||||||
|
'{}' is not a valid field name\nNames must \
|
||||||
|
match /^[_a-zA-Z][_a-zA-Z0-9]*$/",
|
||||||
|
raw,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(FieldAttribute::Name(lit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"description" => {
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
Ok(FieldAttribute::Description(input.parse()?))
|
||||||
|
}
|
||||||
|
"deprecated" | "deprecation" => {
|
||||||
|
let reason = if input.peek(Token![=]) {
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
Some(input.parse::<syn::LitStr>()?.value())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(FieldAttribute::Deprecation(DeprecationAttr {
|
||||||
|
reason: reason,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
"skip" => Ok(FieldAttribute::Skip(ident)),
|
||||||
|
"arguments" => {
|
||||||
|
let mut arg_content;
|
||||||
|
syn::parenthesized!(arg_content in input);
|
||||||
|
let args = Punctuated::<FieldAttributeArgument, Token![,]>::parse_terminated(
|
||||||
|
&arg_content,
|
||||||
|
)?;
|
||||||
|
let map = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| (arg.name.to_string(), arg))
|
||||||
|
.collect();
|
||||||
|
Ok(FieldAttribute::Arguments(map))
|
||||||
|
}
|
||||||
|
other => Err(input.error(format!("Unknown attribute: {}", other))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FieldAttributes {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub deprecation: Option<DeprecationAttr>,
|
||||||
|
// Only relevant for GraphQLObject derive.
|
||||||
|
pub skip: bool,
|
||||||
|
/// Only relevant for object macro.
|
||||||
|
pub arguments: HashMap<String, FieldAttributeArgument>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parse::Parse for FieldAttributes {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
|
||||||
|
// Remove wrapping parantheses.
|
||||||
|
let mut content;
|
||||||
|
syn::parenthesized!(content in input);
|
||||||
|
|
||||||
|
let items = Punctuated::<FieldAttribute, Token![,]>::parse_terminated(&content)?;
|
||||||
|
|
||||||
|
let mut output = Self {
|
||||||
|
name: None,
|
||||||
|
description: None,
|
||||||
|
deprecation: None,
|
||||||
|
skip: false,
|
||||||
|
arguments: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in items {
|
||||||
|
match item {
|
||||||
|
FieldAttribute::Name(name) => {
|
||||||
|
output.name = Some(name.value());
|
||||||
|
}
|
||||||
|
FieldAttribute::Description(name) => {
|
||||||
|
output.description = Some(name.value());
|
||||||
|
}
|
||||||
|
FieldAttribute::Deprecation(attr) => {
|
||||||
|
output.deprecation = Some(attr);
|
||||||
|
}
|
||||||
|
FieldAttribute::Skip(_) => {
|
||||||
|
output.skip = true;
|
||||||
|
}
|
||||||
|
FieldAttribute::Arguments(args) => {
|
||||||
|
output.arguments = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !content.is_empty() {
|
||||||
|
Err(content.error("Unexpected input"))
|
||||||
|
} else {
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldAttributes {
|
||||||
|
pub fn from_attrs(
|
||||||
|
attrs: Vec<syn::Attribute>,
|
||||||
|
_mode: FieldAttributeParseMode,
|
||||||
|
) -> syn::parse::Result<Self> {
|
||||||
|
let doc_comment = get_doc_comment(&attrs);
|
||||||
|
let deprecation = get_deprecated(&attrs);
|
||||||
|
|
||||||
|
let attr_opt = attrs
|
||||||
|
.into_iter()
|
||||||
|
.find(|attr| path_eq_single(&attr.path, "graphql"));
|
||||||
|
|
||||||
|
let mut output = match attr_opt {
|
||||||
|
Some(attr) => syn::parse(attr.tts.into())?,
|
||||||
|
None => Self::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for regular doc comment.
|
||||||
|
if output.description.is_none() {
|
||||||
|
output.description = doc_comment;
|
||||||
|
}
|
||||||
|
if output.deprecation.is_none() {
|
||||||
|
output.deprecation = deprecation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn argument(&self, name: &str) -> Option<&FieldAttributeArgument> {
|
||||||
|
self.arguments.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GraphQLTypeDefinitionFieldArg {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub default: Option<syn::Expr>,
|
||||||
|
pub _type: syn::Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GraphQLTypeDefinitionField {
|
||||||
|
pub name: String,
|
||||||
|
pub _type: syn::Type,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub deprecation: Option<DeprecationAttr>,
|
||||||
|
pub args: Vec<GraphQLTypeDefinitionFieldArg>,
|
||||||
|
pub resolver_code: proc_macro2::TokenStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Definition of a graphql type based on information extracted
|
||||||
|
/// by various macros.
|
||||||
|
/// The definition can be rendered to Rust code.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GraphQLTypeDefiniton {
|
||||||
|
pub name: String,
|
||||||
|
pub _type: syn::Type,
|
||||||
|
pub context: Option<syn::Type>,
|
||||||
|
pub scalar: Option<syn::Type>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub fields: Vec<GraphQLTypeDefinitionField>,
|
||||||
|
pub generics: syn::Generics,
|
||||||
|
pub interfaces: Option<Vec<syn::Type>>,
|
||||||
|
// Due to syn parsing differences,
|
||||||
|
// when parsing an impl the type generics are included in the type
|
||||||
|
// directly, but in syn::DeriveInput, the type generics are
|
||||||
|
// in the generics field.
|
||||||
|
// This flag signifies if the type generics need to be
|
||||||
|
// included manually.
|
||||||
|
pub include_type_generics: bool,
|
||||||
|
// This flag indicates if the generated code should always be
|
||||||
|
// generic over the ScalarValue.
|
||||||
|
// If false, the scalar is only generic if a generic parameter
|
||||||
|
// is specified manually.
|
||||||
|
pub generic_scalar: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphQLTypeDefiniton {
|
||||||
|
pub fn into_tokens(self, juniper_crate_name: &str) -> proc_macro2::TokenStream {
|
||||||
|
let juniper_crate_name = syn::parse_str::<syn::Path>(juniper_crate_name).unwrap();
|
||||||
|
|
||||||
|
let name = &self.name;
|
||||||
|
let ty = &self._type;
|
||||||
|
let context = self
|
||||||
|
.context
|
||||||
|
.as_ref()
|
||||||
|
.map(|ctx| quote!( #ctx ))
|
||||||
|
.unwrap_or(quote!(()));
|
||||||
|
|
||||||
|
let field_definitions = self.fields.iter().map(|field| {
|
||||||
|
let args = field.args.iter().map(|arg| {
|
||||||
|
let arg_type = &arg._type;
|
||||||
|
let arg_name = &arg.name;
|
||||||
|
|
||||||
|
let description = match arg.description.as_ref() {
|
||||||
|
Some(value) => quote!( .description( #value ) ),
|
||||||
|
None => quote!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let code = match arg.default.as_ref() {
|
||||||
|
Some(value) => quote!(
|
||||||
|
.argument(
|
||||||
|
registry.arg_with_default::<#arg_type>(#arg_name, &#value, info)
|
||||||
|
#description
|
||||||
|
)
|
||||||
|
),
|
||||||
|
None => quote!(
|
||||||
|
.argument(
|
||||||
|
registry.arg::<#arg_type>(#arg_name, info)
|
||||||
|
#description
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
code
|
||||||
|
});
|
||||||
|
|
||||||
|
let description = match field.description.as_ref() {
|
||||||
|
Some(description) => quote!( .description(#description) ),
|
||||||
|
None => quote!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let deprecation = match field.deprecation.as_ref() {
|
||||||
|
Some(deprecation) => {
|
||||||
|
if let Some(reason) = deprecation.reason.as_ref() {
|
||||||
|
quote!( .deprecated(Some(#reason)) )
|
||||||
|
} else {
|
||||||
|
quote!( .deprecated(None) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => quote!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let field_name = &field.name;
|
||||||
|
|
||||||
|
let _type = &field._type;
|
||||||
|
quote! {
|
||||||
|
registry
|
||||||
|
.field_convert::<#_type, _, Self::Context>(#field_name, info)
|
||||||
|
#(#args)*
|
||||||
|
#description
|
||||||
|
#deprecation
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let resolve_matches = self.fields.iter().map(|field| {
|
||||||
|
let name = &field.name;
|
||||||
|
let code = &field.resolver_code;
|
||||||
|
|
||||||
|
quote!(
|
||||||
|
#name => {
|
||||||
|
let res = { #code };
|
||||||
|
#juniper_crate_name::IntoResolvable::into(
|
||||||
|
res,
|
||||||
|
executor.context()
|
||||||
|
)
|
||||||
|
.and_then(|res| {
|
||||||
|
match res {
|
||||||
|
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
|
||||||
|
None => Ok(#juniper_crate_name::Value::null()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let description = self
|
||||||
|
.description
|
||||||
|
.as_ref()
|
||||||
|
.map(|description| quote!( .description(#description) ));
|
||||||
|
|
||||||
|
let interfaces = self.interfaces.as_ref().map(|items| {
|
||||||
|
quote!(
|
||||||
|
.interfaces(&[
|
||||||
|
#( registry.get_type::< #items >(&()) ,)*
|
||||||
|
])
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let scalar = self
|
||||||
|
.scalar
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote!( #s ))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
if self.generic_scalar {
|
||||||
|
// If generic_scalar is true, we always insert a generic scalar.
|
||||||
|
// See more comments below.
|
||||||
|
quote!(__S)
|
||||||
|
} else {
|
||||||
|
quote!(#juniper_crate_name::DefaultScalarValue)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Preserve the original type_generics before modification,
|
||||||
|
// since alteration makes them invalid if self.generic_scalar
|
||||||
|
// is specified.
|
||||||
|
let (_, type_generics, _) = self.generics.split_for_impl();
|
||||||
|
|
||||||
|
let mut generics = self.generics.clone();
|
||||||
|
|
||||||
|
if self.scalar.is_some() {
|
||||||
|
// A custom scalar type was specified.
|
||||||
|
// Therefore, we always insert a where clause that marks the scalar as
|
||||||
|
// compatible with ScalarValueRef.
|
||||||
|
// This is done to prevent the user from having to specify this
|
||||||
|
// manually.
|
||||||
|
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
|
||||||
|
where_clause.predicates.push(
|
||||||
|
parse_quote!(for<'__b> &'__b #scalar: #juniper_crate_name::ScalarRefValue<'__b>),
|
||||||
|
);
|
||||||
|
} else if self.generic_scalar {
|
||||||
|
// No custom scalar specified, but always generic specified.
|
||||||
|
// Therefore we inject the generic scalar.
|
||||||
|
|
||||||
|
generics.params.push(parse_quote!(__S));
|
||||||
|
|
||||||
|
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where));
|
||||||
|
// Insert ScalarValue constraint.
|
||||||
|
where_clause
|
||||||
|
.predicates
|
||||||
|
.push(parse_quote!(__S: #juniper_crate_name::ScalarValue));
|
||||||
|
// Insert a where clause that marks the scalar as
|
||||||
|
// compatible with ScalarValueRef.
|
||||||
|
// Same as in branch above.
|
||||||
|
where_clause
|
||||||
|
.predicates
|
||||||
|
.push(parse_quote!(for<'__b> &'__b __S: #juniper_crate_name::ScalarRefValue<'__b>));
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_generics_tokens = if self.include_type_generics {
|
||||||
|
Some(type_generics)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
|
let output = quote!(
|
||||||
|
impl#impl_generics #juniper_crate_name::GraphQLType<#scalar> for #ty #type_generics_tokens
|
||||||
|
#where_clause
|
||||||
|
{
|
||||||
|
type Context = #context;
|
||||||
|
type TypeInfo = ();
|
||||||
|
|
||||||
|
fn name(_: &Self::TypeInfo) -> Option<&str> {
|
||||||
|
Some(#name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meta<'r>(
|
||||||
|
info: &Self::TypeInfo,
|
||||||
|
registry: &mut #juniper_crate_name::Registry<'r, #scalar>
|
||||||
|
) -> #juniper_crate_name::meta::MetaType<'r, #scalar>
|
||||||
|
where #scalar : 'r,
|
||||||
|
for<'z> &'z #scalar: #juniper_crate_name::ScalarRefValue<'z>,
|
||||||
|
{
|
||||||
|
let fields = vec![
|
||||||
|
#( #field_definitions ),*
|
||||||
|
];
|
||||||
|
let meta = registry.build_object_type::<#ty>( info, &fields )
|
||||||
|
#description
|
||||||
|
#interfaces;
|
||||||
|
meta.into_meta()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
fn resolve_field(
|
||||||
|
&self,
|
||||||
|
_info: &(),
|
||||||
|
field: &str,
|
||||||
|
args: &#juniper_crate_name::Arguments<#scalar>,
|
||||||
|
executor: &#juniper_crate_name::Executor<Self::Context, #scalar>,
|
||||||
|
) -> #juniper_crate_name::ExecutionResult<#scalar> {
|
||||||
|
match field {
|
||||||
|
#( #resolve_matches )*
|
||||||
|
_ => {
|
||||||
|
panic!("Field {} not found on type {}", field, "Mutation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn concrete_type_name(&self, _: &Self::Context, _: &Self::TypeInfo) -> String {
|
||||||
|
#name.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -296,25 +930,31 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple() {
|
fn test_multiple() {
|
||||||
let result = join_doc_strings(&strs_to_strings(vec!["foo", "bar"]));
|
let result = join_doc_strings(&strs_to_strings(vec!["foo", "bar"]));
|
||||||
assert_eq!(&result, "foo bar");
|
assert_eq!(&result, "foo\nbar");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_trims_spaces() {
|
fn test_trims_spaces() {
|
||||||
let result = join_doc_strings(&strs_to_strings(vec![" foo ", "bar ", " baz"]));
|
let result = join_doc_strings(&strs_to_strings(vec![" foo ", "bar ", " baz"]));
|
||||||
assert_eq!(&result, "foo bar baz");
|
assert_eq!(&result, "foo\nbar\nbaz");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty() {
|
fn test_empty() {
|
||||||
let result = join_doc_strings(&strs_to_strings(vec!["foo", "", "bar"]));
|
let result = join_doc_strings(&strs_to_strings(vec!["foo", "", "bar"]));
|
||||||
assert_eq!(&result, "foo\nbar");
|
assert_eq!(&result, "foo\n\nbar");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_newline_spaces() {
|
fn test_newline_spaces() {
|
||||||
let result = join_doc_strings(&strs_to_strings(vec!["foo ", "", " bar"]));
|
let result = join_doc_strings(&strs_to_strings(vec!["foo ", "", " bar"]));
|
||||||
assert_eq!(&result, "foo\nbar");
|
assert_eq!(&result, "foo\n\nbar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_continuation_backslash() {
|
||||||
|
let result = join_doc_strings(&strs_to_strings(vec!["foo\\", "x\\", "y", "bar"]));
|
||||||
|
assert_eq!(&result, "foo x y\nbar");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,27 +37,29 @@ use juniper::{Context, EmptyMutation};
|
||||||
# struct QueryRoot;
|
# struct QueryRoot;
|
||||||
# struct Database { users: HashMap<String, User> }
|
# struct Database { users: HashMap<String, User> }
|
||||||
#
|
#
|
||||||
# juniper::graphql_object!(User: Database |&self| {
|
# #[juniper::object( Context = Database )]
|
||||||
# field id() -> FieldResult<&String> {
|
# impl User {
|
||||||
|
# fn id(&self) -> FieldResult<&String> {
|
||||||
# Ok(&self.id)
|
# Ok(&self.id)
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# field name() -> FieldResult<&String> {
|
# fn name(&self) -> FieldResult<&String> {
|
||||||
# Ok(&self.name)
|
# Ok(&self.name)
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# field friends(&executor) -> FieldResult<Vec<&User>> {
|
# fn friends(context: &Database) -> FieldResult<Vec<&User>> {
|
||||||
# Ok(self.friend_ids.iter()
|
# Ok(self.friend_ids.iter()
|
||||||
# .filter_map(|id| executor.context().users.get(id))
|
# .filter_map(|id| executor.context().users.get(id))
|
||||||
# .collect())
|
# .collect())
|
||||||
# }
|
# }
|
||||||
# });
|
# }
|
||||||
#
|
#
|
||||||
# juniper::graphql_object!(QueryRoot: Database |&self| {
|
# #[juniper::object( Context = Database )]
|
||||||
# field user(&executor, id: String) -> FieldResult<Option<&User>> {
|
# impl QueryRoot {
|
||||||
|
# fn user(context: &Database, id: String) -> FieldResult<Option<&User>> {
|
||||||
# Ok(executor.context().users.get(&id))
|
# Ok(executor.context().users.get(&id))
|
||||||
# }
|
# }
|
||||||
# });
|
# }
|
||||||
|
|
||||||
// This function is executed for every request. Here, we would realistically
|
// This function is executed for every request. Here, we would realistically
|
||||||
// provide a database connection or similar. For this example, we'll be
|
// provide a database connection or similar. For this example, we'll be
|
||||||
|
|
|
@ -134,13 +134,18 @@ where
|
||||||
///
|
///
|
||||||
/// struct QueryRoot;
|
/// struct QueryRoot;
|
||||||
///
|
///
|
||||||
/// juniper::graphql_object! (QueryRoot: ExampleContext |&self| {
|
/// #[juniper::object(
|
||||||
/// field say_hello(&executor) -> String {
|
/// Context = ExampleContext
|
||||||
/// let context = executor.context();
|
/// )]
|
||||||
///
|
/// impl QueryRoot {
|
||||||
/// format!("good morning {}, the app state is {:?}", context.1, context.0)
|
/// fn say_hello(context: &ExampleContext) -> String {
|
||||||
|
/// format!(
|
||||||
|
/// "good morning {}, the app state is {:?}",
|
||||||
|
/// context.1,
|
||||||
|
/// context.0
|
||||||
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// });
|
/// }
|
||||||
///
|
///
|
||||||
/// let schema = RootNode::new(QueryRoot, EmptyMutation::new());
|
/// let schema = RootNode::new(QueryRoot, EmptyMutation::new());
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in a new issue