Rework codegen for GraphQL objects and subscriptions (#971, #421)

- preserve and reuse defined impl blocks in #[graphql_object] and #[graphql_subscription] macros expansion
- allow renaming `ScalarValue` type parameter in expanded code via `scalar = S: ScalarValue` syntax

Additionally:
- rename `rename` attribute's argument to `rename_all`
- support `rename_all` in #[graphql_interface] macro
This commit is contained in:
Kai Ren 2021-08-11 17:41:49 +03:00 committed by GitHub
parent 39d1e43420
commit a3fda7363d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
165 changed files with 11581 additions and 8998 deletions

View file

@ -99,10 +99,11 @@ impl Into<UserPatch> for UserPatchInput {
struct Context { struct Context {
session: Session, session: Session,
} }
impl juniper::Context for Context {}
struct Mutation; struct Mutation;
#[juniper::graphql_object(Context=Context)] #[juniper::graphql_object(context = Context)]
impl Mutation { impl Mutation {
fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> { fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> {
ctx.session.patch_user(patch.into())?; ctx.session.patch_user(patch.into())?;

View file

@ -25,10 +25,6 @@ This example shows a subscription operation that returns two events, the strings
sequentially: sequentially:
```rust ```rust
# extern crate futures;
# extern crate juniper;
# extern crate juniper_subscriptions;
# extern crate tokio;
# use juniper::{graphql_object, graphql_subscription, FieldError}; # use juniper::{graphql_object, graphql_subscription, FieldError};
# use futures::Stream; # use futures::Stream;
# use std::pin::Pin; # use std::pin::Pin;
@ -40,7 +36,7 @@ sequentially:
# pub struct Query; # pub struct Query;
# #[graphql_object(context = Database)] # #[graphql_object(context = Database)]
# impl Query { # impl Query {
# fn hello_world() -> &str { # fn hello_world() -> &'static str {
# "Hello World!" # "Hello World!"
# } # }
# } # }
@ -110,7 +106,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
# #
# #[graphql_object(context = Database)] # #[graphql_object(context = Database)]
# impl Query { # impl Query {
# fn hello_world() -> &str { # fn hello_world() -> &'static str {
# "Hello World!" # "Hello World!"
# } # }
# } # }

View file

@ -6,11 +6,9 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch
## Installation ## Installation
!FILENAME Cargo.toml
```toml ```toml
[dependencies] [dependencies]
juniper = { git = "https://github.com/graphql-rust/juniper" } juniper = "0.15"
``` ```
## Schema example ## Schema example
@ -89,7 +87,7 @@ struct Query;
context = Context, context = Context,
)] )]
impl Query { impl Query {
fn apiVersion() -> &str { fn apiVersion() -> &'static str {
"1.0" "1.0"
} }
@ -114,14 +112,13 @@ struct Mutation;
#[graphql_object( #[graphql_object(
context = Context, context = Context,
// If we need to use `ScalarValue` parametrization explicitly somewhere // If we need to use `ScalarValue` parametrization explicitly somewhere
// in the object definition (like here in `FieldResult`), we should // in the object definition (like here in `FieldResult`), we could
// declare an explicit type parameter for that, and specify it. // declare an explicit type parameter for that, and specify it.
scalar = S, scalar = S: ScalarValue + Display,
)] )]
impl<S: ScalarValue + Display> Mutation { impl Mutation {
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> { fn createHuman<S: ScalarValue + Display>(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
let db = context.pool.get_connection().map_err(|e| e.map_scalar_value())?; let db = context.pool.get_connection().map_err(|e| e.map_scalar_value())?;
let human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?; let human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?;
Ok(human) Ok(human)

View file

@ -232,6 +232,21 @@ trait Character {
# fn main() {} # fn main() {}
``` ```
Renaming policies for all [GraphQL interface][1] fields and arguments are supported as well:
```rust
# #![allow(deprecated)]
# extern crate juniper;
use juniper::graphql_interface;
#[graphql_interface(rename_all = "none")] // disables any renaming
trait Character {
// Now exposed as `my_id` and `my_num` in the schema
fn my_id(&self, my_num: i32) -> &str;
}
#
# fn main() {}
```
### Custom context ### Custom context

View file

@ -3,9 +3,10 @@
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]` procedural macro. This macro lets you define GraphQL object the `#[graphql_object]` procedural macro. This macro lets you define GraphQL object
fields in a Rust `impl` block for a type. Note that only GraphQL fields fields in a Rust `impl` block for a type. Note, that GraphQL fields are defined in
can be specified in this `impl` block. If you want to define normal methods on the struct, this `impl` block by default. If you want to define normal methods on the struct,
you have to do so in a separate, normal `impl` block. Continuing with the you have to do so either in a separate "normal" `impl` block, or mark them with
`#[graphql(ignore)]` attribute to be omitted by the macro. 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:
@ -28,12 +29,15 @@ impl Person {
fn age(&self) -> i32 { fn age(&self) -> i32 {
self.age self.age
} }
#[graphql(ignore)]
pub fn hidden_from_graphql(&self) {
// [...]
}
} }
// Note that this syntax generates an implementation of the GraphQLType trait,
// the base impl of your struct can still be written like usual:
impl Person { impl Person {
pub fn hidden_from_graphql(&self) { pub fn hidden_from_graphql2(&self) {
// [...] // [...]
} }
} }
@ -44,7 +48,6 @@ impl Person {
While this is a bit more verbose, it lets you write any kind of function in the While this is a bit more verbose, it lets you write any kind of function in the
field resolver. With this syntax, fields can also take arguments: field resolver. With this syntax, fields can also take arguments:
```rust ```rust
# extern crate juniper; # extern crate juniper;
# use juniper::{graphql_object, GraphQLObject}; # use juniper::{graphql_object, GraphQLObject};
@ -61,7 +64,7 @@ struct House {
#[graphql_object] #[graphql_object]
impl House { impl House {
// Creates the field inhabitantWithName(name), returning a nullable person // Creates the field `inhabitantWithName(name)`, returning a nullable `Person`.
fn inhabitant_with_name(&self, 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)
} }
@ -127,15 +130,29 @@ impl Person {
# fn main() { } # fn main() { }
``` ```
Or provide a different renaming policy on a `impl` block for all its fields:
```rust
# extern crate juniper;
# use juniper::graphql_object;
struct Person;
#[graphql_object(rename_all = "none")] // disables any renaming
impl Person {
// Now exposed as `renamed_field` in the schema
fn renamed_field() -> bool {
true
}
}
#
# fn main() {}
```
## Customizing arguments ## Customizing arguments
Method field arguments can also be customized. Method field arguments can also be customized.
They can have custom descriptions and default values. They can have custom descriptions and default values.
**Note**: The syntax for this is currently a little awkward.
This will become better once the [Rust RFC 2565](https://github.com/rust-lang/rust/issues/60406) is implemented.
```rust ```rust
# extern crate juniper; # extern crate juniper;
# use juniper::graphql_object; # use juniper::graphql_object;
@ -144,21 +161,22 @@ struct Person {}
#[graphql_object] #[graphql_object]
impl Person { impl Person {
fn field1(
&self,
#[graphql( #[graphql(
arguments( // Arguments can also be renamed if required.
arg1( name = "arg",
// Set a default value which will be injected if not present. // Set a default value which will be injected if not present.
// The default can be any valid Rust expression, including a function call, etc. // The default can be any valid Rust expression, including a function call, etc.
default = true, default = true,
// Set a description. // Set a description.
description = "The first argument..." description = "The first argument..."
),
arg2(
default = 0,
)
)
)] )]
fn field1(&self, arg1: bool, arg2: i32) -> String { arg1: bool,
// If default expression is not specified then `Default::default()` value is used.
#[graphql(default)]
arg2: i32,
) -> String {
format!("{} {}", arg1, arg2) format!("{} {}", arg1, arg2)
} }
} }
@ -166,13 +184,23 @@ impl Person {
# fn main() { } # fn main() { }
``` ```
Provide a different renaming policy on a `impl` block also implies for arguments:
```rust
# extern crate juniper;
# use juniper::graphql_object;
struct Person;
#[graphql_object(rename_all = "none")] // disables any renaming
impl Person {
// Now exposed as `my_arg` in the schema
fn field(my_arg: bool) -> bool {
my_arg
}
}
#
# fn main() {}
```
## More features ## More features
GraphQL fields expose more features than Rust's standard method syntax gives us: These, and more features, are described more thoroughly in [the reference documentation](https://docs.rs/juniper/latest/juniper/attr.graphql_object.html).
* Per-field description and deprecation messages
* Per-argument default values
* Per-argument descriptions
These, and more features, are described more thoroughly in [the reference
documentation](https://docs.rs/juniper/latest/juniper/macro.object.html).

View file

@ -152,7 +152,22 @@ struct Person {
name: String, name: String,
age: i32, age: i32,
#[graphql(name = "websiteURL")] #[graphql(name = "websiteURL")]
website_url: Option<String>, // Now exposed as websiteURL in the schema website_url: Option<String>, // now exposed as `websiteURL` in the schema
}
#
# fn main() {}
```
Or provide a different renaming policy on a struct for all its fields:
```rust
# extern crate juniper;
# use juniper::GraphQLObject;
#[derive(GraphQLObject)]
#[graphql(rename_all = "none")] // disables any renaming
struct Person {
name: String,
age: i32,
website_url: Option<String>, // now exposed as `website_url` in the schema
} }
# #
# fn main() {} # fn main() {}
@ -181,9 +196,9 @@ The `name`, `description`, and `deprecation` arguments can of course be
combined. Some restrictions from the GraphQL spec still applies though; you can combined. Some restrictions from the GraphQL spec still applies though; you can
only deprecate object fields and enum values. only deprecate object fields and enum values.
## Skipping fields ## Ignoring fields
By default all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(skip)]`: By default, all fields in a `GraphQLObject` are included in the generated GraphQL type. To prevent including a specific field, annotate the field with `#[graphql(ignore)]`:
```rust ```rust
# extern crate juniper; # extern crate juniper;
@ -192,9 +207,9 @@ By default all fields in a `GraphQLObject` are included in the generated GraphQL
struct Person { struct Person {
name: String, name: String,
age: i32, age: i32,
#[graphql(skip)] #[graphql(ignore)]
# #[allow(dead_code)] # #[allow(dead_code)]
password_hash: String, // This cannot be queried or modified from GraphQL password_hash: String, // cannot be queried or modified from GraphQL
} }
# #
# fn main() {} # fn main() {}

View file

@ -17,8 +17,7 @@ it will bubble up to the surrounding framework and hopefully be dealt with
there. there.
For recoverable errors, Juniper works well with the built-in `Result` type, you For recoverable errors, Juniper works well with the built-in `Result` type, you
can use the `?` operator or the `try!` macro and things will generally just work can use the `?` operator and things will generally just work as you expect them to:
as you expect them to:
```rust ```rust
# extern crate juniper; # extern crate juniper;
@ -36,7 +35,7 @@ struct Example {
#[graphql_object] #[graphql_object]
impl Example { impl Example {
fn contents() -> FieldResult<String> { fn contents(&self) -> 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)?;
@ -47,10 +46,7 @@ impl Example {
// Some invalid bytes. // Some invalid bytes.
let invalid = vec![128, 223]; let invalid = vec![128, 223];
match str::from_utf8(&invalid) { Ok(Some(str::from_utf8(&invalid)?.to_string()))
Ok(s) => Ok(Some(s.to_string())),
Err(e) => Err(e)?,
}
} }
} }
# #
@ -66,7 +62,6 @@ there - those errors are automatically converted into `FieldError`.
Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability). Juniper's error behavior conforms to the [GraphQL specification](https://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability).
When a field returns an error, the field's result is replaced by `null`, an When a field returns an error, the field's result is replaced by `null`, an
additional `errors` object is created at the top level of the response, and the additional `errors` object is created at the top level of the response, and the
execution is resumed. For example, with the previous example and the following execution is resumed. For example, with the previous example and the following
@ -86,12 +81,12 @@ returned:
!FILENAME Response for nullable field with error !FILENAME Response for nullable field with error
```js ```json
{ {
"data": { "data": {
"example": { "example": {
contents: "<Contents of the file>", contents: "<Contents of the file>",
foo: null, foo: null
} }
}, },
"errors": [ "errors": [
@ -120,7 +115,7 @@ following would be returned:
!FILENAME Response for non-null field with error and no nullable parent !FILENAME Response for non-null field with error and no nullable parent
```js ```json
{ {
"errors": [ "errors": [
"message": "Permission denied (os error 13)", "message": "Permission denied (os error 13)",
@ -162,7 +157,7 @@ struct Example {
#[graphql_object] #[graphql_object]
impl Example { impl Example {
fn whatever() -> Result<bool, CustomError> { fn whatever(&self) -> Result<bool, CustomError> {
if let Some(value) = self.whatever { if let Some(value) = self.whatever {
return Ok(value); return Ok(value);
} }

View file

@ -63,8 +63,8 @@ impl User {
// with the context type. // with the context type.
// Note: // Note:
// - the type must be a reference // - the type must be a reference
// - the name of the argument SHOULD be context // - the name of the argument SHOULD be `context`
fn friends(&self, context: &Database) -> Vec<&User> { fn friends<'db>(&self, context: &'db Database) -> Vec<&'db User> {
// 5. Use the database to lookup users // 5. Use the database to lookup users
self.friend_ids.iter() self.friend_ids.iter()

View file

@ -11,7 +11,7 @@ use actix_web::{
use juniper::{ use juniper::{
graphql_object, graphql_subscription, graphql_object, graphql_subscription,
tests::fixtures::starwars::schema::{Character as _, Database, Query}, tests::fixtures::starwars::schema::{Character as _, Database, Query},
DefaultScalarValue, EmptyMutation, FieldError, RootNode, DefaultScalarValue, EmptyMutation, FieldError, RootNode, Value,
}; };
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
use juniper_graphql_ws::ConnectionConfig; use juniper_graphql_ws::ConnectionConfig;

View file

@ -5,8 +5,6 @@ edition = "2018"
publish = false publish = false
authors = ["Jordao Rosario <jordao.rosario01@gmail.com>"] authors = ["Jordao Rosario <jordao.rosario01@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
futures = "0.3" futures = "0.3"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View file

@ -24,7 +24,7 @@ pub struct Query;
#[graphql_object(context = Database)] #[graphql_object(context = Database)]
impl Query { impl Query {
fn hello_world() -> &str { fn hello_world() -> &'static str {
"Hello World!" "Hello World!"
} }
} }

View file

@ -13,6 +13,7 @@ serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
warp = "0.3" warp = "0.3"
async-stream = "0.3" async-stream = "0.3"
juniper = { path = "../../juniper" } juniper = { path = "../../juniper" }
juniper_graphql_ws = { path = "../../juniper_graphql_ws" } juniper_graphql_ws = { path = "../../juniper_graphql_ws" }
juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] } juniper_warp = { path = "../../juniper_warp", features = ["subscriptions"] }

View file

@ -5,7 +5,7 @@ use std::{env, pin::Pin, sync::Arc, time::Duration};
use futures::{FutureExt as _, Stream}; use futures::{FutureExt as _, Stream};
use juniper::{ use juniper::{
graphql_object, graphql_subscription, DefaultScalarValue, EmptyMutation, FieldError, graphql_object, graphql_subscription, DefaultScalarValue, EmptyMutation, FieldError,
GraphQLEnum, RootNode, GraphQLEnum, RootNode, Value,
}; };
use juniper_graphql_ws::ConnectionConfig; use juniper_graphql_ws::ConnectionConfig;
use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws}; use juniper_warp::{playground_filter, subscriptions::serve_graphql_ws};

View file

@ -1,10 +1,10 @@
error[E0277]: the trait bound `ObjA: GraphQLObjectType<__S>` is not satisfied error[E0277]: the trait bound `ObjA: GraphQLObject<__S>` is not satisfied
--> $DIR/implementer_non_object_type.rs:15:1 --> $DIR/implementer_non_object_type.rs:15:1
| |
15 | #[graphql_interface(for = ObjA)] 15 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `ObjA` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA`
| |
= note: required by `juniper::marker::GraphQLObjectType::mark` = note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied

View file

@ -0,0 +1,12 @@
use juniper::graphql_object;
struct Obj;
#[graphql_object]
impl Obj {
fn id(&self, __num: i32) -> &str {
"funA"
}
}
fn main() {}

View file

@ -1,7 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system. error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/impl_no_argument_underscore.rs:5:29 --> $DIR/argument_double_underscored.rs:7:18
| |
5 | #[graphql(arguments(arg(name = "__arg")))] 7 | fn id(&self, __num: i32) -> &str {
| ^^^^ | ^^^^^
| |
= note: https://spec.graphql.org/June2018/#sec-Schema = note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,17 @@
use juniper::{graphql_object, GraphQLObject};
#[derive(GraphQLObject)]
struct ObjA {
test: String,
}
struct ObjB;
#[graphql_object]
impl ObjB {
fn id(&self, obj: ObjA) -> &str {
"funA"
}
}
fn main() {}

View file

@ -0,0 +1,16 @@
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,12 @@
use juniper::graphql_object;
struct ObjA;
#[graphql_object]
impl ObjA {
fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool {
input[0]
}
}
fn main() {}

View file

@ -0,0 +1,12 @@
error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
--> $DIR/argument_wrong_default_array.rs:5:1
|
5 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
= help: the following implementations were found:
<&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
<&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
<&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,12 @@
use juniper::graphql_object;
struct ObjA;
#[graphql_object]
impl Character for ObjA {
fn __id(&self) -> &str {
"funA"
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: #[graphql_object] attribute is applicable to non-trait `impl` blocks only
--> $DIR/attr_field_double_underscored.rs:5:1
|
5 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,17 @@
use juniper::{graphql_object, GraphQLInputObject};
#[derive(GraphQLInputObject)]
struct ObjB {
id: i32,
}
struct ObjA;
#[graphql_object]
impl ObjA {
fn id(&self) -> ObjB {
ObjB { id: 34 }
}
}
fn main() {}

View file

@ -0,0 +1,8 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/attr_field_non_output_return_type.rs:10:1
|
10 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,17 @@
use juniper::graphql_object;
struct ObjA;
#[graphql_object]
impl ObjA {
fn id(&self) -> &str {
"funA"
}
#[graphql(name = "id")]
fn id2(&self) -> &str {
"funB"
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL object must have a different name for each field
--> $DIR/attr_fields_duplicate.rs:6:6
|
6 | impl ObjA {
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -0,0 +1,12 @@
use juniper::graphql_object;
struct __Obj;
#[graphql_object]
impl __Obj {
fn id(&self) -> &str {
"funA"
}
}
fn main() {}

View file

@ -1,7 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system. error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/impl_no_underscore.rs:5:15 --> $DIR/attr_name_double_underscored.rs:6:6
| |
5 | #[graphql(name = "__test")] 6 | impl __Obj {
| ^^^^ | ^^^^^
| |
= note: https://spec.graphql.org/June2018/#sec-Schema = note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,8 @@
use juniper::graphql_object;
struct Obj;
#[graphql_object]
impl Obj {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL object must have at least one field
--> $DIR/attr_no_fields.rs:6:6
|
6 | impl Obj {}
| ^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -0,0 +1,6 @@
use juniper::graphql_object;
#[graphql_object]
enum Character {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: #[graphql_object] attribute is applicable to non-trait `impl` blocks only
--> $DIR/attr_wrong_item.rs:3:1
|
3 | #[graphql_object]
| ^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,8 @@
use juniper::GraphQLObject;
#[derive(GraphQLObject)]
struct Object {
__test: String,
}
fn main() {}

View file

@ -1,7 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system. error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/derive_no_underscore.rs:3:15 --> $DIR/derive_field_double_underscored.rs:5:5
| |
3 | #[graphql(name = "__test")] 5 | __test: String,
| ^^^^ | ^^^^^^
| |
= note: https://spec.graphql.org/June2018/#sec-Schema = note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,13 @@
use juniper::{GraphQLInputObject, GraphQLObject};
#[derive(GraphQLInputObject)]
struct ObjB {
id: i32,
}
#[derive(GraphQLObject)]
struct ObjA {
id: ObjB,
}
fn main() {}

View file

@ -0,0 +1,8 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/derive_field_non_output_return_type.rs:8:10
|
8 | #[derive(GraphQLObject)]
| ^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the derive macro `GraphQLObject` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,10 @@
use juniper::GraphQLObject;
#[derive(GraphQLObject)]
struct ObjA {
id: String,
#[graphql(name = "id")]
id2: String,
}
fn main() {}

View file

@ -0,0 +1,11 @@
error: GraphQL object must have a different name for each field
--> $DIR/derive_fields_duplicate.rs:4:1
|
4 | / struct ObjA {
5 | | id: String,
6 | | #[graphql(name = "id")]
7 | | id2: String,
8 | | }
| |_^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,8 +0,0 @@
#[derive(juniper::GraphQLObject)]
struct Object {
test: String,
#[graphql(name = "test")]
test2: String,
}
fn main() {}

View file

@ -1,9 +0,0 @@
error: GraphQL object does not allow fields with the same name
--> $DIR/derive_fields_unique.rs:4:5
|
4 | / #[graphql(name = "test")]
5 | | test2: String,
| |_________________^
|
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,12 +0,0 @@
// FIXME: enable this if interfaces are supported
#[derive(juniper::GraphQLInputObject)]
struct ObjectA {
test: String,
}
#[derive(juniper::GraphQLObject)]
struct Object {
field: ObjectA,
}
fn main() {}

View file

@ -0,0 +1,8 @@
use juniper::GraphQLObject;
#[derive(GraphQLObject)]
struct __Obj {
id: String,
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/derive_name_double_underscored.rs:4:8
|
4 | struct __Obj {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -1,4 +1,6 @@
#[derive(juniper::GraphQLObject)] use juniper::GraphQLObject;
struct Object {}
#[derive(GraphQLObject)]
struct Obj {}
fn main() {} fn main() {}

View file

@ -1,7 +1,7 @@
error: GraphQL object expects at least one field error: GraphQL object must have at least one field
--> $DIR/derive_no_fields.rs:2:1 --> $DIR/derive_no_fields.rs:4:1
| |
2 | struct Object {} 4 | struct Obj {}
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
| |
= note: https://spec.graphql.org/June2018/#sec-Objects = note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,7 +0,0 @@
#[derive(juniper::GraphQLObject)]
struct Object {
#[graphql(name = "__test")]
test: String,
}
fn main() {}

View file

@ -0,0 +1,6 @@
use juniper::GraphQLObject;
#[derive(GraphQLObject)]
enum Character {}
fn main() {}

View file

@ -0,0 +1,5 @@
error: GraphQL object can only be derived for structs
--> $DIR/derive_wrong_item.rs:4:1
|
4 | enum Character {}
| ^^^^^^^^^^^^^^^^^

View file

@ -1,15 +0,0 @@
#[derive(juniper::GraphQLObject)]
struct Obj {
field: String,
}
struct Object {}
#[juniper::graphql_object]
impl Object {
fn test(&self, test: Obj) -> String {
String::new()
}
}
fn main() {}

View file

@ -1,29 +0,0 @@
error[E0277]: the trait bound `Obj: IsInputType<__S>` is not satisfied
--> $DIR/impl_argument_no_object.rs:8:1
|
8 | #[juniper::graphql_object]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `Obj`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Obj: FromInputValue<__S>` is not satisfied
--> $DIR/impl_argument_no_object.rs:8:1
|
8 | #[juniper::graphql_object]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `Obj`
|
= note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Obj: FromInputValue` is not satisfied
--> $DIR/impl_argument_no_object.rs:8:1
|
8 | #[juniper::graphql_object]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue` is not implemented for `Obj`
|
::: $WORKSPACE/juniper/src/ast.rs
|
| pub trait FromInputValue<S = DefaultScalarValue>: Sized {
| ------------------------------------------------------- required by this bound in `FromInputValue`
|
= note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,11 +0,0 @@
struct Object;
#[juniper::graphql_object]
impl Object {
#[graphql(arguments(input(default = [true, false, false])))]
fn wrong(input: [bool; 2]) -> bool {
input[0]
}
}
fn main() {}

View file

@ -1,7 +0,0 @@
error[E0308]: mismatched types
--> $DIR/impl_argument_wrong_default_array.rs:3:1
|
3 | #[juniper::graphql_object]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 2 elements, found one with 3 elements
|
= note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,14 +0,0 @@
struct Object {}
#[juniper::graphql_object]
impl Object {
fn test(&self) -> String {
String::new()
}
fn test(&self) -> String {
String::new()
}
}
fn main() {}

View file

@ -1,10 +0,0 @@
error: GraphQL object does not allow fields with the same name
--> $DIR/impl_fields_unique.rs:9:5
|
9 | / fn test(&self) -> String {
10 | | String::new()
11 | | }
| |_____^
|
= help: There is at least one other field with the same name `test`, possibly renamed via the #[graphql] attribute
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,19 +0,0 @@
// FIXME: enable this if interfaces are supported
#[derive(juniper::GraphQLInputObject)]
#[graphql(scalar = juniper::DefaultScalarValue)]
struct Obj {
field: String,
}
struct Object {}
#[juniper::graphql_object]
impl Object {
fn test(&self) -> Obj {
Obj {
field: String::new(),
}
}
}
fn main() {}

View file

@ -1,11 +0,0 @@
struct Object {}
#[juniper::graphql_object]
impl Object {
#[graphql(arguments(arg(name = "__arg")))]
fn test(&self, arg: String) -> String {
arg
}
}
fn main() {}

View file

@ -1,6 +0,0 @@
struct Object {}
#[juniper::graphql_object]
impl Object {}
fn main() {}

View file

@ -1,7 +0,0 @@
error: GraphQL object expects at least one field
--> $DIR/impl_no_fields.rs:4:1
|
4 | impl Object {}
| ^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -1,11 +0,0 @@
struct Object {}
#[juniper::graphql_object]
impl Object {
#[graphql(name = "__test")]
fn test(&self) -> String {
String::new()
}
}
fn main() {}

View file

@ -0,0 +1,16 @@
use std::pin::Pin;
use juniper::graphql_subscription;
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
struct Obj;
#[graphql_subscription]
impl Obj {
async fn id(&self, __num: i32) -> Stream<'static, &'static str> {
Box::pin(stream::once(future::ready("funA")))
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/argument_double_underscored.rs:11:24
|
11 | async fn id(&self, __num: i32) -> Stream<'static, &'static str> {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,22 @@
use std::pin::Pin;
use futures::{future, stream};
use juniper::{graphql_subscription, GraphQLObject};
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
#[derive(GraphQLObject)]
struct ObjA {
test: String,
}
struct ObjB;
#[graphql_subscription]
impl ObjB {
async fn id(&self, obj: ObjA) -> Stream<'static, &'static str> {
Box::pin(stream::once(future::ready("funA")))
}
}
fn main() {}

View file

@ -0,0 +1,16 @@
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied
--> $DIR/argument_non_input_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,20 @@
use std::pin::Pin;
use futures::{future, stream};
use juniper::graphql_subscription;
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
struct ObjA;
#[graphql_subscription]
impl ObjA {
async fn wrong(
&self,
#[graphql(default = [true, false, false])] input: [bool; 2],
) -> Stream<'static, bool> {
Box::pin(stream::once(future::ready(input[0])))
}
}
fn main() {}

View file

@ -0,0 +1,12 @@
error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
--> $DIR/argument_wrong_default_array.rs:10:1
|
10 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
= help: the following implementations were found:
<&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
<&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
<&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,16 @@
use std::pin::Pin;
use juniper::graphql_subscription;
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
struct ObjA;
#[graphql_subscription]
impl Character for ObjA {
async fn __id() -> Stream<'static, &'static str> {
Box::pin(stream::once(future::ready("funA")))
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: #[graphql_subscription] attribute is applicable to non-trait `impl` blocks only
--> $DIR/field_double_underscored.rs:9:1
|
9 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,22 @@
use std::pin::Pin;
use futures::{future, stream};
use juniper::{graphql_subscription, GraphQLInputObject};
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
#[derive(GraphQLInputObject)]
struct ObjB {
id: i32,
}
struct ObjA;
#[graphql_subscription]
impl ObjA {
async fn id(&self) -> Stream<'static, ObjB> {
Box::pin(stream::once(future::ready(ObjB { id: 34 })))
}
}
fn main() {}

View file

@ -0,0 +1,8 @@
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
--> $DIR/field_non_output_return_type.rs:15:1
|
15 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
= note: required by `juniper::marker::IsOutputType::mark`
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,16 @@
use std::pin::Pin;
use juniper::graphql_subscription;
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
struct ObjA;
#[graphql_subscription]
impl ObjA {
fn id(&self) -> Stream<'static, bool> {
Box::pin(stream::once(future::ready(true)))
}
}
fn main() {}

View file

@ -0,0 +1,8 @@
error: GraphQL object synchronous resolvers are not supported
--> $DIR/field_not_async.rs:11:5
|
11 | fn id(&self) -> Stream<'static, bool> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects
= note: Specify that this function is async: `async fn foo()`

View file

@ -0,0 +1,21 @@
use std::pin::Pin;
use juniper::graphql_subscription;
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
struct ObjA;
#[graphql_subscription]
impl ObjA {
async fn id(&self) -> Stream<'static, &'static str> {
Box::pin(stream::once(future::ready("funA")))
}
#[graphql(name = "id")]
async fn id2(&self) -> Stream<'static, &'static str> {
Box::pin(stream::once(future::ready("funB")))
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL object must have a different name for each field
--> $DIR/fields_duplicate.rs:10:6
|
10 | impl ObjA {
| ^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -0,0 +1,16 @@
use std::pin::Pin;
use juniper::graphql_subscription;
type Stream<'a, I> = Pin<Box<dyn futures::Stream<Item = I> + Send + 'a>>;
struct __Obj;
#[graphql_subscription]
impl __Obj {
fn id(&self) -> Stream<'static, &'static str> {
Box::pin(stream::once(future::ready("funA")))
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQLs introspection system.
--> $DIR/name_double_underscored.rs:10:6
|
10 | impl __Obj {
| ^^^^^
|
= note: https://spec.graphql.org/June2018/#sec-Schema

View file

@ -0,0 +1,8 @@
use juniper::graphql_subscription;
struct Obj;
#[graphql_subscription]
impl Obj {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: GraphQL object must have at least one field
--> $DIR/no_fields.rs:6:6
|
6 | impl Obj {}
| ^^^
|
= note: https://spec.graphql.org/June2018/#sec-Objects

View file

@ -0,0 +1,6 @@
use juniper::graphql_subscription;
#[graphql_subscription]
enum Character {}
fn main() {}

View file

@ -0,0 +1,7 @@
error: #[graphql_subscription] attribute is applicable to non-trait `impl` blocks only
--> $DIR/wrong_item.rs:3:1
|
3 | #[graphql_subscription]
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `graphql_subscription` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,8 +1,8 @@
error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
--> $DIR/enum_non_object_variant.rs:9:10 --> $DIR/enum_non_object_variant.rs:9:10
| |
9 | #[derive(GraphQLUnion)] 9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test` | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
| |
= note: required by `juniper::marker::GraphQLObjectType::mark` = note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,8 +1,8 @@
error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
--> $DIR/struct_non_object_variant.rs:9:10 --> $DIR/struct_non_object_variant.rs:9:10
| |
9 | #[derive(GraphQLUnion)] 9 | #[derive(GraphQLUnion)]
| ^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test` | ^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
| |
= note: required by `juniper::marker::GraphQLObjectType::mark` = note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the derive macro `GraphQLUnion` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,8 +1,8 @@
error[E0277]: the trait bound `Test: GraphQLObjectType<__S>` is not satisfied error[E0277]: the trait bound `Test: GraphQLObject<__S>` is not satisfied
--> $DIR/trait_non_object_variant.rs:9:1 --> $DIR/trait_non_object_variant.rs:9:1
| |
9 | #[graphql_union] 9 | #[graphql_union]
| ^^^^^^^^^^^^^^^^ the trait `GraphQLObjectType<__S>` is not implemented for `Test` | ^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `Test`
| |
= note: required by `juniper::marker::GraphQLObjectType::mark` = note: required by `juniper::GraphQLObject::mark`
= note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `graphql_union` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -145,8 +145,7 @@ mod as_input_argument {
input[0] input[0]
} }
#[graphql(arguments(input(default = [true, false, false])))] fn third(#[graphql(default = [true, false, false])] input: [bool; 3]) -> bool {
fn third(input: [bool; 3]) -> bool {
input[2] input[2]
} }
} }

View file

@ -69,7 +69,7 @@ fn test_derived_enum() {
let meta = SomeEnum::meta(&(), &mut registry); let meta = SomeEnum::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("Some")); assert_eq!(meta.name(), Some("Some"));
assert_eq!(meta.description(), Some(&"enum descr".to_string())); assert_eq!(meta.description(), Some("enum descr"));
// Test no rename variant. // Test no rename variant.
assert_eq!( assert_eq!(
@ -102,24 +102,21 @@ fn test_derived_enum() {
fn test_doc_comment() { fn test_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = DocEnum::meta(&(), &mut registry); let meta = DocEnum::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"Enum doc.".to_string())); assert_eq!(meta.description(), Some("Enum doc."));
} }
#[test] #[test]
fn test_multi_doc_comment() { fn test_multi_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = MultiDocEnum::meta(&(), &mut registry); let meta = MultiDocEnum::meta(&(), &mut registry);
assert_eq!( assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4."));
meta.description(),
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
);
} }
#[test] #[test]
fn test_doc_comment_override() { fn test_doc_comment_override() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = OverrideDocEnum::meta(&(), &mut registry); let meta = OverrideDocEnum::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"enum override".to_string())); assert_eq!(meta.description(), Some("enum override"));
} }
fn test_context<T>(_t: T) fn test_context<T>(_t: T)

View file

@ -115,7 +115,7 @@ fn test_derived_input_object() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = Input::meta(&(), &mut registry); let meta = Input::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("MyInput")); assert_eq!(meta.name(), Some("MyInput"));
assert_eq!(meta.description(), Some(&"input descr".to_string())); assert_eq!(meta.description(), Some("input descr"));
// Test default value injection. // Test default value injection.
@ -173,22 +173,19 @@ fn test_derived_input_object() {
fn test_doc_comment() { fn test_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = 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."));
} }
#[test] #[test]
fn test_multi_doc_comment() { fn test_multi_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = MultiDocComment::meta(&(), &mut registry); let meta = MultiDocComment::meta(&(), &mut registry);
assert_eq!( assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4."));
meta.description(),
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
);
} }
#[test] #[test]
fn test_doc_comment_override() { fn test_doc_comment_override() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = OverrideDocComment::meta(&(), &mut registry); let meta = OverrideDocComment::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"obj override".to_string())); assert_eq!(meta.description(), Some("obj override"));
} }

View file

@ -1,502 +0,0 @@
use fnv::FnvHashMap;
use juniper::{
execute, graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLObject,
GraphQLType, Object, Registry, RootNode, Value, Variables,
};
#[derive(GraphQLObject, Debug, PartialEq)]
#[graphql(name = "MyObj", description = "obj descr")]
struct Obj {
regular_field: bool,
#[graphql(
name = "renamedField",
description = "descr",
deprecated = "field deprecation"
)]
c: i32,
}
#[derive(GraphQLObject, Debug, PartialEq)]
struct Nested {
obj: Obj,
}
/// Object comment.
#[derive(GraphQLObject, Debug, PartialEq)]
struct DocComment {
/// Field comment.
regular_field: bool,
}
/// Doc 1.\
/// Doc 2.
///
/// Doc 4.
#[derive(GraphQLObject, Debug, PartialEq)]
struct MultiDocComment {
/// Field 1.
/// Field 2.
regular_field: bool,
}
/// This is not used as the description.
#[derive(GraphQLObject, Debug, PartialEq)]
#[graphql(description = "obj override")]
struct OverrideDocComment {
/// This is not used as the description.
#[graphql(description = "field override")]
regular_field: bool,
}
#[derive(GraphQLObject, Debug, PartialEq)]
struct WithLifetime<'a> {
regular_field: &'a i32,
}
#[derive(GraphQLObject, Debug, PartialEq)]
struct SkippedFieldObj {
regular_field: bool,
#[graphql(skip)]
skipped: i32,
}
#[derive(GraphQLObject, Debug, PartialEq)]
#[graphql(rename = "none")]
struct NoRenameObj {
one_field: bool,
another_field: i32,
}
struct Context;
impl juniper::Context for Context {}
#[derive(GraphQLObject, Debug)]
#[graphql(context = Context)]
struct WithCustomContext {
a: bool,
}
struct Query;
#[graphql_object]
impl Query {
fn obj() -> Obj {
Obj {
regular_field: true,
c: 22,
}
}
fn nested() -> Nested {
Nested {
obj: Obj {
regular_field: false,
c: 333,
},
}
}
fn doc() -> DocComment {
DocComment {
regular_field: true,
}
}
fn multi_doc() -> MultiDocComment {
MultiDocComment {
regular_field: true,
}
}
fn override_doc() -> OverrideDocComment {
OverrideDocComment {
regular_field: true,
}
}
fn skipped_field_obj() -> SkippedFieldObj {
SkippedFieldObj {
regular_field: false,
skipped: 42,
}
}
fn no_rename_obj() -> NoRenameObj {
NoRenameObj {
one_field: true,
another_field: 146,
}
}
}
struct NoRenameQuery;
#[graphql_object(rename = "none")]
impl NoRenameQuery {
fn obj() -> Obj {
Obj {
regular_field: false,
c: 22,
}
}
fn no_rename_obj() -> NoRenameObj {
NoRenameObj {
one_field: true,
another_field: 146,
}
}
}
#[tokio::test]
async fn test_doc_comment_simple() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = DocComment::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"Object comment.".to_string()));
check_descriptions(
"DocComment",
&Value::scalar("Object comment."),
"regularField",
&Value::scalar("Field comment."),
)
.await;
}
#[tokio::test]
async fn test_multi_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = MultiDocComment::meta(&(), &mut registry);
assert_eq!(
meta.description(),
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
);
check_descriptions(
"MultiDocComment",
&Value::scalar("Doc 1. Doc 2.\n\nDoc 4."),
"regularField",
&Value::scalar("Field 1.\nField 2."),
)
.await;
}
#[tokio::test]
async fn test_doc_comment_override() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = OverrideDocComment::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"obj override".to_string()));
check_descriptions(
"OverrideDocComment",
&Value::scalar("obj override"),
"regularField",
&Value::scalar("field override"),
)
.await;
}
#[tokio::test]
async fn test_derived_object() {
assert_eq!(
<Obj as GraphQLType<DefaultScalarValue>>::name(&()),
Some("MyObj")
);
// Verify meta info.
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = Obj::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("MyObj"));
assert_eq!(meta.description(), Some(&"obj descr".to_string()));
let doc = r#"
{
obj {
regularField
renamedField
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
assert_eq!(
execute(doc, None, &schema, &Variables::new(), &()).await,
Ok((
Value::object(
vec![(
"obj",
Value::object(
vec![
("regularField", Value::scalar(true)),
("renamedField", Value::scalar(22)),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}
#[tokio::test]
#[should_panic]
async fn test_cannot_query_skipped_field() {
let doc = r#"
{
skippedFieldObj {
skippedField
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
execute(doc, None, &schema, &Variables::new(), &())
.await
.unwrap();
}
#[tokio::test]
async fn test_skipped_field_siblings_unaffected() {
let doc = r#"
{
skippedFieldObj {
regularField
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
execute(doc, None, &schema, &Variables::new(), &())
.await
.unwrap();
}
#[tokio::test]
async fn test_derived_object_nested() {
let doc = r#"
{
nested {
obj {
regularField
renamedField
}
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
assert_eq!(
execute(doc, None, &schema, &Variables::new(), &()).await,
Ok((
Value::object(
vec![(
"nested",
Value::object(
vec![(
"obj",
Value::object(
vec![
("regularField", Value::scalar(false)),
("renamedField", Value::scalar(333)),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}
#[tokio::test]
async fn test_no_rename_root() {
let doc = r#"
{
no_rename_obj {
one_field
another_field
}
obj {
regularField
}
}"#;
let schema = RootNode::new(
NoRenameQuery,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
assert_eq!(
execute(doc, None, &schema, &Variables::new(), &()).await,
Ok((
Value::object(
vec![
(
"no_rename_obj",
Value::object(
vec![
("one_field", Value::scalar(true)),
("another_field", Value::scalar(146)),
]
.into_iter()
.collect(),
),
),
(
"obj",
Value::object(
vec![("regularField", Value::scalar(false)),]
.into_iter()
.collect(),
),
)
]
.into_iter()
.collect()
),
vec![]
))
);
}
#[tokio::test]
async fn test_no_rename_obj() {
let doc = r#"
{
noRenameObj {
one_field
another_field
}
}"#;
let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
assert_eq!(
execute(doc, None, &schema, &Variables::new(), &()).await,
Ok((
Value::object(
vec![(
"noRenameObj",
Value::object(
vec![
("one_field", Value::scalar(true)),
("another_field", Value::scalar(146)),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}
async fn check_descriptions(
object_name: &str,
object_description: &Value,
field_name: &str,
field_value: &Value,
) {
let doc = format!(
r#"
{{
__type(name: "{}") {{
name,
description,
fields {{
name
description
}}
}}
}}
"#,
object_name
);
let _result = run_type_info_query(&doc, |(type_info, values)| {
assert_eq!(
type_info.get_field_value("name"),
Some(&Value::scalar(object_name))
);
assert_eq!(
type_info.get_field_value("description"),
Some(object_description)
);
assert!(values.contains(&Value::object(
vec![
("name", Value::scalar(field_name)),
("description", field_value.clone()),
]
.into_iter()
.collect(),
)));
})
.await;
}
async fn run_type_info_query<F>(doc: &str, f: F)
where
F: Fn((&Object<DefaultScalarValue>, &Vec<Value>)) -> (),
{
let schema = RootNode::new(
Query,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
let (result, errs) = execute(doc, None, &schema, &Variables::new(), &())
.await
.expect("Execution failed");
assert_eq!(errs, []);
println!("Result: {:#?}", result);
let type_info = result
.as_object_value()
.expect("Result is not an object")
.get_field_value("__type")
.expect("__type field missing")
.as_object_value()
.expect("__type field not an object value");
let fields = type_info
.get_field_value("fields")
.expect("fields field missing")
.as_list_value()
.expect("fields not a list");
f((type_info, fields));
}

View file

@ -1,139 +0,0 @@
use juniper::{
execute, graphql_object, DefaultScalarValue, EmptyMutation, EmptySubscription, Object,
RootNode, Value, Variables,
};
pub struct MyObject;
#[graphql_object]
impl MyObject {
#[graphql(arguments(arg(name = "test")))]
fn test(&self, arg: String) -> String {
arg
}
}
#[tokio::test]
async fn check_argument_rename() {
let doc = format!(
r#"
{{
__type(name: "{}") {{
name,
description,
fields {{
name
description
}}
}}
}}
"#,
"MyObject"
);
run_type_info_query(&doc, |(_, values)| {
assert_eq!(
*values,
vec![Value::object(
vec![
("name", Value::scalar("test")),
("description", Value::null()),
]
.into_iter()
.collect(),
)]
);
})
.await;
}
async fn run_type_info_query<F>(doc: &str, f: F)
where
F: Fn((&Object<DefaultScalarValue>, &Vec<Value>)) -> (),
{
let schema = RootNode::new(
MyObject,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
let (result, errs) = execute(doc, None, &schema, &Variables::new(), &())
.await
.expect("Execution failed");
assert_eq!(errs, []);
println!("Result: {:#?}", result);
let type_info = result
.as_object_value()
.expect("Result is not an object")
.get_field_value("__type")
.expect("__type field missing")
.as_object_value()
.expect("__type field not an object value");
let fields = type_info
.get_field_value("fields")
.expect("fields field missing")
.as_list_value()
.expect("fields not a list");
f((type_info, fields));
}
mod fallible {
use juniper::{graphql_object, FieldError};
struct Obj;
#[graphql_object]
impl Obj {
fn test(&self, arg: String) -> Result<String, FieldError> {
Ok(arg)
}
}
}
mod raw_argument {
use juniper::{
graphql_object, graphql_value, EmptyMutation, EmptySubscription, RootNode, Variables,
};
struct Obj;
#[graphql_object]
impl Obj {
#[graphql(arguments(r#arg(description = "The only argument")))]
fn test(&self, arg: String) -> String {
arg
}
}
#[tokio::test]
async fn named_correctly() {
let doc = r#"{
__type(name: "Obj") {
fields {
args {
name
}
}
}
}"#;
let schema = RootNode::new(
Obj,
EmptyMutation::<()>::new(),
EmptySubscription::<()>::new(),
);
assert_eq!(
juniper::execute(&doc, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"__type": {"fields": [{"args": [{"name": "arg"}]}]}}),
vec![],
)),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,12 @@
mod derive_enum; mod derive_enum;
mod derive_input_object; mod derive_input_object;
mod derive_object;
mod derive_object_with_raw_idents; mod derive_object_with_raw_idents;
mod derive_scalar; mod derive_scalar;
mod impl_object;
mod impl_scalar; mod impl_scalar;
mod interface_attr; mod interface_attr;
mod object_attr;
mod object_derive;
mod scalar_value_transparent; mod scalar_value_transparent;
mod subscription_attr;
mod union_attr; mod union_attr;
mod union_derive; mod union_derive;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -63,10 +63,7 @@ fn test_scalar_value_custom() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = CustomUserId::meta(&(), &mut registry); let meta = CustomUserId::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("MyUserId")); assert_eq!(meta.name(), Some("MyUserId"));
assert_eq!( assert_eq!(meta.description(), Some("custom description..."));
meta.description(),
Some(&"custom description...".to_string())
);
let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap(); let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap(); let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap();
@ -81,5 +78,5 @@ fn test_scalar_value_custom() {
fn test_scalar_value_doc_comment() { fn test_scalar_value_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default()); let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = IdWithDocComment::meta(&(), &mut registry); let meta = IdWithDocComment::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"The doc comment...".to_string())); assert_eq!(meta.description(), Some("The doc comment..."));
} }

File diff suppressed because it is too large Load diff

View file

@ -660,6 +660,184 @@ mod custom_scalar {
} }
} }
mod explicit_generic_scalar {
use super::*;
#[graphql_union(scalar = S)]
trait Character<S: ScalarValue> {
fn as_human(&self) -> Option<&Human> {
None
}
fn as_droid(&self) -> Option<&Droid> {
None
}
}
impl<S: ScalarValue> Character<S> for Human {
fn as_human(&self) -> Option<&Human> {
Some(&self)
}
}
impl<S: ScalarValue> Character<S> for Droid {
fn as_droid(&self) -> Option<&Droid> {
Some(&self)
}
}
type DynCharacter<'a, S> = dyn Character<S> + Send + Sync + 'a;
enum QueryRoot {
Human,
Droid,
}
#[graphql_object]
impl QueryRoot {
fn character<__S: ScalarValue>(&self) -> Box<DynCharacter<'_, __S>> {
let ch: Box<DynCharacter<'_, _>> = match self {
Self::Human => Box::new(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
Self::Droid => Box::new(Droid {
id: "droid-99".to_string(),
primary_function: "run".to_string(),
}),
};
ch
}
}
const DOC: &str = r#"{
character {
... on Human {
humanId: id
homePlanet
}
... on Droid {
droidId: id
primaryFunction
}
}
}"#;
#[tokio::test]
async fn resolves_human() {
let schema = schema(QueryRoot::Human);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
vec![],
)),
);
}
#[tokio::test]
async fn resolves_droid() {
let schema = schema(QueryRoot::Droid);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
vec![],
)),
);
}
}
mod bounded_generic_scalar {
use super::*;
#[graphql_union(scalar = S: ScalarValue + Clone)]
trait Character {
fn as_human(&self) -> Option<&Human> {
None
}
fn as_droid(&self) -> Option<&Droid> {
None
}
}
impl Character for Human {
fn as_human(&self) -> Option<&Human> {
Some(&self)
}
}
impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> {
Some(&self)
}
}
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
enum QueryRoot {
Human,
Droid,
}
#[graphql_object]
impl QueryRoot {
fn character(&self) -> Box<DynCharacter<'_>> {
let ch: Box<DynCharacter<'_>> = match self {
Self::Human => Box::new(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
Self::Droid => Box::new(Droid {
id: "droid-99".to_string(),
primary_function: "run".to_string(),
}),
};
ch
}
}
const DOC: &str = r#"{
character {
... on Human {
humanId: id
homePlanet
}
... on Droid {
droidId: id
primaryFunction
}
}
}"#;
#[tokio::test]
async fn resolves_human() {
let schema = schema(QueryRoot::Human);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
vec![],
)),
);
}
#[tokio::test]
async fn resolves_droid() {
let schema = schema(QueryRoot::Droid);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
vec![],
)),
);
}
}
mod explicit_custom_context { mod explicit_custom_context {
use super::*; use super::*;

View file

@ -284,6 +284,101 @@ mod generic_enum {
} }
} }
/* TODO: make it work
mod generic_lifetime_enum {
use super::*;
#[derive(GraphQLObject)]
struct LifetimeHuman<'id> {
id: &'id str,
}
#[derive(GraphQLObject)]
struct GenericDroid<B = ()> {
id: String,
#[graphql(ignore)]
_t: PhantomData<B>,
}
#[derive(GraphQLUnion)]
enum Character<'id, B = ()> {
A(LifetimeHuman<'id>),
B(GenericDroid<B>),
}
enum QueryRoot {
Human,
Droid,
}
#[graphql_object]
impl QueryRoot {
fn character(&self) -> Character<'_> {
match self {
Self::Human => Character::A(LifetimeHuman { id: "human-32" }),
Self::Droid => Character::B(GenericDroid {
id: "droid-99".to_string(),
_t: PhantomData,
}),
}
}
}
const DOC: &str = r#"{
character {
... on LifetimeHuman {
humanId: id
}
... on GenericDroid {
droidId: id
}
}
}"#;
#[tokio::test]
async fn resolves_human() {
let schema = schema(QueryRoot::Human);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"humanId": "human-32"}}),
vec![],
)),
);
}
#[tokio::test]
async fn resolves_droid() {
let schema = schema(QueryRoot::Droid);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"droidId": "droid-99"}}),
vec![],
)),
);
}
#[tokio::test]
async fn uses_type_name_without_type_params() {
const DOC: &str = r#"{
__type(name: "Character") {
name
}
}"#;
let schema = schema(QueryRoot::Human);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])),
);
}
}
*/
mod description_from_doc_comments { mod description_from_doc_comments {
use super::*; use super::*;
@ -571,6 +666,150 @@ mod custom_scalar {
} }
} }
mod explicit_generic_scalar {
use super::*;
#[derive(GraphQLUnion)]
#[graphql(scalar = S)]
enum Character<S: ScalarValue> {
A(Human),
B(Droid),
#[graphql(ignore)]
_P(PhantomData<S>),
}
enum QueryRoot {
Human,
Droid,
}
#[graphql_object]
impl QueryRoot {
fn character<__S: ScalarValue>(&self) -> Character<__S> {
match self {
Self::Human => Character::A(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
Self::Droid => Character::B(Droid {
id: "droid-99".to_string(),
primary_function: "run".to_string(),
}),
}
}
}
const DOC: &str = r#"{
character {
... on Human {
humanId: id
homePlanet
}
... on Droid {
droidId: id
primaryFunction
}
}
}"#;
#[tokio::test]
async fn resolves_human() {
let schema = schema(QueryRoot::Human);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
vec![],
)),
);
}
#[tokio::test]
async fn resolves_droid() {
let schema = schema(QueryRoot::Droid);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
vec![],
)),
);
}
}
mod bounded_generic_scalar {
use super::*;
#[derive(GraphQLUnion)]
#[graphql(scalar = S: ScalarValue + Clone)]
enum Character {
A(Human),
B(Droid),
}
enum QueryRoot {
Human,
Droid,
}
#[graphql_object]
impl QueryRoot {
fn character(&self) -> Character {
match self {
Self::Human => Character::A(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
Self::Droid => Character::B(Droid {
id: "droid-99".to_string(),
primary_function: "run".to_string(),
}),
}
}
}
const DOC: &str = r#"{
character {
... on Human {
humanId: id
homePlanet
}
... on Droid {
droidId: id
primaryFunction
}
}
}"#;
#[tokio::test]
async fn resolves_human() {
let schema = schema(QueryRoot::Human);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
vec![],
)),
);
}
#[tokio::test]
async fn resolves_droid() {
let schema = schema(QueryRoot::Droid);
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &()).await,
Ok((
graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
vec![],
)),
);
}
}
mod custom_context { mod custom_context {
use super::*; use super::*;
@ -1227,6 +1466,75 @@ mod trivial_struct {
)), )),
); );
} }
#[tokio::test]
async fn is_graphql_union() {
const DOC: &str = r#"{
__type(name: "Character") {
kind
}
}"#;
let schema = schema(QueryRoot::Human);
let db = Database {
human: Some(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
droid: None,
};
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &db).await,
Ok((graphql_value!({"__type": {"kind": "UNION"}}), vec![])),
);
}
#[tokio::test]
async fn uses_type_name() {
const DOC: &str = r#"{
__type(name: "Character") {
name
}
}"#;
let schema = schema(QueryRoot::Human);
let db = Database {
human: Some(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
droid: None,
};
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &db).await,
Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])),
);
}
#[tokio::test]
async fn has_no_description() {
const DOC: &str = r#"{
__type(name: "Character") {
description
}
}"#;
let schema = schema(QueryRoot::Human);
let db = Database {
human: Some(Human {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}),
droid: None,
};
assert_eq!(
execute(DOC, None, &schema, &Variables::new(), &db).await,
Ok((graphql_value!({"__type": {"description": None}}), vec![])),
);
}
} }
mod generic_struct { mod generic_struct {
@ -1473,6 +1781,8 @@ mod full_featured_struct {
} }
} }
/// Checks that union with boxed variants resolves okay.
/// See [#845](https://github.com/graphql-rust/juniper/issues/845) for details.
mod issue_845 { mod issue_845 {
use std::sync::Arc; use std::sync::Arc;

View file

@ -1,4 +1,7 @@
use juniper::*; use juniper::{
graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLInputObject,
InputValue, Nullable,
};
pub struct Context; pub struct Context;
@ -6,12 +9,12 @@ impl juniper::Context for Context {}
pub struct Query; pub struct Query;
#[derive(juniper::GraphQLInputObject)] #[derive(GraphQLInputObject)]
struct ObjectInput { struct ObjectInput {
field: Nullable<i32>, field: Nullable<i32>,
} }
#[graphql_object(Context=Context)] #[graphql_object(context = Context)]
impl Query { impl Query {
fn is_explicit_null(arg: Nullable<i32>) -> bool { fn is_explicit_null(arg: Nullable<i32>) -> bool {
arg.is_explicit_null() arg.is_explicit_null()
@ -26,8 +29,6 @@ type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>, EmptySub
#[tokio::test] #[tokio::test]
async fn explicit_null() { async fn explicit_null() {
let ctx = Context;
let query = r#" let query = r#"
query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) { query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) {
literalOneIsExplicitNull: isExplicitNull(arg: 1) literalOneIsExplicitNull: isExplicitNull(arg: 1)
@ -41,25 +42,25 @@ async fn explicit_null() {
} }
"#; "#;
let (data, errors) = juniper::execute( let schema = &Schema::new(
query,
None,
&Schema::new(
Query, Query,
EmptyMutation::<Context>::new(), EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(), EmptySubscription::<Context>::new(),
), );
&[ let vars = [
("emptyObj".to_string(), InputValue::Object(vec![])), ("emptyObj".to_string(), InputValue::Object(vec![])),
( (
"literalNullObj".to_string(), "literalNullObj".to_string(),
InputValue::object(vec![("field", InputValue::null())].into_iter().collect()), InputValue::object(vec![("field", InputValue::null())].into_iter().collect()),
), ),
] ];
.iter()
.cloned() let (data, errors) = juniper::execute(
.collect(), query,
&ctx, None,
&schema,
&vars.iter().cloned().collect(),
&Context,
) )
.await .await
.unwrap(); .unwrap();

View file

@ -1,7 +1,12 @@
// Original author of this test is <https://github.com/davidpdrsn>. //! Checks that `executor.look_ahead().field_name()` is correct in presence of
//! multiple query fields.
//! See [#371](https://github.com/graphql-rust/juniper/issues/371) for details.
//!
//! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn).
use juniper::{ use juniper::{
graphql_object, EmptyMutation, EmptySubscription, LookAheadMethods as _, RootNode, Variables, graphql_object, EmptyMutation, EmptySubscription, Executor, LookAheadMethods as _, RootNode,
ScalarValue, Variables,
}; };
pub struct Context; pub struct Context;
@ -12,14 +17,14 @@ pub struct Query;
#[graphql_object(context = Context)] #[graphql_object(context = Context)]
impl Query { impl Query {
fn users(exec: &Executor) -> Vec<User> { fn users<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec<User> {
let lh = exec.look_ahead(); let lh = executor.look_ahead();
assert_eq!(lh.field_name(), "users"); assert_eq!(lh.field_name(), "users");
vec![User] vec![User]
} }
fn countries(exec: &Executor) -> Vec<Country> { fn countries<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec<Country> {
let lh = exec.look_ahead(); let lh = executor.look_ahead();
assert_eq!(lh.field_name(), "countries"); assert_eq!(lh.field_name(), "countries");
vec![Country] vec![Country]
} }
@ -49,21 +54,10 @@ type Schema = RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription
#[tokio::test] #[tokio::test]
async fn users() { async fn users() {
let ctx = Context; let query = "{ users { id } }";
let query = r#"{ users { id } }"#; let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&juniper::Variables::new(),
&ctx,
)
.await .await
.unwrap(); .unwrap();
@ -72,17 +66,10 @@ async fn users() {
#[tokio::test] #[tokio::test]
async fn countries() { async fn countries() {
let ctx = Context; let query = "{ countries { id } }";
let query = r#"{ countries { id } }"#; let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&juniper::Variables::new(),
&ctx,
)
.await .await
.unwrap(); .unwrap();
@ -91,26 +78,13 @@ async fn countries() {
#[tokio::test] #[tokio::test]
async fn both() { async fn both() {
let ctx = Context; let query = "{
let query = r#"
{
countries { id } countries { id }
users { id } users { id }
} }";
"#;
let (_, errors) = juniper::execute( let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
query, let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&Variables::new(),
&ctx,
)
.await .await
.unwrap(); .unwrap();
@ -119,26 +93,13 @@ async fn both() {
#[tokio::test] #[tokio::test]
async fn both_in_different_order() { async fn both_in_different_order() {
let ctx = Context; let query = "{
let query = r#"
{
users { id } users { id }
countries { id } countries { id }
} }";
"#;
let (_, errors) = juniper::execute( let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
query, let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&Variables::new(),
&ctx,
)
.await .await
.unwrap(); .unwrap();

View file

@ -1,3 +1,6 @@
//! Checks that `__typename` field queries okay on root types.
//! See [#372](https://github.com/graphql-rust/juniper/issues/372) for details.
use futures::{stream, StreamExt as _}; use futures::{stream, StreamExt as _};
use juniper::{graphql_object, graphql_subscription, graphql_value, RootNode, Value, Variables}; use juniper::{graphql_object, graphql_subscription, graphql_value, RootNode, Value, Variables};

View file

@ -1,13 +1,18 @@
// Original author of this test is <https://github.com/davidpdrsn>. //! Checks that `executor.look_ahead()` on a fragment with nested type works okay.
//! See [#398](https://github.com/graphql-rust/juniper/issues/398) for details.
//!
//! Original author of this test is [@davidpdrsn](https://github.com/davidpdrsn).
use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode, Variables}; use juniper::{
graphql_object, EmptyMutation, EmptySubscription, Executor, RootNode, ScalarValue, Variables,
};
struct Query; struct Query;
#[graphql_object] #[graphql_object]
impl Query { impl Query {
fn users(executor: &Executor) -> Vec<User> { fn users<S: ScalarValue>(executor: &Executor<'_, '_, (), S>) -> Vec<User> {
// This doesn't cause a panic // This doesn't cause a panic.
executor.look_ahead(); executor.look_ahead();
vec![User { vec![User {
@ -22,7 +27,7 @@ struct User {
#[graphql_object] #[graphql_object]
impl User { impl User {
fn country(&self, executor: &Executor) -> &Country { fn country<S: ScalarValue>(&self, executor: &Executor<'_, '_, (), S>) -> &Country {
// This panics! // This panics!
executor.look_ahead(); executor.look_ahead();
@ -44,7 +49,7 @@ impl Country {
type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>; type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
#[tokio::test] #[tokio::test]
async fn test_lookahead_from_fragment_with_nested_type() { async fn lookahead_from_fragment_with_nested_type() {
let _ = juniper::execute( let _ = juniper::execute(
r#" r#"
query Query { query Query {

View file

@ -1,4 +1,9 @@
use juniper::*; //! Checks that using a fragment of an implementation in an interface works okay.
//! See [#407](https://github.com/graphql-rust/juniper/issues/407) for details.
use juniper::{
graphql_interface, graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, Variables,
};
struct Query; struct Query;
@ -53,7 +58,7 @@ impl Query {
type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
#[tokio::test] #[tokio::test]
async fn test_fragments_in_interface() { async fn fragments_in_interface() {
let query = r#" let query = r#"
query Query { query Query {
characters { characters {
@ -71,30 +76,19 @@ async fn test_fragments_in_interface() {
} }
"#; "#;
let (_, errors) = juniper::execute( let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
query,
None, let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.await .await
.unwrap(); .unwrap();
assert_eq!(errors.len(), 0); assert_eq!(errors.len(), 0);
let (_, errors) = juniper::execute_sync( let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.unwrap();
assert_eq!(errors.len(), 0); assert_eq!(errors.len(), 0);
} }
#[tokio::test] #[tokio::test]
async fn test_inline_fragments_in_interface() { async fn inline_fragments_in_interface() {
let query = r#" let query = r#"
query Query { query Query {
characters { characters {
@ -116,24 +110,13 @@ async fn test_inline_fragments_in_interface() {
} }
"#; "#;
let (_, errors) = juniper::execute( let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
query,
None, let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.await .await
.unwrap(); .unwrap();
assert_eq!(errors.len(), 0); assert_eq!(errors.len(), 0);
let (_, errors) = juniper::execute_sync( let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.unwrap();
assert_eq!(errors.len(), 0); assert_eq!(errors.len(), 0);
} }

Some files were not shown because too many files have changed in this diff Show more