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 {
session: Session,
}
impl juniper::Context for Context {}
struct Mutation;
#[juniper::graphql_object(Context=Context)]
#[juniper::graphql_object(context = Context)]
impl Mutation {
fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> {
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:
```rust
# extern crate futures;
# extern crate juniper;
# extern crate juniper_subscriptions;
# extern crate tokio;
# use juniper::{graphql_object, graphql_subscription, FieldError};
# use futures::Stream;
# use std::pin::Pin;
@ -40,7 +36,7 @@ sequentially:
# pub struct Query;
# #[graphql_object(context = Database)]
# impl Query {
# fn hello_world() -> &str {
# fn hello_world() -> &'static str {
# "Hello World!"
# }
# }
@ -110,7 +106,7 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
#
# #[graphql_object(context = Database)]
# impl Query {
# fn hello_world() -> &str {
# fn hello_world() -> &'static str {
# "Hello World!"
# }
# }

View file

@ -6,11 +6,9 @@ Juniper follows a [code-first approach][schema_approach] to defining GraphQL sch
## Installation
!FILENAME Cargo.toml
```toml
[dependencies]
juniper = { git = "https://github.com/graphql-rust/juniper" }
juniper = "0.15"
```
## Schema example
@ -89,7 +87,7 @@ struct Query;
context = Context,
)]
impl Query {
fn apiVersion() -> &str {
fn apiVersion() -> &'static str {
"1.0"
}
@ -114,14 +112,13 @@ struct Mutation;
#[graphql_object(
context = Context,
// 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.
scalar = S,
scalar = S: ScalarValue + Display,
)]
impl<S: ScalarValue + Display> Mutation {
fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human, S> {
impl Mutation {
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 human: Human = db.insert_human(&new_human).map_err(|e| e.map_scalar_value())?;
Ok(human)

View file

@ -232,6 +232,21 @@ trait Character {
# 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

View file

@ -3,9 +3,10 @@
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:
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
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. Continuing with the
fields in a Rust `impl` block for a type. Note, that GraphQL fields are defined in
this `impl` block by default. If you want to define normal methods on the struct,
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
macro:
@ -28,12 +29,15 @@ impl Person {
fn age(&self) -> i32 {
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 {
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
field resolver. With this syntax, fields can also take arguments:
```rust
# extern crate juniper;
# use juniper::{graphql_object, GraphQLObject};
@ -61,7 +64,7 @@ struct House {
#[graphql_object]
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> {
self.inhabitants.iter().find(|p| p.name == name)
}
@ -127,15 +130,29 @@ impl Person {
# 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
Method field arguments can also be customized.
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
# extern crate juniper;
# use juniper::graphql_object;
@ -144,21 +161,22 @@ struct Person {}
#[graphql_object]
impl Person {
#[graphql(
arguments(
arg1(
// Set a default value which will be injected if not present.
// The default can be any valid Rust expression, including a function call, etc.
default = true,
// Set a description.
description = "The first argument..."
),
arg2(
default = 0,
)
)
)]
fn field1(&self, arg1: bool, arg2: i32) -> String {
fn field1(
&self,
#[graphql(
// Arguments can also be renamed if required.
name = "arg",
// Set a default value which will be injected if not present.
// The default can be any valid Rust expression, including a function call, etc.
default = true,
// Set a description.
description = "The first argument..."
)]
arg1: bool,
// If default expression is not specified then `Default::default()` value is used.
#[graphql(default)]
arg2: i32,
) -> String {
format!("{} {}", arg1, arg2)
}
}
@ -166,13 +184,23 @@ impl Person {
# 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
GraphQL fields expose more features than Rust's standard method syntax gives us:
* 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).
These, and more features, are described more thoroughly in [the reference documentation](https://docs.rs/juniper/latest/juniper/attr.graphql_object.html).

View file

@ -152,7 +152,22 @@ struct Person {
name: String,
age: i32,
#[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() {}
@ -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
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
# extern crate juniper;
@ -192,9 +207,9 @@ By default all fields in a `GraphQLObject` are included in the generated GraphQL
struct Person {
name: String,
age: i32,
#[graphql(skip)]
#[graphql(ignore)]
# #[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() {}

View file

@ -17,8 +17,7 @@ it will bubble up to the surrounding framework and hopefully be dealt with
there.
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
as you expect them to:
can use the `?` operator and things will generally just work as you expect them to:
```rust
# extern crate juniper;
@ -36,7 +35,7 @@ struct Example {
#[graphql_object]
impl Example {
fn contents() -> FieldResult<String> {
fn contents(&self) -> FieldResult<String> {
let mut file = File::open(&self.filename)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
@ -44,13 +43,10 @@ impl Example {
}
fn foo() -> FieldResult<Option<String>> {
// Some invalid bytes.
let invalid = vec![128, 223];
// Some invalid bytes.
let invalid = vec![128, 223];
match str::from_utf8(&invalid) {
Ok(s) => Ok(Some(s.to_string())),
Err(e) => Err(e)?,
}
Ok(Some(str::from_utf8(&invalid)?.to_string()))
}
}
#
@ -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).
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
execution is resumed. For example, with the previous example and the following
@ -86,12 +81,12 @@ returned:
!FILENAME Response for nullable field with error
```js
```json
{
"data": {
"example": {
contents: "<Contents of the file>",
foo: null,
foo: null
}
},
"errors": [
@ -120,7 +115,7 @@ following would be returned:
!FILENAME Response for non-null field with error and no nullable parent
```js
```json
{
"errors": [
"message": "Permission denied (os error 13)",
@ -162,11 +157,11 @@ struct Example {
#[graphql_object]
impl Example {
fn whatever() -> Result<bool, CustomError> {
if let Some(value) = self.whatever {
return Ok(value);
}
Err(CustomError::WhateverNotSet)
fn whatever(&self) -> Result<bool, CustomError> {
if let Some(value) = self.whatever {
return Ok(value);
}
Err(CustomError::WhateverNotSet)
}
}
#

View file

@ -63,8 +63,8 @@ impl User {
// 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> {
// - the name of the argument SHOULD be `context`
fn friends<'db>(&self, context: &'db Database) -> Vec<&'db User> {
// 5. Use the database to lookup users
self.friend_ids.iter()

View file

@ -11,7 +11,7 @@ use actix_web::{
use juniper::{
graphql_object, graphql_subscription,
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_graphql_ws::ConnectionConfig;

View file

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

View file

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

View file

@ -13,6 +13,7 @@ serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
warp = "0.3"
async-stream = "0.3"
juniper = { path = "../../juniper" }
juniper_graphql_ws = { path = "../../juniper_graphql_ws" }
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 juniper::{
graphql_object, graphql_subscription, DefaultScalarValue, EmptyMutation, FieldError,
GraphQLEnum, RootNode,
GraphQLEnum, RootNode, Value,
};
use juniper_graphql_ws::ConnectionConfig;
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
|
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)
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.
--> $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

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.
--> $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

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.
--> $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

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)]
struct Object {}
use juniper::GraphQLObject;
#[derive(GraphQLObject)]
struct Obj {}
fn main() {}

View file

@ -1,7 +1,7 @@
error: GraphQL object expects at least one field
--> $DIR/derive_no_fields.rs:2:1
error: GraphQL object must have at least one field
--> $DIR/derive_no_fields.rs:4:1
|
2 | struct Object {}
| ^^^^^^^^^^^^^^^^
4 | struct Obj {}
| ^^^^^^^^^^^^^
|
= 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
|
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)

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
|
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)

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
|
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)

View file

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

View file

@ -69,7 +69,7 @@ fn test_derived_enum() {
let meta = SomeEnum::meta(&(), &mut registry);
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.
assert_eq!(
@ -102,24 +102,21 @@ fn test_derived_enum() {
fn test_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = DocEnum::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"Enum doc.".to_string()));
assert_eq!(meta.description(), Some("Enum doc."));
}
#[test]
fn test_multi_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = MultiDocEnum::meta(&(), &mut registry);
assert_eq!(
meta.description(),
Some(&"Doc 1. Doc 2.\n\nDoc 4.".to_string())
);
assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4."));
}
#[test]
fn test_doc_comment_override() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
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)

View file

@ -115,7 +115,7 @@ fn test_derived_input_object() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = Input::meta(&(), &mut registry);
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.
@ -173,22 +173,19 @@ fn test_derived_input_object() {
fn test_doc_comment() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
let meta = DocComment::meta(&(), &mut registry);
assert_eq!(meta.description(), Some(&"Object comment.".to_string()));
assert_eq!(meta.description(), Some("Object comment."));
}
#[test]
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())
);
assert_eq!(meta.description(), Some("Doc 1. Doc 2.\n\nDoc 4."));
}
#[test]
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()));
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_input_object;
mod derive_object;
mod derive_object_with_raw_idents;
mod derive_scalar;
mod impl_object;
mod impl_scalar;
mod interface_attr;
mod object_attr;
mod object_derive;
mod scalar_value_transparent;
mod subscription_attr;
mod union_attr;
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 meta = CustomUserId::meta(&(), &mut registry);
assert_eq!(meta.name(), Some("MyUserId"));
assert_eq!(
meta.description(),
Some(&"custom description...".to_string())
);
assert_eq!(meta.description(), Some("custom description..."));
let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).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() {
let mut registry: Registry = Registry::new(FnvHashMap::default());
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 {
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 {
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 {
use super::*;
@ -1124,8 +1363,8 @@ mod trivial_struct {
#[derive(GraphQLUnion)]
#[graphql(context = Database)]
#[graphql(
on Human = Character::as_human,
on Droid = Character::as_droid,
on Human = Character::as_human,
on Droid = Character::as_droid,
)]
struct Character {
id: String,
@ -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 {
@ -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 {
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;
@ -6,12 +9,12 @@ impl juniper::Context for Context {}
pub struct Query;
#[derive(juniper::GraphQLInputObject)]
#[derive(GraphQLInputObject)]
struct ObjectInput {
field: Nullable<i32>,
}
#[graphql_object(Context=Context)]
#[graphql_object(context = Context)]
impl Query {
fn is_explicit_null(arg: Nullable<i32>) -> bool {
arg.is_explicit_null()
@ -26,40 +29,38 @@ type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>, EmptySub
#[tokio::test]
async fn explicit_null() {
let ctx = Context;
let query = r#"
query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) {
literalOneIsExplicitNull: isExplicitNull(arg: 1)
literalNullIsExplicitNull: isExplicitNull(arg: null)
noArgIsExplicitNull: isExplicitNull
literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1})
literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null})
noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {})
emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj)
literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj)
}
query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) {
literalOneIsExplicitNull: isExplicitNull(arg: 1)
literalNullIsExplicitNull: isExplicitNull(arg: null)
noArgIsExplicitNull: isExplicitNull
literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1})
literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null})
noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {})
emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj)
literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj)
}
"#;
let schema = &Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
);
let vars = [
("emptyObj".to_string(), InputValue::Object(vec![])),
(
"literalNullObj".to_string(),
InputValue::object(vec![("field", InputValue::null())].into_iter().collect()),
),
];
let (data, errors) = juniper::execute(
query,
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&[
("emptyObj".to_string(), InputValue::Object(vec![])),
(
"literalNullObj".to_string(),
InputValue::object(vec![("field", InputValue::null())].into_iter().collect()),
),
]
.iter()
.cloned()
.collect(),
&ctx,
&schema,
&vars.iter().cloned().collect(),
&Context,
)
.await
.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::{
graphql_object, EmptyMutation, EmptySubscription, LookAheadMethods as _, RootNode, Variables,
graphql_object, EmptyMutation, EmptySubscription, Executor, LookAheadMethods as _, RootNode,
ScalarValue, Variables,
};
pub struct Context;
@ -12,14 +17,14 @@ pub struct Query;
#[graphql_object(context = Context)]
impl Query {
fn users(exec: &Executor) -> Vec<User> {
let lh = exec.look_ahead();
fn users<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec<User> {
let lh = executor.look_ahead();
assert_eq!(lh.field_name(), "users");
vec![User]
}
fn countries(exec: &Executor) -> Vec<Country> {
let lh = exec.look_ahead();
fn countries<__S: ScalarValue>(executor: &Executor<'_, '_, Context, __S>) -> Vec<Country> {
let lh = executor.look_ahead();
assert_eq!(lh.field_name(), "countries");
vec![Country]
}
@ -49,98 +54,54 @@ type Schema = RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription
#[tokio::test]
async fn users() {
let ctx = Context;
let query = "{ users { id } }";
let query = r#"{ users { id } }"#;
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&juniper::Variables::new(),
&ctx,
)
.await
.unwrap();
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
.await
.unwrap();
assert_eq!(errors.len(), 0);
}
#[tokio::test]
async fn countries() {
let ctx = Context;
let query = "{ countries { id } }";
let query = r#"{ countries { id } }"#;
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&juniper::Variables::new(),
&ctx,
)
.await
.unwrap();
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
.await
.unwrap();
assert_eq!(errors.len(), 0);
}
#[tokio::test]
async fn both() {
let ctx = Context;
let query = r#"
{
let query = "{
countries { id }
users { id }
}
"#;
}";
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&Variables::new(),
&ctx,
)
.await
.unwrap();
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
.await
.unwrap();
assert_eq!(errors.len(), 0);
}
#[tokio::test]
async fn both_in_different_order() {
let ctx = Context;
let query = r#"
{
let query = "{
users { id }
countries { id }
}
"#;
}";
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(
Query,
EmptyMutation::<Context>::new(),
EmptySubscription::<Context>::new(),
),
&Variables::new(),
&ctx,
)
.await
.unwrap();
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &Context)
.await
.unwrap();
assert_eq!(errors.len(), 0);
}

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 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;
#[graphql_object]
impl Query {
fn users(executor: &Executor) -> Vec<User> {
// This doesn't cause a panic
fn users<S: ScalarValue>(executor: &Executor<'_, '_, (), S>) -> Vec<User> {
// This doesn't cause a panic.
executor.look_ahead();
vec![User {
@ -22,7 +27,7 @@ struct User {
#[graphql_object]
impl User {
fn country(&self, executor: &Executor) -> &Country {
fn country<S: ScalarValue>(&self, executor: &Executor<'_, '_, (), S>) -> &Country {
// This panics!
executor.look_ahead();
@ -44,7 +49,7 @@ impl Country {
type Schema = RootNode<'static, Query, EmptyMutation<()>, EmptySubscription<()>>;
#[tokio::test]
async fn test_lookahead_from_fragment_with_nested_type() {
async fn lookahead_from_fragment_with_nested_type() {
let _ = juniper::execute(
r#"
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;
@ -53,7 +58,7 @@ impl Query {
type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>;
#[tokio::test]
async fn test_fragments_in_interface() {
async fn fragments_in_interface() {
let query = r#"
query Query {
characters {
@ -71,30 +76,19 @@ async fn test_fragments_in_interface() {
}
"#;
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.await
.unwrap();
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
let (_, errors) = juniper::execute_sync(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.unwrap();
let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
assert_eq!(errors.len(), 0);
}
#[tokio::test]
async fn test_inline_fragments_in_interface() {
async fn inline_fragments_in_interface() {
let query = r#"
query Query {
characters {
@ -116,24 +110,13 @@ async fn test_inline_fragments_in_interface() {
}
"#;
let (_, errors) = juniper::execute(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.await
.unwrap();
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (_, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
let (_, errors) = juniper::execute_sync(
query,
None,
&Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()),
&Variables::new(),
&(),
)
.unwrap();
let (_, errors) = juniper::execute_sync(query, None, &schema, &Variables::new(), &()).unwrap();
assert_eq!(errors.len(), 0);
}

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