Make interfaces great again! (#682)
* Bootstrap * Upd * Bootstrap macro * Revert stuff * Correct PoC to compile * Bootstrap #[graphql_interface] expansion * Bootstrap #[graphql_interface] meta parsing * Bootstrap #[graphql_interface] very basic code generation [skip ci] * Upd trait code generation and fix keywords usage [skip ci] * Expand trait impls [skip ci] * Tune up objects [skip ci] * Finally! Complies at least... [skip ci] * Parse meta for fields and its arguments [skip ci] - also, refactor and bikeshed new macros code * Impl filling fields meta and bootstrap field resolution [skip ci] * Poking with fields resolution [skip ci] * Solve Rust's teen async HRTB problems [skip ci] * Start parsing trait methods [skip ci] * Finish parsing fields from trait methods [skip ci] * Autodetect trait asyncness and allow to specify it [skip ci] * Allow to autogenerate trait object alias via attribute * Support generics in trait definition and asyncify them correctly * Temporary disable explicit async * Cover arguments and custom names/descriptions in tests * Re-enable tests with explicit async and fix the codegen to satisfy it * Check implementers are registered in schema and vice versa * Check argument camelCases * Test argument defaults, and allow Into coercions for them * Re-enable markers * Re-enable markers and relax Sized requirement on IsInputType/IsOutputType marker traits * Revert 'juniper_actix' fmt * Fix missing marks for object * Fix subscriptions marks * Deduce result type correctly via traits * Final fixes * Fmt * Restore marks checking * Support custom ScalarValue * Cover deprecations with tests * Impl dowcasting via methods * Impl dowcasting via external functions * Support custom context, vol. 1 * Support custom context, vol. 2 * Cover fallible field with test * Impl explicit generic ScalarValue, vol.1 * Impl explicit generic ScalarValue, vol.2 * Allow passing executor into methods * Generating enum, vol.1 * Generating enum, vol.2 * Generating enum, vol.3 * Generating enum, vol.3 * Generating enum, vol.4 * Generating enum, vol.5 * Generating enum, vol.6 * Generating enum, vol.7 * Generating enum, vol.8 * Refactor juniper stuff * Fix juniper tests, vol.1 * Fix juniper tests, vol.2 * Polish 'juniper' crate changes, vol.1 * Polish 'juniper' crate changes, vol.2 * Remove redundant stuf * Polishing 'juniper_codegen', vol.1 * Polishing 'juniper_codegen', vol.2 * Polishing 'juniper_codegen', vol.3 * Polishing 'juniper_codegen', vol.4 * Polishing 'juniper_codegen', vol.5 * Polishing 'juniper_codegen', vol.6 * Polishing 'juniper_codegen', vol.7 * Polishing 'juniper_codegen', vol.8 * Polishing 'juniper_codegen', vol.9 * Fix other crates tests and make Clippy happier * Fix examples * Add codegen failure tests, vol. 1 * Add codegen failure tests, vol. 2 * Add codegen failure tests, vol.3 * Fix codegen failure tests accordingly to latest nightly Rust * Fix codegen when interface has no implementers * Fix warnings in book tests * Describing new interfaces in Book, vol.1 Co-authored-by: Christian Legnitto <LegNeato@users.noreply.github.com>
This commit is contained in:
parent
1e733cc793
commit
cbf16c5a33
108 changed files with 10707 additions and 2550 deletions
|
@ -30,6 +30,7 @@ result can then be converted to JSON for use with tools and libraries such as
|
|||
[graphql-client](https://github.com/graphql-rust/graphql-client):
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
# extern crate serde_json;
|
||||
use juniper::{EmptyMutation, EmptySubscription, FieldResult, IntrospectionFormat};
|
||||
|
|
|
@ -78,6 +78,7 @@ operation returns a [`Future`][Future] with an `Item` value of a `Result<Connect
|
|||
where [`Connection`][Connection] is a `Stream` of values returned by the operation and [`GraphQLError`][GraphQLError] is the error when the subscription fails.
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# extern crate futures;
|
||||
# extern crate juniper;
|
||||
# extern crate juniper_subscriptions;
|
||||
|
@ -88,8 +89,6 @@ where [`Connection`][Connection] is a `Stream` of values returned by the operati
|
|||
# use juniper_subscriptions::Coordinator;
|
||||
# use futures::{Stream, StreamExt};
|
||||
# use std::pin::Pin;
|
||||
# use tokio::runtime::Runtime;
|
||||
# use tokio::task;
|
||||
#
|
||||
# #[derive(Clone)]
|
||||
# pub struct Database;
|
||||
|
|
|
@ -26,6 +26,7 @@ types to a GraphQL schema. The most important one is the
|
|||
resolvers, which you will use for the `Query` and `Mutation` roots.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
use juniper::{FieldResult, EmptySubscription};
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ The query root is just a GraphQL object. You define it like any other GraphQL
|
|||
object in Juniper, most commonly using the `graphql_object` proc macro:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
# use juniper::FieldResult;
|
||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||
|
@ -44,6 +45,7 @@ Mutations are _also_ just GraphQL objects. Each mutation is a single field
|
|||
that performs some mutating side-effect such as updating a database.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
# use juniper::FieldResult;
|
||||
# #[derive(juniper::GraphQLObject)] struct User { name: String }
|
||||
|
|
|
@ -5,6 +5,7 @@ GraphQL fields. In Juniper, you can define input objects using a custom derive
|
|||
attribute, similar to simple objects and enums:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
#[derive(juniper::GraphQLInputObject)]
|
||||
struct Coordinate {
|
||||
|
@ -33,6 +34,7 @@ Just like the [other](objects/defining_objects.md) [derives](enums.md), you can
|
|||
and add documentation to both the type and the fields:
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
#[derive(juniper::GraphQLInputObject)]
|
||||
#[graphql(name="Coordinate", description="A position on the globe")]
|
||||
|
|
|
@ -1,16 +1,280 @@
|
|||
# Interfaces
|
||||
Interfaces
|
||||
==========
|
||||
|
||||
[GraphQL interfaces][1] map well to interfaces known from common object-oriented languages such as Java or C#, but Rust, unfortunately, has no concept that maps perfectly to them. The nearest analogue of [GraphQL interfaces][1] are Rust traits, and the main difference is that in GraphQL [interface type][1] serves both as an _abstraction_ and a _boxed value (downcastable to concrete implementers)_, while in Rust, a trait is an _abstraction only_ and _to represent such a boxed value a separate type is required_, like enum or trait object, because Rust trait does not represent a type itself, and so can have no values. This difference imposes some unintuitive and non-obvious corner cases when we try to express [GraphQL interfaces][1] in Rust, but on the other hand gives you full control over which type is backing your interface, and how it's resolved.
|
||||
|
||||
For implementing [GraphQL interfaces][1] Juniper provides `#[graphql_interface]` macro.
|
||||
|
||||
|
||||
GraphQL interfaces map well to interfaces known from common object-oriented
|
||||
languages such as Java or C#, but Rust has unfortunately not a concept that maps
|
||||
perfectly to them. Because of this, defining interfaces in Juniper can require a
|
||||
little bit of boilerplate code, but on the other hand gives you full control
|
||||
over which type is backing your interface.
|
||||
|
||||
To highlight a couple of different ways you can implement interfaces in Rust,
|
||||
let's have a look at the same end-result from a few different implementations:
|
||||
|
||||
## Traits
|
||||
|
||||
Defining a trait is mandatory for defining a [GraphQL interface][1], because this is the _obvious_ way we describe an _abstraction_ in Rust. All [interface][1] fields are defined as computed ones via trait methods.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
use juniper::graphql_interface;
|
||||
|
||||
#[graphql_interface]
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
However, to return values of such [interface][1], we should provide its implementers and the Rust type representing a _boxed value of this trait_. The last one can be represented in two flavors: enum and [trait object][2].
|
||||
|
||||
|
||||
### Enum values (default)
|
||||
|
||||
By default, Juniper generates an enum representing the values of the defined [GraphQL interface][1], and names it straightforwardly, `{Interface}Value`.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[graphql_interface(for = Human)] // enumerating all implementers is mandatory
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)] // notice enum name, NOT trait name
|
||||
struct Human {
|
||||
id: String,
|
||||
}
|
||||
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {
|
||||
let human = Human { id: "human-32".to_owned() };
|
||||
// Values type for interface has `From` implementations for all its implementers,
|
||||
// so we don't need to bother with enum variant names.
|
||||
let character: CharacterValue = human.into();
|
||||
assert_eq!(character.id(), "human-32");
|
||||
# }
|
||||
```
|
||||
|
||||
Also, enum name can be specified explicitly, if desired.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[graphql_interface(enum = CharaterInterface, for = Human)]
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharaterInterface)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
#[graphql_interface]
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
|
||||
### Trait object values
|
||||
|
||||
If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, that should be specified explicitly in the trait definition.
|
||||
|
||||
Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood.
|
||||
|
||||
> __NOTICE__:
|
||||
> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# extern crate tokio;
|
||||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
// `dyn` argument accepts the name of type alias for the required trait object,
|
||||
// and macro generates this alias automatically
|
||||
#[graphql_interface(dyn = DynCharacter, for = Human)]
|
||||
trait Character {
|
||||
async fn id(&self) -> &str; // async fields are supported natively
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait,
|
||||
struct Human { // so it may be specified explicitly when required
|
||||
id: String,
|
||||
}
|
||||
#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
|
||||
impl Character for Human {
|
||||
async fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
#
|
||||
# #[tokio::main]
|
||||
# async fn main() {
|
||||
let human = Human { id: "human-32".to_owned() };
|
||||
let character: Box<DynCharacter> = Box::new(human);
|
||||
assert_eq!(character.id().await, "human-32");
|
||||
# }
|
||||
```
|
||||
|
||||
|
||||
### Ignoring trait methods
|
||||
|
||||
We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[graphql_interface(for = Human)]
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
|
||||
#[graphql_interface(ignore)] // or `#[graphql_interface(skip)]`, your choice
|
||||
fn ignored(&self) -> u32 { 0 }
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
struct Human {
|
||||
id: String,
|
||||
}
|
||||
#[graphql_interface] // implementing requires macro attribute too, (°o°)!
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
|
||||
### Custom context
|
||||
|
||||
If a context is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
}
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
#[graphql_interface(for = Human)] // look, ma, context type is inferred! \(^o^)/
|
||||
trait Character { // while still can be specified via `Context = ...` attribute argument
|
||||
// If a field argument is named `context` or `ctx`, it's automatically assumed
|
||||
// as a context argument.
|
||||
fn id(&self, context: &Database) -> Option<&str>;
|
||||
|
||||
// Otherwise, you may mark it explicitly as a context argument.
|
||||
fn name(&self, #[graphql_interface(context)] db: &Database) -> Option<&str>;
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue, Context = Database)]
|
||||
struct Human {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
#[graphql_interface]
|
||||
impl Character for Human {
|
||||
fn id(&self, db: &Database) -> Option<&str> {
|
||||
if db.humans.contains_key(&self.id) {
|
||||
Some(&self.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self, db: &Database) -> Option<&str> {
|
||||
if db.humans.contains_key(&self.id) {
|
||||
Some(&self.name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
|
||||
### Using executor and explicit generic scalar
|
||||
|
||||
If an executor is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
|
||||
|
||||
This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`][4] does so.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
|
||||
|
||||
#[graphql_interface(for = Human, Scalar = S)] // notice specifying scalar as existing type parameter
|
||||
trait Character<S: ScalarValue> {
|
||||
// If a field argument is named `executor`, it's automatically assumed
|
||||
// as an executor argument.
|
||||
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
||||
where
|
||||
S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
|
||||
|
||||
|
||||
// Otherwise, you may mark it explicitly as an executor argument.
|
||||
async fn name<'b>(
|
||||
&'b self,
|
||||
#[graphql_interface(executor)] another: &Executor<'_, '_, (), S>,
|
||||
) -> &'b str
|
||||
where
|
||||
S: Send + Sync;
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue<__S>)]
|
||||
struct Human {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
#[graphql_interface(Scalar = S)]
|
||||
impl<S: ScalarValue> Character<S> for Human {
|
||||
async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
executor.look_ahead().field_name()
|
||||
}
|
||||
|
||||
async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Traits are maybe the most obvious concept you want to use when building
|
||||
interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
|
||||
have to manually specify how to convert a trait into a concrete type. This can
|
||||
|
@ -63,7 +327,7 @@ juniper::graphql_interface!(<'a> &'a dyn Character: () as "Character" where Scal
|
|||
# fn main() {}
|
||||
```
|
||||
|
||||
The `instance_resolvers` declaration lists all the implementors of the given
|
||||
The `instance_resolvers` declaration lists all the implementers of the given
|
||||
interface and how to resolve them.
|
||||
|
||||
As you can see, you lose a bit of the point with using traits: you need to list
|
||||
|
@ -213,3 +477,12 @@ juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
|
|||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
[2]: https://doc.rust-lang.org/reference/types/trait-object.html
|
||||
[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
|
||||
[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html
|
|
@ -10,8 +10,9 @@ example from the last chapter, this is how you would define `Person` using the
|
|||
macro:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# extern crate juniper;
|
||||
|
||||
#
|
||||
struct Person {
|
||||
name: String,
|
||||
age: i32,
|
||||
|
|
|
@ -16,7 +16,7 @@ Most of the time, we just need a trivial and straightforward Rust enum to repres
|
|||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate derive_more;
|
||||
# extern crate derive_more;
|
||||
use derive_more::From;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
|
@ -53,7 +53,7 @@ As an example, let's consider the situation where we need to bind some type para
|
|||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# #[macro_use] extern crate derive_more;
|
||||
# extern crate derive_more;
|
||||
# use std::marker::PhantomData;
|
||||
use derive_more::From;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
@ -88,6 +88,7 @@ enum Character<S> {
|
|||
If some custom logic is needed to resolve a [GraphQL union][1] variant, you may specify an external function to do so:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# extern crate juniper;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
|
@ -132,6 +133,7 @@ impl Character {
|
|||
With an external resolver function we can even declare a new [GraphQL union][1] variant where the Rust type is absent in the initial enum definition. The attribute syntax `#[graphql(on VariantType = resolver_fn)]` follows the [GraphQL syntax for dispatching union variants](https://spec.graphql.org/June2018/#example-f8163).
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# extern crate juniper;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
|
@ -289,6 +291,7 @@ impl Character for Droid {
|
|||
If a context is required in a trait method to resolve a [GraphQL union][1] variant, specify it as an argument.
|
||||
|
||||
```rust
|
||||
# #![allow(unused_variables)]
|
||||
# extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
use juniper::{graphql_union, GraphQLObject};
|
||||
|
@ -451,6 +454,7 @@ fn get_droid<'db>(ch: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droi
|
|||
By default, `#[derive(GraphQLUnion)]` and `#[graphql_union]` macros generate code, which is generic over a [`ScalarValue`][2] type. This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a concrete [`ScalarValue`][2] type in its implementation. To resolve such problem, a concrete [`ScalarValue`][2] type should be specified:
|
||||
|
||||
```rust
|
||||
# #![allow(dead_code)]
|
||||
# extern crate juniper;
|
||||
use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ juniper_subscriptions = { path = "../../../juniper_subscriptions" }
|
|||
|
||||
derive_more = "0.99.7"
|
||||
futures = "0.3"
|
||||
tokio = { version = "0.2", features = ["rt-core", "blocking", "stream", "rt-util"] }
|
||||
tokio = { version = "0.2", features = ["blocking", "macros", "rt-core", "rt-util", "stream"] }
|
||||
iron = "0.5.0"
|
||||
mount = "0.4.0"
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use actix_cors::Cors;
|
|||
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
|
||||
|
||||
use juniper::{
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Character as _, Database, Query},
|
||||
DefaultScalarValue, EmptyMutation, FieldError, RootNode,
|
||||
};
|
||||
use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler};
|
||||
|
@ -76,11 +76,11 @@ impl Subscription {
|
|||
))
|
||||
} else {
|
||||
let random_id = rng.gen_range(1000, 1005).to_string();
|
||||
let human = context.get_human(&random_id).unwrap();
|
||||
let human = context.get_human(&random_id).unwrap().clone();
|
||||
|
||||
Ok(RandomHuman {
|
||||
id: human.id().to_owned(),
|
||||
name: human.name().to_owned(),
|
||||
name: human.name().unwrap().to_owned(),
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {
|
||||
fn id(&self, __num: i32) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,19 @@
|
|||
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 GraphQL’s introspection system.
|
||||
--> $DIR/argument_double_underscored.rs:14:18
|
||||
|
|
||||
14 | fn id(&self, __num: i32) -> &str {
|
||||
| ^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Schema
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/argument_double_underscored.rs:4:18
|
||||
|
|
||||
4 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/argument_double_underscored.rs:10:6
|
||||
|
|
||||
10 | impl Character for ObjA {}
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -0,0 +1,21 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {
|
||||
fn id(&self, obj: Self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {
|
||||
fn id(&self, obj: ObjA) -> &str;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,16 @@
|
|||
error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied
|
||||
--> $DIR/argument_non_input_type.rs:16:1
|
||||
|
|
||||
16 | #[graphql_interface(for = ObjA)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA`
|
||||
|
|
||||
= note: required by `juniper::marker::IsInputType::mark`
|
||||
= note: this error originates in an attribute macro (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:16:1
|
||||
|
|
||||
16 | #[graphql_interface(for = ObjA)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA`
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,29 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {
|
||||
fn id(&self, _: i32) -> &str {
|
||||
"funA"
|
||||
}
|
||||
|
||||
fn as_obja(&self) -> Option<&ObjA> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
#[graphql_interface(on ObjA = downcast_obja)]
|
||||
trait Character {
|
||||
fn id(&self, num: i32) -> &str;
|
||||
|
||||
#[graphql_interface(downcast)]
|
||||
fn as_obja(&self) -> Option<&ObjA>;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,20 @@
|
|||
error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA`
|
||||
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:26:5
|
||||
|
|
||||
26 | fn as_obja(&self) -> Option<&ObjA>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
= note: use `#[graphql_interface(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:4:18
|
||||
|
|
||||
4 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/downcast_method_conflicts_with_external_downcast_fn.rs:10:6
|
||||
|
|
||||
10 | impl Character for ObjA {
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -0,0 +1,24 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[graphql_interface(for = Human)]
|
||||
trait Character {
|
||||
fn id(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[graphql_interface(downcast)]
|
||||
fn a(&self, ctx: &(), rand: u8) -> Option<&Human> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct Human {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for Human {}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,19 @@
|
|||
error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context`
|
||||
--> $DIR/downcast_method_wrong_input_args.rs:10:10
|
||||
|
|
||||
10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/downcast_method_wrong_input_args.rs:16:18
|
||||
|
|
||||
16 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/downcast_method_wrong_input_args.rs:22:6
|
||||
|
|
||||
22 | impl Character for Human {}
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -0,0 +1,24 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[graphql_interface(for = Human)]
|
||||
trait Character {
|
||||
fn id(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[graphql_interface(downcast)]
|
||||
fn a(&self, ctx: &(), rand: u8) -> &Human {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct Human {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for Human {}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,19 @@
|
|||
error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only
|
||||
--> $DIR/downcast_method_wrong_return_type.rs:10:40
|
||||
|
|
||||
10 | fn a(&self, ctx: &(), rand: u8) -> &Human {
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/downcast_method_wrong_return_type.rs:16:18
|
||||
|
|
||||
16 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/downcast_method_wrong_return_type.rs:22:6
|
||||
|
|
||||
22 | impl Character for Human {}
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -0,0 +1,19 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {
|
||||
fn __id(&self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,19 @@
|
|||
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 GraphQL’s introspection system.
|
||||
--> $DIR/field_double_underscored.rs:14:8
|
||||
|
|
||||
14 | fn __id(&self) -> &str {
|
||||
| ^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Schema
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/field_double_underscored.rs:4:18
|
||||
|
|
||||
4 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/field_double_underscored.rs:10:6
|
||||
|
|
||||
10 | impl Character for ObjA {}
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -0,0 +1,24 @@
|
|||
use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLInputObject)]
|
||||
pub struct ObjB {
|
||||
id: i32,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {
|
||||
fn id(&self) -> ObjB {
|
||||
ObjB { id: 34 }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,8 @@
|
|||
error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied
|
||||
--> $DIR/field_non_output_return_type.rs:17:1
|
||||
|
|
||||
17 | #[graphql_interface(for = ObjA)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB`
|
||||
|
|
||||
= note: required by `juniper::marker::IsOutputType::mark`
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,24 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {
|
||||
fn id(&self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
|
||||
#[graphql_interface(name = "id")]
|
||||
fn id2(&self) -> &str {
|
||||
"funB"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,25 @@
|
|||
error: GraphQL interface must have a different name for each field
|
||||
--> $DIR/fields_duplicate.rs:13:1
|
||||
|
|
||||
13 | / trait Character {
|
||||
14 | | fn id(&self) -> &str {
|
||||
15 | | "funA"
|
||||
16 | | }
|
||||
... |
|
||||
21 | | }
|
||||
22 | | }
|
||||
| |_^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/fields_duplicate.rs:4:18
|
||||
|
|
||||
4 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/fields_duplicate.rs:10:6
|
||||
|
|
||||
10 | impl Character for ObjA {}
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -1,23 +0,0 @@
|
|||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
enum Character {
|
||||
A(ObjA),
|
||||
}
|
||||
|
||||
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
field id(__test: ObjA) -> &str {
|
||||
match *self {
|
||||
Character::A(_) => "funA",
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {
|
||||
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||
}
|
||||
});
|
||||
|
||||
fn main() {}
|
|
@ -1,27 +0,0 @@
|
|||
error[E0277]: the trait bound `ObjA: FromInputValue` is not satisfied
|
||||
--> $DIR/impl_argument_no_object.rs:11:1
|
||||
|
|
||||
11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
12 | | field id(__test: ObjA) -> &str {
|
||||
13 | | match *self {
|
||||
14 | | Character::A(_) => "funA",
|
||||
... |
|
||||
20 | | }
|
||||
21 | | });
|
||||
| |___^ the trait `FromInputValue` is not implemented for `ObjA`
|
||||
|
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `ObjA: FromInputValue` is not satisfied
|
||||
--> $DIR/impl_argument_no_object.rs:11:1
|
||||
|
|
||||
11 | / juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
12 | | field id(__test: ObjA) -> &str {
|
||||
13 | | match *self {
|
||||
14 | | Character::A(_) => "funA",
|
||||
... |
|
||||
20 | | }
|
||||
21 | | });
|
||||
| |___^ the trait `FromInputValue` is not implemented for `ObjA`
|
||||
|
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -1,23 +0,0 @@
|
|||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
enum Character {
|
||||
A(ObjA),
|
||||
}
|
||||
|
||||
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
field id(__test: String) -> &str {
|
||||
match *self {
|
||||
Character::A(_) => "funA",
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {
|
||||
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||
}
|
||||
});
|
||||
|
||||
fn main() {}
|
|
@ -1,12 +0,0 @@
|
|||
enum Character {}
|
||||
|
||||
juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
|
||||
field id() -> &str {
|
||||
match *self {
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {}
|
||||
});
|
||||
|
||||
fn main() {}
|
|
@ -1,23 +0,0 @@
|
|||
#[derive(juniper::GraphQLInputObject)]
|
||||
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
enum Character {
|
||||
A(ObjA),
|
||||
}
|
||||
|
||||
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
field id() -> &str {
|
||||
match *self {
|
||||
Character::A(_) => "funA",
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {
|
||||
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||
}
|
||||
});
|
||||
|
||||
fn main() {}
|
|
@ -1,23 +0,0 @@
|
|||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
enum Character {
|
||||
A(ObjA),
|
||||
}
|
||||
|
||||
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
field __id() -> &str {
|
||||
match *self {
|
||||
Character::A(_) => "funA",
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {
|
||||
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||
}
|
||||
});
|
||||
|
||||
fn main() {}
|
|
@ -1,29 +0,0 @@
|
|||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(scalar = juniper::DefaultScalarValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
enum Character {
|
||||
A(ObjA),
|
||||
}
|
||||
|
||||
juniper::graphql_interface!(Character: () where Scalar = juniper::DefaultScalarValue |&self| {
|
||||
field id() -> &str {
|
||||
match *self {
|
||||
Character::A(_) => "funA",
|
||||
}
|
||||
}
|
||||
|
||||
field id() -> &str {
|
||||
match *self {
|
||||
Character::A(_) => "funA",
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| {
|
||||
&ObjA => match *self { Character::A(ref h) => Some(h) },
|
||||
}
|
||||
});
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,20 @@
|
|||
use juniper::{graphql_interface, GraphQLInputObject};
|
||||
|
||||
#[derive(GraphQLInputObject)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {
|
||||
fn id(&self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,17 @@
|
|||
error[E0277]: the trait bound `ObjA: GraphQLObjectType<__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`
|
||||
|
|
||||
= note: required by `juniper::marker::GraphQLObjectType::mark`
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied
|
||||
--> $DIR/implementer_non_object_type.rs:15:1
|
||||
|
|
||||
15 | #[graphql_interface(for = ObjA)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA`
|
||||
|
|
||||
= note: required by `juniper::marker::IsOutputType::mark`
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,21 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {
|
||||
fn id(&self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(for = [ObjA, ObjA])]
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,17 @@
|
|||
error: duplicated attribute argument found
|
||||
--> $DIR/implementers_duplicate_pretty.rs:16:34
|
||||
|
|
||||
16 | #[graphql_interface(for = [ObjA, ObjA])]
|
||||
| ^^^^
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/implementers_duplicate_pretty.rs:4:18
|
||||
|
|
||||
4 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/implementers_duplicate_pretty.rs:10:6
|
||||
|
|
||||
10 | impl Character for ObjA {
|
||||
| ^^^^^^^^^ not found in this scope
|
|
@ -0,0 +1,23 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
type ObjAlias = ObjA;
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {
|
||||
fn id(&self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(for = [ObjA, ObjAlias])]
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,21 @@
|
|||
error[E0119]: conflicting implementations of trait `<CharacterValue as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA`:
|
||||
--> $DIR/implementers_duplicate_ugly.rs:18:1
|
||||
|
|
||||
18 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| first implementation here
|
||||
| conflicting implementation for `ObjA`
|
||||
|
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0119]: conflicting implementations of trait `std::convert::From<ObjA>` for type `CharacterValue`:
|
||||
--> $DIR/implementers_duplicate_ugly.rs:18:1
|
||||
|
|
||||
18 | #[graphql_interface(for = [ObjA, ObjAlias])]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| first implementation here
|
||||
| conflicting implementation for `CharacterValue`
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -0,0 +1,10 @@
|
|||
use juniper::graphql_interface;
|
||||
|
||||
#[graphql_interface]
|
||||
trait __Character {
|
||||
fn id(&self) -> &str {
|
||||
"funA"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -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 GraphQL’s introspection system.
|
||||
--> $DIR/name_double_underscored.rs:4:7
|
||||
|
|
||||
4 | trait __Character {
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Schema
|
15
integration_tests/codegen_fail/fail/interface/no_fields.rs
Normal file
15
integration_tests/codegen_fail/fail/interface/no_fields.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(impl = CharacterValue)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface]
|
||||
impl Character for ObjA {}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
trait Character {}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,19 @@
|
|||
error: GraphQL interface must have at least one field
|
||||
--> $DIR/no_fields.rs:13:1
|
||||
|
|
||||
13 | trait Character {}
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
|
||||
error[E0412]: cannot find type `CharacterValue` in this scope
|
||||
--> $DIR/no_fields.rs:4:18
|
||||
|
|
||||
4 | #[graphql(impl = CharacterValue)]
|
||||
| ^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0405]: cannot find trait `Character` in this scope
|
||||
--> $DIR/no_fields.rs:10:6
|
||||
|
|
||||
10 | impl Character for ObjA {}
|
||||
| ^^^^^^^^^ not found in this scope
|
11
integration_tests/codegen_fail/fail/interface/wrong_item.rs
Normal file
11
integration_tests/codegen_fail/fail/interface/wrong_item.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use juniper::{graphql_interface, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
pub struct ObjA {
|
||||
test: String,
|
||||
}
|
||||
|
||||
#[graphql_interface(for = ObjA)]
|
||||
enum Character {}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,7 @@
|
|||
error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only
|
||||
--> $DIR/wrong_item.rs:8:1
|
||||
|
|
||||
8 | #[graphql_interface(for = ObjA)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
@ -1,4 +1,4 @@
|
|||
error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
|
||||
error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`:
|
||||
--> $DIR/enum_same_type_ugly.rs:3:10
|
||||
|
|
||||
3 | #[derive(GraphQLUnion)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: duplicated attribute
|
||||
error: duplicated attribute argument found
|
||||
--> $DIR/struct_same_type_pretty.rs:5:14
|
||||
|
|
||||
5 | #[graphql(on i32 = Character::b)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
|
||||
error[E0119]: conflicting implementations of trait `<Character as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`:
|
||||
--> $DIR/struct_same_type_ugly.rs:3:10
|
||||
|
|
||||
3 | #[derive(GraphQLUnion)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{{closure}}#0::MutuallyExclusive` for type `std::string::String`:
|
||||
error[E0119]: conflicting implementations of trait `<(dyn Character + std::marker::Send + std::marker::Sync + '__obj) as juniper::GraphQLUnion<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `std::string::String`:
|
||||
--> $DIR/trait_same_type_ugly.rs:3:1
|
||||
|
|
||||
3 | #[graphql_union]
|
||||
|
|
|
@ -10,6 +10,7 @@ futures = "0.3.1"
|
|||
juniper = { path = "../../juniper" }
|
||||
|
||||
[dev-dependencies]
|
||||
async-trait = "0.1.39"
|
||||
serde_json = { version = "1" }
|
||||
fnv = "1.0.3"
|
||||
tokio = { version = "0.2", features = ["rt-core", "time", "macros"] }
|
5259
integration_tests/juniper_tests/src/codegen/interface_attr.rs
Normal file
5259
integration_tests/juniper_tests/src/codegen/interface_attr.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,6 +4,7 @@ mod derive_object;
|
|||
mod derive_object_with_raw_idents;
|
||||
mod impl_object;
|
||||
mod impl_scalar;
|
||||
mod interface_attr;
|
||||
mod scalar_value_transparent;
|
||||
mod union_attr;
|
||||
mod union_derive;
|
||||
|
|
|
@ -305,7 +305,7 @@ mod generic {
|
|||
}
|
||||
}
|
||||
|
||||
mod description_from_doc_comments {
|
||||
mod description_from_doc_comment {
|
||||
use super::*;
|
||||
|
||||
/// Rust docs.
|
||||
|
@ -647,93 +647,6 @@ mod custom_scalar {
|
|||
}
|
||||
}
|
||||
|
||||
mod inferred_custom_context {
|
||||
use super::*;
|
||||
|
||||
#[graphql_union]
|
||||
trait Character {
|
||||
fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
|
||||
None
|
||||
}
|
||||
fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Character for HumanCustomContext {
|
||||
fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
|
||||
Some(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Character for DroidCustomContext {
|
||||
fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
|
||||
Some(&self)
|
||||
}
|
||||
}
|
||||
|
||||
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object(context = CustomContext)]
|
||||
impl QueryRoot {
|
||||
fn character(&self, ctx: &CustomContext) -> Box<DynCharacter<'_>> {
|
||||
let ch: Box<DynCharacter<'_>> = match ctx {
|
||||
CustomContext::Human => Box::new(HumanCustomContext {
|
||||
id: "human-32".to_string(),
|
||||
home_planet: "earth".to_string(),
|
||||
}),
|
||||
CustomContext::Droid => Box::new(DroidCustomContext {
|
||||
id: "droid-99".to_string(),
|
||||
primary_function: "run".to_string(),
|
||||
}),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
ch
|
||||
}
|
||||
}
|
||||
|
||||
const DOC: &str = r#"{
|
||||
character {
|
||||
... on HumanCustomContext {
|
||||
humanId: id
|
||||
homePlanet
|
||||
}
|
||||
... on DroidCustomContext {
|
||||
droidId: id
|
||||
primaryFunction
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_human() {
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &Variables::new(), &CustomContext::Human).await,
|
||||
Ok((
|
||||
graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_droid() {
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &Variables::new(), &CustomContext::Droid).await,
|
||||
Ok((
|
||||
graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod explicit_custom_context {
|
||||
use super::*;
|
||||
|
||||
|
@ -821,7 +734,94 @@ mod explicit_custom_context {
|
|||
}
|
||||
}
|
||||
|
||||
mod ignored_methods {
|
||||
mod inferred_custom_context {
|
||||
use super::*;
|
||||
|
||||
#[graphql_union]
|
||||
trait Character {
|
||||
fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
|
||||
None
|
||||
}
|
||||
fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Character for HumanCustomContext {
|
||||
fn as_human(&self, _: &CustomContext) -> Option<&HumanCustomContext> {
|
||||
Some(&self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Character for DroidCustomContext {
|
||||
fn as_droid(&self, _: &()) -> Option<&DroidCustomContext> {
|
||||
Some(&self)
|
||||
}
|
||||
}
|
||||
|
||||
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[graphql_object(context = CustomContext)]
|
||||
impl QueryRoot {
|
||||
fn character(&self, ctx: &CustomContext) -> Box<DynCharacter<'_>> {
|
||||
let ch: Box<DynCharacter<'_>> = match ctx {
|
||||
CustomContext::Human => Box::new(HumanCustomContext {
|
||||
id: "human-32".to_string(),
|
||||
home_planet: "earth".to_string(),
|
||||
}),
|
||||
CustomContext::Droid => Box::new(DroidCustomContext {
|
||||
id: "droid-99".to_string(),
|
||||
primary_function: "run".to_string(),
|
||||
}),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
ch
|
||||
}
|
||||
}
|
||||
|
||||
const DOC: &str = r#"{
|
||||
character {
|
||||
... on HumanCustomContext {
|
||||
humanId: id
|
||||
homePlanet
|
||||
}
|
||||
... on DroidCustomContext {
|
||||
droidId: id
|
||||
primaryFunction
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_human() {
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &Variables::new(), &CustomContext::Human).await,
|
||||
Ok((
|
||||
graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_droid() {
|
||||
let schema = schema(QueryRoot);
|
||||
|
||||
assert_eq!(
|
||||
execute(DOC, None, &schema, &Variables::new(), &CustomContext::Droid).await,
|
||||
Ok((
|
||||
graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod ignored_method {
|
||||
use super::*;
|
||||
|
||||
#[graphql_union]
|
||||
|
|
|
@ -18,43 +18,44 @@ edition = "2018"
|
|||
[badges]
|
||||
travis-ci = { repository = "graphql-rust/juniper" }
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
harness = false
|
||||
path = "benches/bench.rs"
|
||||
|
||||
[features]
|
||||
expose-test-schema = ["anyhow", "serde_json"]
|
||||
schema-language = ["graphql-parser-integration"]
|
||||
graphql-parser-integration = ["graphql-parser"]
|
||||
default = [
|
||||
"bson",
|
||||
"chrono",
|
||||
"schema-language",
|
||||
"url",
|
||||
"uuid",
|
||||
"schema-language",
|
||||
]
|
||||
expose-test-schema = ["anyhow", "serde_json"]
|
||||
graphql-parser-integration = ["graphql-parser"]
|
||||
scalar-naivetime = []
|
||||
schema-language = ["graphql-parser-integration"]
|
||||
|
||||
[dependencies]
|
||||
juniper_codegen = { version = "0.14.2", path = "../juniper_codegen" }
|
||||
|
||||
anyhow = { default-features = false, version = "1.0.32", optional = true }
|
||||
async-trait = "0.1.39"
|
||||
bson = { version = "1.0", optional = true }
|
||||
chrono = { default-features = false, version = "0.4", optional = true }
|
||||
fnv = "1.0.3"
|
||||
futures = { default-features = false, features = ["alloc"], version = "0.3.1" }
|
||||
futures-enum = "0.1.12"
|
||||
graphql-parser = { version = "0.3", optional = true }
|
||||
indexmap = { version = "1.0", features = ["serde-1"] }
|
||||
serde = { default-features = false, version = "1.0.8", features = ["derive"] }
|
||||
serde_json = { default-features = false, version = "1.0", optional = true }
|
||||
serde = { version = "1.0.8", features = ["derive"], default-features = false }
|
||||
serde_json = { version = "1.0.2", default-features = false, optional = true }
|
||||
static_assertions = "1.1"
|
||||
url = { version = "2.0", optional = true }
|
||||
uuid = { default-features = false, version = "0.8", optional = true }
|
||||
graphql-parser = { version = "0.3", optional = true }
|
||||
uuid = { version = "0.8", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bencher = "0.1.2"
|
||||
pretty_assertions = "0.6.1"
|
||||
serde_json = { version = "1.0.2" }
|
||||
tokio = { version = "0.2", features = ["macros", "rt-core", "time"] }
|
||||
pretty_assertions = "0.6.1"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
harness = false
|
||||
path = "benches/bench.rs"
|
||||
|
|
|
@ -1025,10 +1025,9 @@ where
|
|||
|
||||
let mut fragments = vec![];
|
||||
for def in document.iter() {
|
||||
match def {
|
||||
Definition::Fragment(f) => fragments.push(f),
|
||||
_ => (),
|
||||
};
|
||||
if let Definition::Fragment(f) = def {
|
||||
fragments.push(f)
|
||||
}
|
||||
}
|
||||
|
||||
let default_variable_values = operation.item.variable_definitions.as_ref().map(|defs| {
|
||||
|
|
|
@ -7,6 +7,7 @@ use self::input_object::{NamedPublic, NamedPublicWithDescription};
|
|||
|
||||
use crate::{
|
||||
executor::Variables,
|
||||
graphql_interface, graphql_object,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::{DefaultScalarValue, ParseScalarResult, ParseScalarValue, Value},
|
||||
|
@ -22,10 +23,6 @@ enum Sample {
|
|||
|
||||
struct Scalar(i32);
|
||||
|
||||
struct Interface;
|
||||
|
||||
struct Root;
|
||||
|
||||
#[crate::graphql_scalar(name = "SampleScalar")]
|
||||
impl GraphQLScalar for Scalar {
|
||||
fn resolve(&self) -> Value {
|
||||
|
@ -41,23 +38,19 @@ impl GraphQLScalar for Scalar {
|
|||
}
|
||||
}
|
||||
|
||||
graphql_interface!(Interface: () as "SampleInterface" |&self| {
|
||||
description: "A sample interface"
|
||||
|
||||
field sample_enum() -> Sample as "A sample field in the interface" {
|
||||
/// A sample interface
|
||||
#[graphql_interface(name = "SampleInterface", for = Root, scalar = DefaultScalarValue)]
|
||||
trait Interface {
|
||||
/// A sample field in the interface
|
||||
fn sample_enum(&self) -> Sample {
|
||||
Sample::One
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |&_| {
|
||||
Root => Some(Root),
|
||||
}
|
||||
});
|
||||
struct Root;
|
||||
|
||||
/// The root query object in the schema
|
||||
#[crate::graphql_object(
|
||||
interfaces = [&Interface]
|
||||
Scalar = crate::DefaultScalarValue,
|
||||
)]
|
||||
#[graphql_object(interfaces = InterfaceValue)]
|
||||
impl Root {
|
||||
fn sample_enum() -> Sample {
|
||||
Sample::One
|
||||
|
@ -65,7 +58,7 @@ impl Root {
|
|||
|
||||
#[graphql(arguments(
|
||||
first(description = "The first number",),
|
||||
second(description = "The second number", default = 123,),
|
||||
second(description = "The second number", default = 123),
|
||||
))]
|
||||
|
||||
/// A sample scalar field on the object
|
||||
|
@ -74,6 +67,9 @@ impl Root {
|
|||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl Interface for Root {}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execution() {
|
||||
let doc = r#"
|
||||
|
|
|
@ -119,7 +119,7 @@ extern crate bson;
|
|||
|
||||
// These are required by the code generated via the `juniper_codegen` macros.
|
||||
#[doc(hidden)]
|
||||
pub use {futures, static_assertions as sa};
|
||||
pub use {async_trait::async_trait, futures, static_assertions as sa};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use futures::future::{BoxFuture, LocalBoxFuture};
|
||||
|
@ -128,8 +128,8 @@ pub use futures::future::{BoxFuture, LocalBoxFuture};
|
|||
// This allows users to just depend on juniper and get the derive
|
||||
// functionality automatically.
|
||||
pub use juniper_codegen::{
|
||||
graphql_object, graphql_scalar, graphql_subscription, graphql_union, GraphQLEnum,
|
||||
GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
|
||||
graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union,
|
||||
GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
|
@ -175,15 +175,18 @@ pub use crate::{
|
|||
LookAheadSelection, LookAheadValue, OwnedExecutor, Registry, ValuesStream, Variables,
|
||||
},
|
||||
introspection::IntrospectionFormat,
|
||||
macros::subscription_helpers::{ExtractTypeFromStream, IntoFieldResult},
|
||||
macros::helper::{
|
||||
subscription::{ExtractTypeFromStream, IntoFieldResult},
|
||||
AsDynGraphQLValue,
|
||||
},
|
||||
schema::{
|
||||
meta,
|
||||
model::{RootNode, SchemaType},
|
||||
},
|
||||
types::{
|
||||
async_await::{GraphQLTypeAsync, GraphQLValueAsync},
|
||||
base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
|
||||
marker::{self, GraphQLUnion, IsOutputType},
|
||||
async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync},
|
||||
base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind},
|
||||
marker::{self, GraphQLInterface, GraphQLUnion},
|
||||
scalars::{EmptyMutation, EmptySubscription, ID},
|
||||
subscriptions::{
|
||||
ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
|
||||
|
|
|
@ -1,741 +0,0 @@
|
|||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __juniper_impl_trait {
|
||||
(
|
||||
impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty {
|
||||
$($body:tt)+
|
||||
}
|
||||
) => {
|
||||
impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name {
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
(
|
||||
impl< < DefaultScalarValue > $(, $other: tt)* > $impl_trait:tt for $name:ty
|
||||
where ( $($where:tt)* )
|
||||
{
|
||||
$($body:tt)+
|
||||
}
|
||||
) => {
|
||||
impl<$($other,)*> $crate::$impl_trait<$crate::DefaultScalarValue> for $name
|
||||
where $($where)*
|
||||
{
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty {
|
||||
$($body:tt)*
|
||||
}
|
||||
) => {
|
||||
impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name
|
||||
where
|
||||
$generic: $crate::ScalarValue,
|
||||
{
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
(
|
||||
impl< <$generic:tt $(: $bound: tt)*> $(, $other: tt)* > $impl_trait:tt for $name:ty
|
||||
where ( $($where:tt)* )
|
||||
{
|
||||
$($body:tt)*
|
||||
}
|
||||
) => {
|
||||
impl<$($other,)* $generic $(: $bound)*> $crate::$impl_trait<$generic> for $name
|
||||
where
|
||||
$($where)*
|
||||
$generic: $crate::ScalarValue,
|
||||
{
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty {
|
||||
$($body:tt)*
|
||||
}
|
||||
) => {
|
||||
impl<$($other, )*> $crate::$impl_trait<$scalar> for $name {
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
(
|
||||
impl<$scalar:ty $(, $other: tt )*> $impl_trait:tt for $name:ty
|
||||
where ( $($where:tt)* )
|
||||
{
|
||||
$($body:tt)*
|
||||
}
|
||||
) => {
|
||||
impl<$($other, )*> $crate::$impl_trait<$scalar> for $name
|
||||
where $($where)*
|
||||
{
|
||||
$($body)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __juniper_insert_generic {
|
||||
(<DefaultScalarValue>) => {
|
||||
$crate::DefaultScalarValue
|
||||
};
|
||||
(
|
||||
<$generic:tt $(: $bound: tt)*>
|
||||
) => {
|
||||
$generic
|
||||
};
|
||||
(
|
||||
$scalar: ty
|
||||
) => {
|
||||
$scalar
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __juniper_parse_object_header {
|
||||
(
|
||||
callback = $callback:ident,
|
||||
rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt
|
||||
where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {$outname},
|
||||
scalar = {<$generic $(: $bound)*>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback:ident,
|
||||
rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt
|
||||
where Scalar = $scalar: ty $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {$outname},
|
||||
scalar = {$scalar},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* as $outname: tt $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {$outname},
|
||||
scalar = {<DefaultScalarValue>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $name: ty $(: $ctxt: ty)* as $outname: tt
|
||||
where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {$outname},
|
||||
scalar = {<$generic $(:$bound)*>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $name: ty $(: $ctxt: ty)* as $outname: tt
|
||||
where Scalar = $scalar: ty $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {$outname},
|
||||
scalar = {$scalar},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $name: ty $(: $ctxt: ty)* as $outname: tt $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {$outname},
|
||||
scalar = {<DefaultScalarValue>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)*
|
||||
where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {stringify!($name)},
|
||||
scalar = {<$generic $(:$bounds)*>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)*
|
||||
where Scalar = $scalar: ty $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {stringify!($name)},
|
||||
scalar = {$scalar},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = <$($lifetime:tt),*> $name: ty $(: $ctxt: ty)* $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {stringify!($name)},
|
||||
scalar = {<DefaultScalarValue>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $name: ty $(: $ctxt: ty)*
|
||||
where Scalar = <$generic:tt $(: $bound:tt)*> $(| &$mainself:ident |)*
|
||||
{
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {stringify!($name)},
|
||||
scalar = {<$generic $(: $bound)*>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $name: ty $(: $ctxt: ty)* where Scalar = $scalar: ty $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {stringify!($name)},
|
||||
scalar = {$scalar},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $name: ty $(: $ctxt: ty)* $(| &$mainself:ident |)* {
|
||||
$($items: tt)*
|
||||
}
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
@parse,
|
||||
meta = {
|
||||
lifetimes = [],
|
||||
name = $name,
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
outname = {stringify!($name)},
|
||||
scalar = {<DefaultScalarValue>},
|
||||
},
|
||||
rest = $($items)*
|
||||
);
|
||||
};
|
||||
(
|
||||
callback = $callback: ident,
|
||||
rest = $($rest:tt)*
|
||||
) => {
|
||||
compile_error!("Invalid syntax");
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __juniper_parse_field_list {
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest =
|
||||
) => {
|
||||
$crate::$success_callback!(
|
||||
@generate,
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)*],
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = , $($rest: tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)*],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
(
|
||||
@parse_description,
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {
|
||||
$(lifetimes = [$($lifetime:tt,)*],)*
|
||||
$(name = $name:ty,)*
|
||||
$(ctx = $ctxt: ty,)*
|
||||
$(main_self = $mainself: ident,)*
|
||||
$(outname = {$($outname:tt)*},)*
|
||||
$(scalar = {$($scalar:tt)*},)*
|
||||
$(description = $_desciption: tt,)*
|
||||
$(additional = {$($other: tt)*},)*
|
||||
},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = $desc: tt $($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {
|
||||
$(lifetimes = [$($lifetime,)*],)*
|
||||
$(name = $name,)*
|
||||
$(ctx = $ctxt,)*
|
||||
$(main_self = $mainself,)*
|
||||
$(outname = {$($outname)*},)*
|
||||
$(scalar = {$($scalar)*},)*
|
||||
description = $desc,
|
||||
$(additional = {$($other)*},)*
|
||||
|
||||
},
|
||||
items = [$({$($items)*},)*],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = { $($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = description: $($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
@parse_description,
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)*],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = $(#[doc = $desc: tt])*
|
||||
#[deprecated $(( $(since = $since: tt,)* note = $reason: tt ))* ]
|
||||
field $name: ident (
|
||||
$(&$executor: tt)* $(,)*
|
||||
$($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)*
|
||||
) -> $return_ty: ty $body: block
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)* {
|
||||
name = $name,
|
||||
body = $body,
|
||||
return_ty = $return_ty,
|
||||
args = [
|
||||
$({
|
||||
arg_name = $arg_name,
|
||||
arg_ty = $arg_ty,
|
||||
$(arg_default = $arg_default,)*
|
||||
$(arg_docstring = $arg_desc,)*
|
||||
},)*
|
||||
],
|
||||
$(docstring = $desc,)*
|
||||
deprecated = None$(.unwrap_or_else(|| Some($reason)))*,
|
||||
$(executor_var = $executor,)*
|
||||
},],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = $(#[doc = $desc: tt])*
|
||||
field $name: ident (
|
||||
$(&$executor: ident)* $(,)*
|
||||
$($(#[doc = $arg_desc: expr])* $arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty),* $(,)*
|
||||
) -> $return_ty: ty $body: block
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)* {
|
||||
name = $name,
|
||||
body = $body,
|
||||
return_ty = $return_ty,
|
||||
args = [
|
||||
$({
|
||||
arg_name = $arg_name,
|
||||
arg_ty = $arg_ty,
|
||||
$(arg_default = $arg_default,)*
|
||||
$(arg_docstring = $arg_desc,)*
|
||||
},)*
|
||||
],
|
||||
$(docstring = $desc,)*
|
||||
$(executor_var = $executor,)*
|
||||
},],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = field deprecated $reason:tt $name: ident (
|
||||
$(&$executor: tt)* $(,)*
|
||||
$($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
|
||||
) -> $return_ty: ty $(as $desc: tt)* $body: block
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)* {
|
||||
name = $name,
|
||||
body = $body,
|
||||
return_ty = $return_ty,
|
||||
args = [
|
||||
$({
|
||||
arg_name = $arg_name,
|
||||
arg_ty = $arg_ty,
|
||||
$(arg_default = $arg_default,)*
|
||||
$(arg_description = $arg_desc,)*
|
||||
},)*
|
||||
],
|
||||
$(decs = $desc,)*
|
||||
deprecated = Some($reason),
|
||||
$(executor_var = $executor,)*
|
||||
},],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = field $name: ident (
|
||||
$(&$executor: ident)* $(,)*
|
||||
$($arg_name:ident $(= $arg_default: tt)* : $arg_ty: ty $(as $arg_desc: expr)*),* $(,)*
|
||||
) -> $return_ty: ty $(as $desc: tt)* $body: block
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)* {
|
||||
name = $name,
|
||||
body = $body,
|
||||
return_ty = $return_ty,
|
||||
args = [
|
||||
$({
|
||||
arg_name = $arg_name,
|
||||
arg_ty = $arg_ty,
|
||||
$(arg_default = $arg_default,)*
|
||||
$(arg_description = $arg_desc,)*
|
||||
},)*
|
||||
],
|
||||
$(decs = $desc,)*
|
||||
$(executor_var = $executor,)*
|
||||
},],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {
|
||||
callback = $callback: ident,
|
||||
header = {$($header:tt)*},
|
||||
},
|
||||
meta = {$($meta:tt)*},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = $($rest:tt)*
|
||||
) => {
|
||||
$crate::$callback!(
|
||||
$($header)*
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {
|
||||
callback = $callback,
|
||||
header = {$($header)*},
|
||||
},
|
||||
meta = {$($meta)*},
|
||||
items = [$({$($items)*},)*],
|
||||
rest = $($rest)*
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __juniper_parse_instance_resolver {
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {
|
||||
lifetimes = [$($lifetime:tt,)*],
|
||||
name = $name:ty,
|
||||
ctx = $ctxt:ty,
|
||||
main_self = $mainself:ident,
|
||||
outname = {$($outname:tt)*},
|
||||
scalar = {$($scalar:tt)*},
|
||||
$(description = $desciption:tt,)*
|
||||
$(additional = {
|
||||
$(resolver = {$($ignored_resolver:tt)*},)*
|
||||
},)*
|
||||
|
||||
},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = instance_resolvers: |&$context: ident| {
|
||||
$( $srctype:ty => $resolver:expr ),* $(,)*
|
||||
} $($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
ctx = $ctxt,
|
||||
main_self = $mainself,
|
||||
outname = {$($outname)*},
|
||||
scalar = {$($scalar)*},
|
||||
$(description = $desciption,)*
|
||||
additional = {
|
||||
resolver = {
|
||||
context = $context,
|
||||
items = [
|
||||
$({
|
||||
src = $srctype,
|
||||
resolver = $resolver,
|
||||
},)*
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
items = [$({$($items)*},)*],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
success_callback = $success_callback: ident,
|
||||
additional_parser = {$($additional:tt)*},
|
||||
meta = {
|
||||
lifetimes = [$($lifetime:tt,)*],
|
||||
name = $name:ty,
|
||||
ctx = $ctxt:ty,
|
||||
main_self = $mainself:ident,
|
||||
outname = {$($outname:tt)*},
|
||||
scalar = {$($scalar:tt)*},
|
||||
$(description = $desciption:tt,)*
|
||||
$(additional = {
|
||||
$(resolver = {$($ignored_resolver:tt)*},)*
|
||||
},)*
|
||||
|
||||
},
|
||||
items = [$({$($items: tt)*},)*],
|
||||
rest = instance_resolvers: |$(&)* _| {$( $srctype:ty => $resolver:expr ),* $(,)*} $($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = $success_callback,
|
||||
additional_parser = {$($additional)*},
|
||||
meta = {
|
||||
lifetimes = [$($lifetime,)*],
|
||||
name = $name,
|
||||
ctx = $ctxt,
|
||||
main_self = $mainself,
|
||||
outname = {$($outname)*},
|
||||
scalar = {$($scalar)*},
|
||||
$(description = $desciption,)*
|
||||
additional = {
|
||||
resolver = {
|
||||
items = [
|
||||
$({
|
||||
src = $srctype,
|
||||
resolver = $resolver,
|
||||
},)*
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
items = [$({$($items)*},)*],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! __juniper_create_arg {
|
||||
(
|
||||
registry = $reg: ident,
|
||||
info = $info: ident,
|
||||
arg_ty = $arg_ty: ty,
|
||||
arg_name = $arg_name: ident,
|
||||
$(description = $arg_description: expr,)*
|
||||
$(docstring = $arg_docstring: expr,)*
|
||||
) => {
|
||||
$reg.arg::<$arg_ty>(
|
||||
&$crate::to_camel_case(stringify!($arg_name)),
|
||||
$info,
|
||||
)
|
||||
$(.description($arg_description))*
|
||||
.push_docstring(&[$($arg_docstring,)*])
|
||||
};
|
||||
|
||||
(
|
||||
registry = $reg: ident,
|
||||
info = $info: ident,
|
||||
arg_ty = $arg_ty: ty,
|
||||
arg_name = $arg_name: ident,
|
||||
default = $arg_default: expr,
|
||||
$(description = $arg_description: expr,)*
|
||||
$(docstring = $arg_docstring: expr,)*
|
||||
) => {
|
||||
$reg.arg_with_default::<$arg_ty>(
|
||||
&$crate::to_camel_case(stringify!($arg_name)),
|
||||
&($arg_default),
|
||||
$info,
|
||||
)
|
||||
$(.description($arg_description))*
|
||||
.push_docstring(&[$($arg_docstring,)*])
|
||||
};
|
||||
}
|
34
juniper/src/macros/helper/mod.rs
Normal file
34
juniper/src/macros/helper/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
//! Helper traits and definitions for macros.
|
||||
|
||||
pub mod subscription;
|
||||
|
||||
use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ScalarValue};
|
||||
|
||||
/// Conversion of a [`GraphQLValue`] to its [trait object][1].
|
||||
///
|
||||
/// [`GraphQLValue`]: crate::GraphQLValue
|
||||
/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
|
||||
pub trait AsDynGraphQLValue<S: ScalarValue = DefaultScalarValue> {
|
||||
/// Context type of this [`GraphQLValue`].
|
||||
///
|
||||
/// [`GraphQLValue`]: crate::GraphQLValue
|
||||
type Context;
|
||||
|
||||
/// Schema information type of this [`GraphQLValue`].
|
||||
///
|
||||
/// [`GraphQLValue`]: crate::GraphQLValue
|
||||
type TypeInfo;
|
||||
|
||||
/// Converts this value to a [`DynGraphQLValue`] [trait object][1].
|
||||
///
|
||||
/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
|
||||
fn as_dyn_graphql_value(&self) -> &DynGraphQLValue<S, Self::Context, Self::TypeInfo>;
|
||||
|
||||
/// Converts this value to a [`DynGraphQLValueAsync`] [trait object][1].
|
||||
///
|
||||
/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
|
||||
fn as_dyn_graphql_value_async(&self)
|
||||
-> &DynGraphQLValueAsync<S, Self::Context, Self::TypeInfo>;
|
||||
}
|
||||
|
||||
crate::sa::assert_obj_safe!(AsDynGraphQLValue<Context = (), TypeInfo = ()>);
|
|
@ -1,302 +0,0 @@
|
|||
/**
|
||||
Expose GraphQL interfaces
|
||||
|
||||
Mapping interfaces to GraphQL can be tricky: there is no direct counterpart to
|
||||
GraphQL interfaces in Rust, and downcasting is not possible in the general case.
|
||||
Many other GraphQL implementations in other languages use instance checks and
|
||||
either dynamic typing or forced downcasts to support these features.
|
||||
|
||||
A GraphQL interface defines fields that the implementing types also need to
|
||||
implement. A GraphQL interface also needs to be able to determine the concrete
|
||||
type name as well as downcast the general type to the actual concrete type.
|
||||
|
||||
## Syntax
|
||||
|
||||
See the documentation for [`graphql_object!`][1] on the general item and type
|
||||
syntax. `graphql_interface!` requires an additional `instance_resolvers` item,
|
||||
and does _not_ support the `interfaces` item.
|
||||
|
||||
`instance_resolvers` is a match like structure used to resolve the concrete
|
||||
instance type of the interface. It starts with a context argument and continues
|
||||
with a number of match arms; on the left side is the indicated type, and on the
|
||||
right an expression that resolve into `Option<T>` of the type indicated:
|
||||
|
||||
```rust,ignore
|
||||
instance_resolvers: |&context| {
|
||||
&Human => context.get_human(self.id()), // returns Option<&Human>
|
||||
&Droid => context.get_droid(self.id()), // returns Option<&Droid>
|
||||
},
|
||||
```
|
||||
|
||||
This is used for both the `__typename` field and when resolving a specialized
|
||||
fragment, e.g. `...on Human`. For `__typename`, the resolvers will be executed
|
||||
in order - the first one returning `Some` will be the determined type name. When
|
||||
resolving fragment type conditions, only the corresponding match arm will be
|
||||
executed.
|
||||
|
||||
## Example
|
||||
|
||||
A simplified extract from the StarWars schema example shows how to use the
|
||||
shared context to implement downcasts.
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
# use std::collections::HashMap;
|
||||
struct Human { id: String }
|
||||
struct Droid { id: String }
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
trait Character {
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str { &self.id }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn id(&self) -> &str { &self.id }
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(Context = Database)]
|
||||
impl Human {
|
||||
fn id(&self) -> &str { &self.id }
|
||||
}
|
||||
|
||||
#[juniper::graphql_object(
|
||||
name = "Droid",
|
||||
Context = Database,
|
||||
)]
|
||||
impl Droid {
|
||||
fn id(&self) -> &str { &self.id }
|
||||
}
|
||||
|
||||
// You can introduce lifetimes or generic parameters by < > before the name.
|
||||
juniper::graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| {
|
||||
field id() -> &str { self.id() }
|
||||
|
||||
instance_resolvers: |&context| {
|
||||
&Human => context.humans.get(self.id()),
|
||||
&Droid => context.droids.get(self.id()),
|
||||
}
|
||||
});
|
||||
|
||||
# fn main() { }
|
||||
```
|
||||
|
||||
[1]: macro.graphql_object!.html
|
||||
|
||||
*/
|
||||
#[macro_export]
|
||||
macro_rules! graphql_interface {
|
||||
|
||||
(
|
||||
@generate,
|
||||
meta = {
|
||||
lifetimes = [$($lifetimes:tt,)*],
|
||||
name = $name:ty,
|
||||
ctx = $ctx:ty,
|
||||
main_self = $main_self:ident,
|
||||
outname = {$($outname:tt)*},
|
||||
scalar = {$($scalar:tt)*},
|
||||
$(description = $desciption:tt,)*
|
||||
additional = {
|
||||
resolver = {
|
||||
$(context = $resolver_ctx: ident,)*
|
||||
items = [
|
||||
$({
|
||||
src = $resolver_src: ty,
|
||||
resolver = $resolver_expr: expr,
|
||||
},)*
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
items = [$({
|
||||
name = $fn_name: ident,
|
||||
body = $body: block,
|
||||
return_ty = $return_ty: ty,
|
||||
args = [$({
|
||||
arg_name = $arg_name : ident,
|
||||
arg_ty = $arg_ty: ty,
|
||||
$(arg_default = $arg_default: expr,)*
|
||||
$(arg_description = $arg_description: expr,)*
|
||||
$(arg_docstring = $arg_docstring: expr,)*
|
||||
},)*],
|
||||
$(decs = $fn_description: expr,)*
|
||||
$(docstring = $docstring: expr,)*
|
||||
$(deprecated = $deprecated: expr,)*
|
||||
$(executor_var = $executor: ident,)*
|
||||
},)*],
|
||||
) => {
|
||||
$crate::__juniper_impl_trait!(
|
||||
impl<$($scalar)* $(, $lifetimes)* > GraphQLType for $name {
|
||||
fn name(_ : &Self::TypeInfo) -> Option<&'static str> {
|
||||
Some($($outname)*)
|
||||
}
|
||||
|
||||
fn meta<'r>(
|
||||
info: &Self::TypeInfo,
|
||||
registry: &mut $crate::Registry<'r, $crate::__juniper_insert_generic!($($scalar)+)>
|
||||
) -> $crate::meta::MetaType<'r, $crate::__juniper_insert_generic!($($scalar)+)>
|
||||
where
|
||||
$crate::__juniper_insert_generic!($($scalar)+): 'r
|
||||
{
|
||||
// Ensure all child types are registered
|
||||
$(
|
||||
let _ = registry.get_type::<$resolver_src>(info);
|
||||
)*
|
||||
let fields = &[$(
|
||||
registry.field_convert::<$return_ty, _, Self::Context>(
|
||||
&$crate::to_camel_case(stringify!($fn_name)),
|
||||
info
|
||||
)
|
||||
$(.description($fn_description))*
|
||||
.push_docstring(&[$($docstring,)*])
|
||||
$(.deprecated($deprecated))*
|
||||
$(.argument(
|
||||
$crate::__juniper_create_arg!(
|
||||
registry = registry,
|
||||
info = info,
|
||||
arg_ty = $arg_ty,
|
||||
arg_name = $arg_name,
|
||||
$(default = $arg_default,)*
|
||||
$(description = $arg_description,)*
|
||||
$(docstring = $arg_docstring,)*
|
||||
)
|
||||
))*,
|
||||
)*];
|
||||
registry.build_interface_type::<$name>(
|
||||
info, fields
|
||||
)
|
||||
$(.description($desciption))*
|
||||
.into_meta()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$crate::__juniper_impl_trait!(
|
||||
impl<$($scalar)* $(, $lifetimes)* > IsOutputType for $name { }
|
||||
);
|
||||
|
||||
$crate::__juniper_impl_trait!(
|
||||
impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name {
|
||||
type Context = $ctx;
|
||||
type TypeInfo = ();
|
||||
|
||||
fn type_name(&self, _ : &Self::TypeInfo) -> Option<&'static str> {
|
||||
Some($($outname)*)
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn resolve_field(
|
||||
&$main_self,
|
||||
info: &Self::TypeInfo,
|
||||
field: &str,
|
||||
args: &$crate::Arguments<$crate::__juniper_insert_generic!($($scalar)+)>,
|
||||
executor: &$crate::Executor<Self::Context, $crate::__juniper_insert_generic!($($scalar)+)>
|
||||
) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)+)> {
|
||||
$(
|
||||
if field == &$crate::to_camel_case(stringify!($fn_name)) {
|
||||
let f = (|| {
|
||||
$(
|
||||
let $arg_name: $arg_ty = args.get(&$crate::to_camel_case(stringify!($arg_name)))
|
||||
.expect(concat!(
|
||||
"Argument ",
|
||||
stringify!($arg_name),
|
||||
" missing - validation must have failed"
|
||||
));
|
||||
)*
|
||||
$(
|
||||
let $executor = &executor;
|
||||
)*
|
||||
$body
|
||||
});
|
||||
let result: $return_ty = f();
|
||||
|
||||
return $crate::IntoResolvable::into(result, executor.context())
|
||||
.and_then(|res| {
|
||||
match res {
|
||||
Some((ctx, r)) => {
|
||||
executor.replaced_context(ctx)
|
||||
.resolve_with_ctx(&(), &r)
|
||||
}
|
||||
None => Ok($crate::Value::null())
|
||||
}
|
||||
});
|
||||
}
|
||||
)*
|
||||
|
||||
panic!("Field {} not found on type {}", field, $($outname)*)
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn concrete_type_name(&$main_self, context: &Self::Context, _info: &Self::TypeInfo) -> String {
|
||||
$(let $resolver_ctx = &context;)*
|
||||
|
||||
$(
|
||||
if ($resolver_expr as ::std::option::Option<$resolver_src>).is_some() {
|
||||
return
|
||||
<$resolver_src as $crate::GraphQLType<_>>::name(&()).unwrap().to_owned();
|
||||
}
|
||||
)*
|
||||
|
||||
panic!("Concrete type not handled by instance resolvers on {}", $($outname)*);
|
||||
}
|
||||
|
||||
fn resolve_into_type(
|
||||
&$main_self,
|
||||
_info: &Self::TypeInfo,
|
||||
type_name: &str,
|
||||
_: Option<&[$crate::Selection<$crate::__juniper_insert_generic!($($scalar)*)>]>,
|
||||
executor: &$crate::Executor<Self::Context, $crate::__juniper_insert_generic!($($scalar)*)>,
|
||||
) -> $crate::ExecutionResult<$crate::__juniper_insert_generic!($($scalar)*)> {
|
||||
$(let $resolver_ctx = &executor.context();)*
|
||||
|
||||
$(
|
||||
if type_name == (<$resolver_src as $crate::GraphQLType<_>>::name(&())).unwrap() {
|
||||
return executor.resolve(&(), &$resolver_expr);
|
||||
}
|
||||
)*
|
||||
|
||||
panic!("Concrete type not handled by instance resolvers on {}", $($outname)*);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
@parse,
|
||||
meta = {$($meta:tt)*},
|
||||
rest = $($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_field_list!(
|
||||
success_callback = graphql_interface,
|
||||
additional_parser = {
|
||||
callback = __juniper_parse_instance_resolver,
|
||||
header = {},
|
||||
},
|
||||
meta = {$($meta)*},
|
||||
items = [],
|
||||
rest = $($rest)*
|
||||
);
|
||||
};
|
||||
|
||||
(@$($stuff:tt)*) => {
|
||||
compile_error!("Invalid syntax for `graphql_interface!`");
|
||||
};
|
||||
|
||||
(
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
$crate::__juniper_parse_object_header!(
|
||||
callback = graphql_interface,
|
||||
rest = $($rest)*
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,12 +1,6 @@
|
|||
// Wrapper macros which allows built-in macros to be recognized as "crate-local"
|
||||
// and helper traits for #[juniper::graphql_subscription] macro.
|
||||
//! Helper definitions for macros.
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
#[macro_use]
|
||||
mod interface;
|
||||
pub mod helper;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod subscription_helpers;
|
||||
|
|
|
@ -1,15 +1,3 @@
|
|||
use crate::{
|
||||
ast::InputValue,
|
||||
executor::FieldResult,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::{DefaultScalarValue, Object, Value},
|
||||
};
|
||||
|
||||
struct Interface;
|
||||
#[derive(Debug)]
|
||||
struct Root;
|
||||
|
||||
/*
|
||||
|
||||
Syntax to validate:
|
||||
|
@ -22,9 +10,21 @@ Syntax to validate:
|
|||
|
||||
*/
|
||||
|
||||
#[crate::graphql_object(
|
||||
interfaces = [&Interface],
|
||||
)]
|
||||
#![allow(deprecated)]
|
||||
|
||||
use crate::{
|
||||
ast::InputValue,
|
||||
executor::FieldResult,
|
||||
graphql_interface, graphql_object,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::{DefaultScalarValue, Object, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Root;
|
||||
|
||||
#[graphql_object(interfaces = [InterfaceValue])]
|
||||
impl Root {
|
||||
fn simple() -> i32 {
|
||||
0
|
||||
|
@ -104,44 +104,85 @@ impl Root {
|
|||
}
|
||||
}
|
||||
|
||||
graphql_interface!(Interface: () |&self| {
|
||||
field simple() -> i32 { 0 }
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl Interface for Root {
|
||||
fn simple(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
field description() -> i32 as "Field description" { 0 }
|
||||
fn description(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
field deprecated "Deprecation reason"
|
||||
deprecated() -> i32 { 0 }
|
||||
fn deprecated(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
field deprecated "Deprecation reason"
|
||||
deprecated_descr() -> i32 as "Field description" { 0 }
|
||||
fn deprecated_descr(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn attr_description(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn attr_description_collapse(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn attr_description_long(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn attr_deprecated(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn attr_deprecated_reason(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn attr_deprecated_descr(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(for = Root, scalar = DefaultScalarValue)]
|
||||
trait Interface {
|
||||
fn simple(&self) -> i32;
|
||||
|
||||
#[graphql_interface(desc = "Field description")]
|
||||
fn description(&self) -> i32;
|
||||
|
||||
#[graphql_interface(deprecated = "Deprecation reason")]
|
||||
fn deprecated(&self) -> i32;
|
||||
|
||||
#[graphql_interface(desc = "Field description", deprecated = "Deprecation reason")]
|
||||
fn deprecated_descr(&self) -> i32;
|
||||
|
||||
/// Field description
|
||||
field attr_description() -> i32 { 0 }
|
||||
fn attr_description(&self) -> i32;
|
||||
|
||||
/// Field description
|
||||
/// with `collapse_docs` behavior
|
||||
field attr_description_collapse() -> i32 { 0 }
|
||||
fn attr_description_collapse(&self) -> i32;
|
||||
|
||||
/// Get the i32 representation of 0.
|
||||
///
|
||||
/// - This comment is longer.
|
||||
/// - These two lines are rendered as bullets by GraphiQL.
|
||||
field attr_description_long() -> i32 { 0 }
|
||||
fn attr_description_long(&self) -> i32;
|
||||
|
||||
#[deprecated]
|
||||
field attr_deprecated() -> i32 { 0 }
|
||||
fn attr_deprecated(&self) -> i32;
|
||||
|
||||
#[deprecated(note = "Deprecation reason")]
|
||||
field attr_deprecated_reason() -> i32 { 0 }
|
||||
fn attr_deprecated_reason(&self) -> i32;
|
||||
|
||||
/// Field description
|
||||
#[deprecated(note = "Deprecation reason")]
|
||||
field attr_deprecated_descr() -> i32 { 0 }
|
||||
|
||||
instance_resolvers: |&_| {
|
||||
Root => Some(Root {}),
|
||||
}
|
||||
});
|
||||
fn attr_deprecated_descr(&self) -> i32;
|
||||
}
|
||||
|
||||
async fn run_field_info_query<F>(type_name: &str, field_name: &str, f: F)
|
||||
where
|
||||
|
|
|
@ -17,7 +17,7 @@ struct WithLifetime<'a> {
|
|||
value: &'a str,
|
||||
}
|
||||
|
||||
#[crate::graphql_object(Context=Context)]
|
||||
#[crate::graphql_object(Context = Context)]
|
||||
impl<'a> WithLifetime<'a> {
|
||||
fn value(&'a self) -> &'a str {
|
||||
self.value
|
||||
|
@ -26,7 +26,7 @@ impl<'a> WithLifetime<'a> {
|
|||
|
||||
struct WithContext;
|
||||
|
||||
#[crate::graphql_object(Context=Context)]
|
||||
#[crate::graphql_object(Context = Context)]
|
||||
impl WithContext {
|
||||
fn ctx(ctx: &Context) -> bool {
|
||||
ctx.flag1
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
ast::InputValue,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::{DefaultScalarValue, Object, Value},
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Syntax to validate:
|
||||
|
@ -19,135 +10,86 @@ Syntax to validate:
|
|||
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
ast::InputValue,
|
||||
graphql_interface, graphql_object,
|
||||
schema::model::RootNode,
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::{DefaultScalarValue, Object, Value},
|
||||
};
|
||||
|
||||
struct Concrete;
|
||||
|
||||
struct CustomName;
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct WithLifetime<'a> {
|
||||
data: PhantomData<&'a i32>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct WithGenerics<T> {
|
||||
data: T,
|
||||
}
|
||||
|
||||
struct DescriptionFirst;
|
||||
struct FieldsFirst;
|
||||
struct InterfacesFirst;
|
||||
|
||||
struct CommasWithTrailing;
|
||||
struct CommasOnMeta;
|
||||
|
||||
struct ResolversWithTrailingComma;
|
||||
|
||||
struct Root;
|
||||
|
||||
#[crate::graphql_object]
|
||||
#[graphql_object(impl = [
|
||||
CustomNameValue, DescriptionValue, WithLifetimeValue<'_>, WithGenericsValue<()>,
|
||||
])]
|
||||
impl Concrete {
|
||||
fn simple() -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
graphql_interface!(CustomName: () as "ACustomNamedInterface" |&self| {
|
||||
field simple() -> i32 { 0 }
|
||||
#[graphql_interface(for = Concrete, name = "ACustomNamedInterface", scalar = DefaultScalarValue)]
|
||||
trait CustomName {
|
||||
fn simple(&self) -> i32;
|
||||
}
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl CustomName for Concrete {
|
||||
fn simple(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
});
|
||||
#[graphql_interface(for = Concrete, scalar = DefaultScalarValue)]
|
||||
trait WithLifetime<'a> {
|
||||
fn simple(&self) -> i32;
|
||||
}
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl<'a> WithLifetime<'a> for Concrete {
|
||||
fn simple(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
graphql_interface!(<'a> WithLifetime<'a>: () as "WithLifetime" |&self| {
|
||||
field simple() -> i32 { 0 }
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
});
|
||||
#[graphql_interface(for = Concrete, scalar = DefaultScalarValue)]
|
||||
trait WithGenerics<T> {
|
||||
fn simple(&self) -> i32;
|
||||
}
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl<T> WithGenerics<T> for Concrete {
|
||||
fn simple(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
graphql_interface!(<T> WithGenerics<T>: () as "WithGenerics" |&self| {
|
||||
field simple() -> i32 { 0 }
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
});
|
||||
#[graphql_interface(for = Concrete, desc = "A description", scalar = DefaultScalarValue)]
|
||||
trait Description {
|
||||
fn simple(&self) -> i32;
|
||||
}
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl Description for Concrete {
|
||||
fn simple(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
graphql_interface!(DescriptionFirst: () |&self| {
|
||||
description: "A description"
|
||||
struct Root;
|
||||
|
||||
field simple() -> i32 { 0 }
|
||||
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
});
|
||||
|
||||
graphql_interface!(FieldsFirst: () |&self| {
|
||||
field simple() -> i32 { 0 }
|
||||
|
||||
description: "A description"
|
||||
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
});
|
||||
|
||||
graphql_interface!(InterfacesFirst: () |&self| {
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
|
||||
field simple() -> i32 { 0 }
|
||||
|
||||
description: "A description"
|
||||
});
|
||||
|
||||
graphql_interface!(CommasWithTrailing: () |&self| {
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) },
|
||||
|
||||
field simple() -> i32 { 0 },
|
||||
|
||||
description: "A description",
|
||||
});
|
||||
|
||||
graphql_interface!(CommasOnMeta: () |&self| {
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete) }
|
||||
description: "A description",
|
||||
|
||||
field simple() -> i32 { 0 }
|
||||
});
|
||||
|
||||
graphql_interface!(ResolversWithTrailingComma: () |&self| {
|
||||
instance_resolvers: |_| { Concrete => Some(Concrete), }
|
||||
description: "A description",
|
||||
|
||||
field simple() -> i32 { 0 }
|
||||
});
|
||||
|
||||
#[crate::graphql_object(
|
||||
// FIXME: make async work
|
||||
noasync
|
||||
)]
|
||||
impl<'a> Root {
|
||||
fn custom_name() -> CustomName {
|
||||
CustomName {}
|
||||
#[crate::graphql_object]
|
||||
impl Root {
|
||||
fn custom_name() -> CustomNameValue {
|
||||
Concrete.into()
|
||||
}
|
||||
|
||||
fn with_lifetime() -> WithLifetime<'a> {
|
||||
WithLifetime { data: PhantomData }
|
||||
fn with_lifetime() -> WithLifetimeValue<'static> {
|
||||
Concrete.into()
|
||||
}
|
||||
fn with_generics() -> WithGenerics<i32> {
|
||||
WithGenerics { data: 123 }
|
||||
fn with_generics() -> WithGenericsValue<i32> {
|
||||
Concrete.into()
|
||||
}
|
||||
|
||||
fn description_first() -> DescriptionFirst {
|
||||
DescriptionFirst {}
|
||||
}
|
||||
fn fields_first() -> FieldsFirst {
|
||||
FieldsFirst {}
|
||||
}
|
||||
fn interfaces_first() -> InterfacesFirst {
|
||||
InterfacesFirst {}
|
||||
}
|
||||
|
||||
fn commas_with_trailing() -> CommasWithTrailing {
|
||||
CommasWithTrailing {}
|
||||
}
|
||||
fn commas_on_meta() -> CommasOnMeta {
|
||||
CommasOnMeta {}
|
||||
}
|
||||
|
||||
fn resolvers_with_trailing_comma() -> ResolversWithTrailingComma {
|
||||
ResolversWithTrailingComma {}
|
||||
fn description() -> DescriptionValue {
|
||||
Concrete.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,115 +198,10 @@ async fn introspect_with_generics() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn introspect_description_first() {
|
||||
run_type_info_query("DescriptionFirst", |object, fields| {
|
||||
run_type_info_query("Description", |object, fields| {
|
||||
assert_eq!(
|
||||
object.get_field_value("name"),
|
||||
Some(&Value::scalar("DescriptionFirst"))
|
||||
);
|
||||
assert_eq!(
|
||||
object.get_field_value("description"),
|
||||
Some(&Value::scalar("A description"))
|
||||
);
|
||||
|
||||
assert!(fields.contains(&Value::object(
|
||||
vec![("name", Value::scalar("simple"))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)));
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn introspect_fields_first() {
|
||||
run_type_info_query("FieldsFirst", |object, fields| {
|
||||
assert_eq!(
|
||||
object.get_field_value("name"),
|
||||
Some(&Value::scalar("FieldsFirst"))
|
||||
);
|
||||
assert_eq!(
|
||||
object.get_field_value("description"),
|
||||
Some(&Value::scalar("A description"))
|
||||
);
|
||||
|
||||
assert!(fields.contains(&Value::object(
|
||||
vec![("name", Value::scalar("simple"))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)));
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn introspect_interfaces_first() {
|
||||
run_type_info_query("InterfacesFirst", |object, fields| {
|
||||
assert_eq!(
|
||||
object.get_field_value("name"),
|
||||
Some(&Value::scalar("InterfacesFirst"))
|
||||
);
|
||||
assert_eq!(
|
||||
object.get_field_value("description"),
|
||||
Some(&Value::scalar("A description"))
|
||||
);
|
||||
|
||||
assert!(fields.contains(&Value::object(
|
||||
vec![("name", Value::scalar("simple"))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)));
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn introspect_commas_with_trailing() {
|
||||
run_type_info_query("CommasWithTrailing", |object, fields| {
|
||||
assert_eq!(
|
||||
object.get_field_value("name"),
|
||||
Some(&Value::scalar("CommasWithTrailing"))
|
||||
);
|
||||
assert_eq!(
|
||||
object.get_field_value("description"),
|
||||
Some(&Value::scalar("A description"))
|
||||
);
|
||||
|
||||
assert!(fields.contains(&Value::object(
|
||||
vec![("name", Value::scalar("simple"))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)));
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn introspect_commas_on_meta() {
|
||||
run_type_info_query("CommasOnMeta", |object, fields| {
|
||||
assert_eq!(
|
||||
object.get_field_value("name"),
|
||||
Some(&Value::scalar("CommasOnMeta"))
|
||||
);
|
||||
assert_eq!(
|
||||
object.get_field_value("description"),
|
||||
Some(&Value::scalar("A description"))
|
||||
);
|
||||
|
||||
assert!(fields.contains(&Value::object(
|
||||
vec![("name", Value::scalar("simple"))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)));
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn introspect_resolvers_with_trailing_comma() {
|
||||
run_type_info_query("ResolversWithTrailingComma", |object, fields| {
|
||||
assert_eq!(
|
||||
object.get_field_value("name"),
|
||||
Some(&Value::scalar("ResolversWithTrailingComma"))
|
||||
Some(&Value::scalar("Description"))
|
||||
);
|
||||
assert_eq!(
|
||||
object.get_field_value("description"),
|
||||
|
|
|
@ -639,29 +639,6 @@ impl<'a, S> Field<'a, S> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds a (multi)line doc string to the description of the field.
|
||||
/// Any leading or trailing newlines will be removed.
|
||||
///
|
||||
/// If the docstring contains newlines, repeated leading tab and space characters
|
||||
/// will be removed from the beginning of each line.
|
||||
///
|
||||
/// If the description hasn't been set, the description is set to the provided line.
|
||||
/// Otherwise, the doc string is added to the current description after a newline.
|
||||
pub fn push_docstring(mut self, multiline: &[&str]) -> Field<'a, S> {
|
||||
if let Some(docstring) = clean_docstring(multiline) {
|
||||
match &mut self.description {
|
||||
&mut Some(ref mut desc) => {
|
||||
desc.push('\n');
|
||||
desc.push_str(&docstring);
|
||||
}
|
||||
desc @ &mut None => {
|
||||
*desc = Some(docstring);
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an argument to the field
|
||||
///
|
||||
/// Arguments are unordered and can't contain duplicates by name.
|
||||
|
@ -706,30 +683,9 @@ impl<'a, S> Argument<'a, S> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds a (multi)line doc string to the description of the field.
|
||||
/// Any leading or trailing newlines will be removed.
|
||||
///
|
||||
/// If the docstring contains newlines, repeated leading tab and space characters
|
||||
/// will be removed from the beginning of each line.
|
||||
///
|
||||
/// If the description hasn't been set, the description is set to the provided line.
|
||||
/// Otherwise, the doc string is added to the current description after a newline.
|
||||
pub fn push_docstring(mut self, multiline: &[&str]) -> Argument<'a, S> {
|
||||
if let Some(docstring) = clean_docstring(multiline) {
|
||||
match &mut self.description {
|
||||
&mut Some(ref mut desc) => {
|
||||
desc.push('\n');
|
||||
desc.push_str(&docstring);
|
||||
}
|
||||
desc @ &mut None => *desc = Some(docstring),
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default value of the argument
|
||||
///
|
||||
/// This overwrites the description if any was previously set.
|
||||
/// This overwrites the default value if any was previously set.
|
||||
pub fn default_value(mut self, default_value: InputValue<S>) -> Self {
|
||||
self.default_value = Some(default_value);
|
||||
self
|
||||
|
@ -798,35 +754,3 @@ where
|
|||
{
|
||||
<T as FromInputValue<S>>::from_input_value(v).is_some()
|
||||
}
|
||||
|
||||
fn clean_docstring(multiline: &[&str]) -> Option<String> {
|
||||
if multiline.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let trim_start = multiline
|
||||
.iter()
|
||||
.filter_map(|ln| ln.chars().position(|ch| !ch.is_whitespace()))
|
||||
.min()
|
||||
.unwrap_or(0);
|
||||
Some(
|
||||
multiline
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(line, ln)| {
|
||||
let new_ln = if !ln.chars().next().map(char::is_whitespace).unwrap_or(false) {
|
||||
ln.trim_end() // skip trimming the first line
|
||||
} else if ln.len() >= trim_start {
|
||||
ln[trim_start..].trim_end()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
new_ln.chars().chain(
|
||||
['\n']
|
||||
.iter()
|
||||
.take_while(move |_| line < multiline.len() - 1)
|
||||
.cloned(),
|
||||
)
|
||||
})
|
||||
.collect::<String>(),
|
||||
)
|
||||
}
|
||||
|
|
1
juniper/src/tests/fixtures/starwars/mod.rs
vendored
1
juniper/src/tests/fixtures/starwars/mod.rs
vendored
|
@ -1,4 +1,3 @@
|
|||
pub mod model;
|
||||
pub mod schema;
|
||||
#[cfg(feature = "schema-language")]
|
||||
pub mod schema_language;
|
||||
|
|
319
juniper/src/tests/fixtures/starwars/model.rs
vendored
319
juniper/src/tests/fixtures/starwars/model.rs
vendored
|
@ -1,319 +0,0 @@
|
|||
#![allow(missing_docs)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::GraphQLEnum;
|
||||
|
||||
#[derive(GraphQLEnum, Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Episode {
|
||||
#[graphql(name = "NEW_HOPE")]
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
pub trait Character {
|
||||
fn id(&self) -> &str;
|
||||
fn name(&self) -> &str;
|
||||
fn friend_ids(&self) -> &[String];
|
||||
fn appears_in(&self) -> &[Episode];
|
||||
fn secret_backstory(&self) -> &Option<String>;
|
||||
fn as_character(&self) -> &dyn Character;
|
||||
}
|
||||
|
||||
pub trait Human: Character {
|
||||
fn home_planet(&self) -> &Option<String>;
|
||||
}
|
||||
|
||||
pub trait Droid: Character {
|
||||
fn primary_function(&self) -> &Option<String>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HumanData {
|
||||
id: String,
|
||||
name: String,
|
||||
friend_ids: Vec<String>,
|
||||
appears_in: Vec<Episode>,
|
||||
secret_backstory: Option<String>,
|
||||
home_planet: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DroidData {
|
||||
id: String,
|
||||
name: String,
|
||||
friend_ids: Vec<String>,
|
||||
appears_in: Vec<Episode>,
|
||||
secret_backstory: Option<String>,
|
||||
primary_function: Option<String>,
|
||||
}
|
||||
|
||||
impl Character for HumanData {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
fn friend_ids(&self) -> &[String] {
|
||||
&self.friend_ids
|
||||
}
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
&self.appears_in
|
||||
}
|
||||
fn secret_backstory(&self) -> &Option<String> {
|
||||
&self.secret_backstory
|
||||
}
|
||||
fn as_character(&self) -> &dyn Character {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Human for HumanData {
|
||||
fn home_planet(&self) -> &Option<String> {
|
||||
&self.home_planet
|
||||
}
|
||||
}
|
||||
|
||||
impl Character for DroidData {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
fn friend_ids(&self) -> &[String] {
|
||||
&self.friend_ids
|
||||
}
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
&self.appears_in
|
||||
}
|
||||
fn secret_backstory(&self) -> &Option<String> {
|
||||
&self.secret_backstory
|
||||
}
|
||||
fn as_character(&self) -> &dyn Character {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Droid for DroidData {
|
||||
fn primary_function(&self) -> &Option<String> {
|
||||
&self.primary_function
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Database {
|
||||
humans: HashMap<String, HumanData>,
|
||||
droids: HashMap<String, DroidData>,
|
||||
}
|
||||
|
||||
use crate::{
|
||||
executor::Registry,
|
||||
schema::meta::MetaType,
|
||||
types::base::{GraphQLType, GraphQLValue},
|
||||
value::ScalarValue,
|
||||
};
|
||||
|
||||
impl<S> GraphQLType<S> for Database
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn name(_: &()) -> Option<&str> {
|
||||
Some("_Database")
|
||||
}
|
||||
|
||||
fn meta<'r>(_: &(), registry: &mut Registry<'r, S>) -> MetaType<'r, S>
|
||||
where
|
||||
S: 'r,
|
||||
{
|
||||
registry.build_object_type::<Self>(&(), &[]).into_meta()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> GraphQLValue<S> for Database
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
type Context = Self;
|
||||
type TypeInfo = ();
|
||||
|
||||
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
|
||||
<Self as GraphQLType<S>>::name(info)
|
||||
}
|
||||
}
|
||||
|
||||
impl HumanData {
|
||||
pub fn new(
|
||||
id: &str,
|
||||
name: &str,
|
||||
friend_ids: &[&str],
|
||||
appears_in: &[Episode],
|
||||
secret_backstory: Option<&str>,
|
||||
home_planet: Option<&str>,
|
||||
) -> HumanData {
|
||||
HumanData {
|
||||
id: id.to_owned(),
|
||||
name: name.to_owned(),
|
||||
friend_ids: friend_ids
|
||||
.to_owned()
|
||||
.into_iter()
|
||||
.map(|f| f.to_owned())
|
||||
.collect(),
|
||||
appears_in: appears_in.to_vec(),
|
||||
secret_backstory: secret_backstory.map(|b| b.to_owned()),
|
||||
home_planet: home_planet.map(|p| p.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DroidData {
|
||||
pub fn new(
|
||||
id: &str,
|
||||
name: &str,
|
||||
friend_ids: &[&str],
|
||||
appears_in: &[Episode],
|
||||
secret_backstory: Option<&str>,
|
||||
primary_function: Option<&str>,
|
||||
) -> DroidData {
|
||||
DroidData {
|
||||
id: id.to_owned(),
|
||||
name: name.to_owned(),
|
||||
friend_ids: friend_ids
|
||||
.to_owned()
|
||||
.into_iter()
|
||||
.map(|f| f.to_owned())
|
||||
.collect(),
|
||||
appears_in: appears_in.to_vec(),
|
||||
secret_backstory: secret_backstory.map(|b| b.to_owned()),
|
||||
primary_function: primary_function.map(|p| p.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new() -> Database {
|
||||
let mut humans = HashMap::new();
|
||||
let mut droids = HashMap::new();
|
||||
|
||||
humans.insert(
|
||||
"1000".to_owned(),
|
||||
HumanData::new(
|
||||
"1000",
|
||||
"Luke Skywalker",
|
||||
&["1002", "1003", "2000", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Tatooine"),
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1001".to_owned(),
|
||||
HumanData::new(
|
||||
"1001",
|
||||
"Darth Vader",
|
||||
&["1004"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Tatooine"),
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1002".to_owned(),
|
||||
HumanData::new(
|
||||
"1002",
|
||||
"Han Solo",
|
||||
&["1000", "1003", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1003".to_owned(),
|
||||
HumanData::new(
|
||||
"1003",
|
||||
"Leia Organa",
|
||||
&["1000", "1002", "2000", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Alderaan"),
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1004".to_owned(),
|
||||
HumanData::new(
|
||||
"1004",
|
||||
"Wilhuff Tarkin",
|
||||
&["1001"],
|
||||
&[Episode::NewHope],
|
||||
None,
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
droids.insert(
|
||||
"2000".to_owned(),
|
||||
DroidData::new(
|
||||
"2000",
|
||||
"C-3PO",
|
||||
&["1000", "1002", "1003", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Protocol"),
|
||||
),
|
||||
);
|
||||
|
||||
droids.insert(
|
||||
"2001".to_owned(),
|
||||
DroidData::new(
|
||||
"2001",
|
||||
"R2-D2",
|
||||
&["1000", "1002", "1003"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Astromech"),
|
||||
),
|
||||
);
|
||||
|
||||
Database { humans, droids }
|
||||
}
|
||||
|
||||
pub fn get_hero(&self, episode: Option<Episode>) -> &dyn Character {
|
||||
if episode == Some(Episode::Empire) {
|
||||
self.get_human("1000").unwrap().as_character()
|
||||
} else {
|
||||
self.get_droid("2001").unwrap().as_character()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_human(&self, id: &str) -> Option<&dyn Human> {
|
||||
self.humans.get(id).map(|h| h as &dyn Human)
|
||||
}
|
||||
|
||||
pub fn get_droid(&self, id: &str) -> Option<&dyn Droid> {
|
||||
self.droids.get(id).map(|d| d as &dyn Droid)
|
||||
}
|
||||
|
||||
pub fn get_character(&self, id: &str) -> Option<&dyn Character> {
|
||||
if let Some(h) = self.humans.get(id) {
|
||||
Some(h)
|
||||
} else if let Some(d) = self.droids.get(id) {
|
||||
Some(d)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_friends(&self, c: &dyn Character) -> Vec<&dyn Character> {
|
||||
c.friend_ids()
|
||||
.iter()
|
||||
.flat_map(|id| self.get_character(id))
|
||||
.collect()
|
||||
}
|
||||
}
|
489
juniper/src/tests/fixtures/starwars/schema.rs
vendored
489
juniper/src/tests/fixtures/starwars/schema.rs
vendored
|
@ -1,164 +1,385 @@
|
|||
#![allow(missing_docs)]
|
||||
|
||||
use std::{collections::HashMap, pin::Pin};
|
||||
|
||||
use crate::{
|
||||
executor::Context,
|
||||
graphql_subscription,
|
||||
tests::fixtures::starwars::model::{Character, Database, Droid, Episode, Human},
|
||||
GraphQLObject,
|
||||
graphql_interface, graphql_object, graphql_subscription, Context, DefaultScalarValue,
|
||||
GraphQLEnum,
|
||||
};
|
||||
use std::pin::Pin;
|
||||
|
||||
impl Context for Database {}
|
||||
|
||||
graphql_interface!(<'a> &'a dyn Character: Database as "Character" |&self| {
|
||||
description: "A character in the Star Wars Trilogy"
|
||||
|
||||
field id() -> &str as "The id of the character" {
|
||||
self.id()
|
||||
}
|
||||
|
||||
field name() -> Option<&str> as "The name of the character" {
|
||||
Some(self.name())
|
||||
}
|
||||
|
||||
field friends(&executor) -> Vec<&dyn Character>
|
||||
as "The friends of the character" {
|
||||
executor.context().get_friends(self.as_character())
|
||||
}
|
||||
|
||||
field appears_in() -> &[Episode] as "Which movies they appear in" {
|
||||
self.appears_in()
|
||||
}
|
||||
|
||||
instance_resolvers: |&context| {
|
||||
&dyn Human => context.get_human(&self.id()),
|
||||
&dyn Droid => context.get_droid(&self.id()),
|
||||
}
|
||||
});
|
||||
|
||||
#[crate::graphql_object(
|
||||
Context = Database,
|
||||
Scalar = crate::DefaultScalarValue,
|
||||
interfaces = [&dyn Character],
|
||||
// FIXME: make async work
|
||||
noasync
|
||||
)]
|
||||
/// A humanoid creature in the Star Wars universe.
|
||||
impl<'a> &'a dyn Human {
|
||||
/// The id of the human
|
||||
fn id(&self) -> &str {
|
||||
self.id()
|
||||
}
|
||||
|
||||
/// The name of the human
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(self.name())
|
||||
}
|
||||
|
||||
/// The friends of the human
|
||||
fn friends(&self, ctx: &Database) -> Vec<&dyn Character> {
|
||||
ctx.get_friends(self.as_character())
|
||||
}
|
||||
|
||||
/// Which movies they appear in
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
self.appears_in()
|
||||
}
|
||||
|
||||
/// The home planet of the human
|
||||
fn home_planet(&self) -> &Option<String> {
|
||||
self.home_planet()
|
||||
}
|
||||
}
|
||||
|
||||
#[crate::graphql_object(
|
||||
Context = Database,
|
||||
Scalar = crate::DefaultScalarValue,
|
||||
interfaces = [&dyn Character],
|
||||
// FIXME: make async work
|
||||
noasync
|
||||
)]
|
||||
/// A mechanical creature in the Star Wars universe.
|
||||
impl<'a> &'a dyn Droid {
|
||||
/// The id of the droid
|
||||
fn id(&self) -> &str {
|
||||
self.id()
|
||||
}
|
||||
|
||||
/// The name of the droid
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(self.name())
|
||||
}
|
||||
|
||||
/// The friends of the droid
|
||||
fn friends(&self, ctx: &Database) -> Vec<&dyn Character> {
|
||||
ctx.get_friends(self.as_character())
|
||||
}
|
||||
|
||||
/// Which movies they appear in
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
self.appears_in()
|
||||
}
|
||||
|
||||
/// The primary function of the droid
|
||||
fn primary_function(&self) -> &Option<String> {
|
||||
self.primary_function()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[crate::graphql_object(
|
||||
Context = Database,
|
||||
Scalar = crate::DefaultScalarValue,
|
||||
// FIXME: make async work
|
||||
noasync
|
||||
)]
|
||||
#[graphql_object(context = Database, scalar = DefaultScalarValue)]
|
||||
/// The root query object of the schema
|
||||
impl Query {
|
||||
#[graphql(arguments(id(description = "id of the human")))]
|
||||
fn human(database: &Database, id: String) -> Option<&dyn Human> {
|
||||
fn human(database: &Database, id: String) -> Option<&Human> {
|
||||
database.get_human(&id)
|
||||
}
|
||||
|
||||
#[graphql(arguments(id(description = "id of the droid")))]
|
||||
fn droid(database: &Database, id: String) -> Option<&dyn Droid> {
|
||||
fn droid(database: &Database, id: String) -> Option<&Droid> {
|
||||
database.get_droid(&id)
|
||||
}
|
||||
|
||||
#[graphql(arguments(episode(
|
||||
description = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode"
|
||||
description = "If omitted, returns the hero of the whole saga. \
|
||||
If provided, returns the hero of that particular episode"
|
||||
)))]
|
||||
fn hero(database: &Database, episode: Option<Episode>) -> Option<&dyn Character> {
|
||||
Some(database.get_hero(episode).as_character())
|
||||
fn hero(database: &Database, episode: Option<Episode>) -> Option<CharacterValue> {
|
||||
Some(database.get_hero(episode))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(description = "A humanoid creature in the Star Wars universe")]
|
||||
#[derive(Clone)]
|
||||
/// A humanoid creature in the Star Wars universe.
|
||||
/// TODO: remove this when async interfaces are merged
|
||||
struct HumanSubscription {
|
||||
id: String,
|
||||
name: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
pub struct Subscription;
|
||||
|
||||
type HumanStream = Pin<Box<dyn futures::Stream<Item = HumanSubscription> + Send>>;
|
||||
type HumanStream = Pin<Box<dyn futures::Stream<Item = Human> + Send>>;
|
||||
|
||||
#[graphql_subscription(context = Database)]
|
||||
/// Super basic subscription fixture
|
||||
impl Subscription {
|
||||
async fn async_human() -> HumanStream {
|
||||
Box::pin(futures::stream::once(async {
|
||||
HumanSubscription {
|
||||
id: "stream id".to_string(),
|
||||
name: "stream name".to_string(),
|
||||
home_planet: "stream home planet".to_string(),
|
||||
}
|
||||
}))
|
||||
async fn async_human(context: &Database) -> HumanStream {
|
||||
let human = context.get_human("1000").unwrap().clone();
|
||||
Box::pin(futures::stream::once(futures::future::ready(human)))
|
||||
}
|
||||
}
|
||||
#[derive(GraphQLEnum, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Episode {
|
||||
#[graphql(name = "NEW_HOPE")]
|
||||
NewHope,
|
||||
Empire,
|
||||
Jedi,
|
||||
}
|
||||
|
||||
#[graphql_interface(for = [Human, Droid], context = Database, scalar = DefaultScalarValue)]
|
||||
/// A character in the Star Wars Trilogy
|
||||
pub trait Character {
|
||||
/// The id of the character
|
||||
fn id(&self) -> &str;
|
||||
|
||||
/// The name of the character
|
||||
fn name(&self) -> Option<&str>;
|
||||
|
||||
/// The friends of the character
|
||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue>;
|
||||
|
||||
/// Which movies they appear in
|
||||
fn appears_in(&self) -> &[Episode];
|
||||
|
||||
#[graphql_interface(ignore)]
|
||||
fn friends_ids(&self) -> &[String];
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Human {
|
||||
id: String,
|
||||
name: String,
|
||||
friend_ids: Vec<String>,
|
||||
appears_in: Vec<Episode>,
|
||||
secret_backstory: Option<String>,
|
||||
home_planet: Option<String>,
|
||||
}
|
||||
|
||||
impl Human {
|
||||
pub fn new(
|
||||
id: &str,
|
||||
name: &str,
|
||||
friend_ids: &[&str],
|
||||
appears_in: &[Episode],
|
||||
secret_backstory: Option<&str>,
|
||||
home_planet: Option<&str>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.to_owned(),
|
||||
name: name.to_owned(),
|
||||
friend_ids: friend_ids
|
||||
.to_owned()
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect(),
|
||||
appears_in: appears_in.to_vec(),
|
||||
secret_backstory: secret_backstory.map(ToOwned::to_owned),
|
||||
home_planet: home_planet.map(|p| p.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A humanoid creature in the Star Wars universe.
|
||||
#[graphql_object(
|
||||
context = Database,
|
||||
scalar = DefaultScalarValue,
|
||||
interfaces = CharacterValue,
|
||||
)]
|
||||
impl Human {
|
||||
/// The id of the human
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// The name of the human
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(self.name.as_str())
|
||||
}
|
||||
|
||||
/// The friends of the human
|
||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
||||
ctx.get_friends(self)
|
||||
}
|
||||
|
||||
/// Which movies they appear in
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
&self.appears_in
|
||||
}
|
||||
|
||||
/// The home planet of the human
|
||||
fn home_planet(&self) -> &Option<String> {
|
||||
&self.home_planet
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl Character for Human {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(&self.name)
|
||||
}
|
||||
|
||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
||||
ctx.get_friends(self)
|
||||
}
|
||||
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
&self.appears_in
|
||||
}
|
||||
|
||||
fn friends_ids(&self) -> &[String] {
|
||||
&self.friend_ids
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Droid {
|
||||
id: String,
|
||||
name: String,
|
||||
friend_ids: Vec<String>,
|
||||
appears_in: Vec<Episode>,
|
||||
secret_backstory: Option<String>,
|
||||
primary_function: Option<String>,
|
||||
}
|
||||
|
||||
impl Droid {
|
||||
pub fn new(
|
||||
id: &str,
|
||||
name: &str,
|
||||
friend_ids: &[&str],
|
||||
appears_in: &[Episode],
|
||||
secret_backstory: Option<&str>,
|
||||
primary_function: Option<&str>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.to_owned(),
|
||||
name: name.to_owned(),
|
||||
friend_ids: friend_ids
|
||||
.to_owned()
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect(),
|
||||
appears_in: appears_in.to_vec(),
|
||||
secret_backstory: secret_backstory.map(ToOwned::to_owned),
|
||||
primary_function: primary_function.map(ToOwned::to_owned),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A mechanical creature in the Star Wars universe.
|
||||
#[graphql_object(
|
||||
context = Database,
|
||||
scalar = DefaultScalarValue,
|
||||
interfaces = CharacterValue,
|
||||
)]
|
||||
impl Droid {
|
||||
/// The id of the droid
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// The name of the droid
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(self.name.as_str())
|
||||
}
|
||||
|
||||
/// The friends of the droid
|
||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
||||
ctx.get_friends(self)
|
||||
}
|
||||
|
||||
/// Which movies they appear in
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
&self.appears_in
|
||||
}
|
||||
|
||||
/// The primary function of the droid
|
||||
fn primary_function(&self) -> &Option<String> {
|
||||
&self.primary_function
|
||||
}
|
||||
}
|
||||
|
||||
#[graphql_interface(scalar = DefaultScalarValue)]
|
||||
impl Character for Droid {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<&str> {
|
||||
Some(&self.name)
|
||||
}
|
||||
|
||||
fn friends(&self, ctx: &Database) -> Vec<CharacterValue> {
|
||||
ctx.get_friends(self)
|
||||
}
|
||||
|
||||
fn appears_in(&self) -> &[Episode] {
|
||||
&self.appears_in
|
||||
}
|
||||
|
||||
fn friends_ids(&self) -> &[String] {
|
||||
&self.friend_ids
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl Context for Database {}
|
||||
|
||||
impl Database {
|
||||
pub fn new() -> Database {
|
||||
let mut humans = HashMap::new();
|
||||
let mut droids = HashMap::new();
|
||||
|
||||
humans.insert(
|
||||
"1000".to_owned(),
|
||||
Human::new(
|
||||
"1000",
|
||||
"Luke Skywalker",
|
||||
&["1002", "1003", "2000", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Tatooine"),
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1001".to_owned(),
|
||||
Human::new(
|
||||
"1001",
|
||||
"Darth Vader",
|
||||
&["1004"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Tatooine"),
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1002".to_owned(),
|
||||
Human::new(
|
||||
"1002",
|
||||
"Han Solo",
|
||||
&["1000", "1003", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1003".to_owned(),
|
||||
Human::new(
|
||||
"1003",
|
||||
"Leia Organa",
|
||||
&["1000", "1002", "2000", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Alderaan"),
|
||||
),
|
||||
);
|
||||
|
||||
humans.insert(
|
||||
"1004".to_owned(),
|
||||
Human::new(
|
||||
"1004",
|
||||
"Wilhuff Tarkin",
|
||||
&["1001"],
|
||||
&[Episode::NewHope],
|
||||
None,
|
||||
None,
|
||||
),
|
||||
);
|
||||
|
||||
droids.insert(
|
||||
"2000".to_owned(),
|
||||
Droid::new(
|
||||
"2000",
|
||||
"C-3PO",
|
||||
&["1000", "1002", "1003", "2001"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Protocol"),
|
||||
),
|
||||
);
|
||||
|
||||
droids.insert(
|
||||
"2001".to_owned(),
|
||||
Droid::new(
|
||||
"2001",
|
||||
"R2-D2",
|
||||
&["1000", "1002", "1003"],
|
||||
&[Episode::NewHope, Episode::Empire, Episode::Jedi],
|
||||
None,
|
||||
Some("Astromech"),
|
||||
),
|
||||
);
|
||||
|
||||
Database { humans, droids }
|
||||
}
|
||||
|
||||
pub fn get_hero(&self, episode: Option<Episode>) -> CharacterValue {
|
||||
if episode == Some(Episode::Empire) {
|
||||
self.get_human("1000").unwrap().clone().into()
|
||||
} else {
|
||||
self.get_droid("2001").unwrap().clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_human(&self, id: &str) -> Option<&Human> {
|
||||
self.humans.get(id)
|
||||
}
|
||||
|
||||
pub fn get_droid(&self, id: &str) -> Option<&Droid> {
|
||||
self.droids.get(id)
|
||||
}
|
||||
|
||||
pub fn get_character(&self, id: &str) -> Option<CharacterValue> {
|
||||
if let Some(h) = self.humans.get(id) {
|
||||
Some(h.clone().into())
|
||||
} else if let Some(d) = self.droids.get(id) {
|
||||
Some(d.clone().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_friends(&self, c: &dyn Character) -> Vec<CharacterValue> {
|
||||
c.friends_ids()
|
||||
.iter()
|
||||
.flat_map(|id| self.get_character(id))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ mod tests {
|
|||
use crate::{
|
||||
schema::model::RootNode,
|
||||
tests::fixtures::starwars::{
|
||||
model::Database, schema::Query, schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION,
|
||||
schema::{Database, Query},
|
||||
schema_language::STATIC_GRAPHQL_SCHEMA_DEFINITION,
|
||||
},
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
};
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::schema_introspection::*;
|
||||
use crate::{
|
||||
executor::Variables,
|
||||
introspection::IntrospectionFormat,
|
||||
schema::model::RootNode,
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
};
|
||||
|
||||
use super::schema_introspection::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_introspection_query_type_name() {
|
||||
let doc = r#"
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
ast::InputValue,
|
||||
executor::Variables,
|
||||
schema::model::RootNode,
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
types::scalars::{EmptyMutation, EmptySubscription},
|
||||
value::Value,
|
||||
};
|
||||
|
@ -641,8 +641,8 @@ async fn test_query_inline_fragments_droid() {
|
|||
"hero",
|
||||
Value::object(
|
||||
vec![
|
||||
("name", Value::scalar("R2-D2")),
|
||||
("__typename", Value::scalar("Droid")),
|
||||
("name", Value::scalar("R2-D2")),
|
||||
("primaryFunction", Value::scalar("Astromech")),
|
||||
]
|
||||
.into_iter()
|
||||
|
@ -662,8 +662,8 @@ async fn test_query_inline_fragments_human() {
|
|||
let doc = r#"
|
||||
query InlineFragments {
|
||||
hero(episode: EMPIRE) {
|
||||
name
|
||||
__typename
|
||||
name
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
@ -682,8 +682,8 @@ async fn test_query_inline_fragments_human() {
|
|||
"hero",
|
||||
Value::object(
|
||||
vec![
|
||||
("name", Value::scalar("Luke Skywalker")),
|
||||
("__typename", Value::scalar("Human")),
|
||||
("name", Value::scalar("Luke Skywalker")),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
|
|
|
@ -113,9 +113,15 @@ where
|
|||
|
||||
crate::sa::assert_obj_safe!(GraphQLValueAsync<Context = (), TypeInfo = ()>);
|
||||
|
||||
/// Helper alias for naming [trait objects][1] of [`GraphQLValueAsync`].
|
||||
///
|
||||
/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
|
||||
pub type DynGraphQLValueAsync<S, C, TI> =
|
||||
dyn GraphQLValueAsync<S, Context = C, TypeInfo = TI> + Send + 'static;
|
||||
|
||||
/// Extension of [`GraphQLType`] trait with asynchronous queries/mutations resolvers.
|
||||
///
|
||||
/// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementors, so
|
||||
/// It's automatically implemented for [`GraphQLValueAsync`] and [`GraphQLType`] implementers, so
|
||||
/// doesn't require manual or code-generated implementation.
|
||||
pub trait GraphQLTypeAsync<S = DefaultScalarValue>: GraphQLValueAsync<S> + GraphQLType<S>
|
||||
where
|
||||
|
|
|
@ -290,6 +290,12 @@ where
|
|||
|
||||
crate::sa::assert_obj_safe!(GraphQLValue<Context = (), TypeInfo = ()>);
|
||||
|
||||
/// Helper alias for naming [trait objects][1] of [`GraphQLValue`].
|
||||
///
|
||||
/// [1]: https://doc.rust-lang.org/reference/types/trait-object.html
|
||||
pub type DynGraphQLValue<S, C, TI> =
|
||||
dyn GraphQLValue<S, Context = C, TypeInfo = TI> + Send + Sync + 'static;
|
||||
|
||||
/// Primary trait used to expose Rust types in a GraphQL schema.
|
||||
///
|
||||
/// All of the convenience macros ultimately expand into an implementation of
|
||||
|
|
|
@ -23,13 +23,37 @@ pub trait GraphQLObjectType<S: ScalarValue>: GraphQLType<S> {
|
|||
fn mark() {}
|
||||
}
|
||||
|
||||
/// Maker trait for [GraphQL interfaces][1].
|
||||
///
|
||||
/// This trait extends the [`GraphQLType`] and is only used to mark an [interface][1]. During
|
||||
/// compile this addition information is required to prevent unwanted structure compiling. If an
|
||||
/// object requires this trait instead of the [`GraphQLType`], then it explicitly requires
|
||||
/// [GraphQL interfaces][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5]
|
||||
/// and [unions][6]) are not allowed.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
/// [2]: https://spec.graphql.org/June2018/#sec-Scalars
|
||||
/// [3]: https://spec.graphql.org/June2018/#sec-Enums
|
||||
/// [4]: https://spec.graphql.org/June2018/#sec-Objects
|
||||
/// [5]: https://spec.graphql.org/June2018/#sec-Input-Objects
|
||||
/// [6]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
pub trait GraphQLInterface<S: ScalarValue>: GraphQLType<S> {
|
||||
/// An arbitrary function without meaning.
|
||||
///
|
||||
/// May contain compile timed check logic which ensures that types are used correctly according
|
||||
/// to the [GraphQL specification][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/
|
||||
fn mark() {}
|
||||
}
|
||||
|
||||
/// Maker trait for [GraphQL unions][1].
|
||||
///
|
||||
/// This trait extends the [`GraphQLType`] and is only used to mark [union][1]. During compile this
|
||||
/// addition information is required to prevent unwanted structure compiling. If an object requires
|
||||
/// this trait instead of the [`GraphQLType`], then it explicitly requires [GraphQL unions][1].
|
||||
/// Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and [interfaces][6]) are
|
||||
/// not allowed.
|
||||
/// This trait extends the [`GraphQLType`] and is only used to mark an [union][1]. During compile
|
||||
/// this addition information is required to prevent unwanted structure compiling. If an object
|
||||
/// requires this trait instead of the [`GraphQLType`], then it explicitly requires
|
||||
/// [GraphQL unions][1]. Other types ([scalars][2], [enums][3], [objects][4], [input objects][5] and
|
||||
/// [interfaces][6]) are not allowed.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
/// [2]: https://spec.graphql.org/June2018/#sec-Scalars
|
||||
|
@ -97,7 +121,7 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
impl<'a, S, T> IsOutputType<S> for &'a [T]
|
||||
impl<S, T> IsOutputType<S> for [T]
|
||||
where
|
||||
T: IsOutputType<S>,
|
||||
S: ScalarValue,
|
||||
|
@ -111,7 +135,7 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
impl<'a, S, T> IsInputType<S> for &'a [T]
|
||||
impl<S, T> IsInputType<S> for [T]
|
||||
where
|
||||
T: IsInputType<S>,
|
||||
S: ScalarValue,
|
||||
|
@ -120,13 +144,13 @@ where
|
|||
|
||||
impl<'a, S, T> IsInputType<S> for &T
|
||||
where
|
||||
T: IsInputType<S>,
|
||||
T: IsInputType<S> + ?Sized,
|
||||
S: ScalarValue,
|
||||
{
|
||||
}
|
||||
impl<'a, S, T> IsOutputType<S> for &T
|
||||
where
|
||||
T: IsOutputType<S>,
|
||||
T: IsOutputType<S> + ?Sized,
|
||||
S: ScalarValue,
|
||||
{
|
||||
}
|
||||
|
@ -144,5 +168,5 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
impl<'a, S> IsInputType<S> for &str where S: ScalarValue {}
|
||||
impl<'a, S> IsOutputType<S> for &str where S: ScalarValue {}
|
||||
impl<'a, S> IsInputType<S> for str where S: ScalarValue {}
|
||||
impl<'a, S> IsOutputType<S> for str where S: ScalarValue {}
|
||||
|
|
|
@ -201,7 +201,7 @@ crate::sa::assert_obj_safe!(GraphQLSubscriptionValue<Context = (), TypeInfo = ()
|
|||
/// Extension of [`GraphQLType`] trait with asynchronous [subscription][1] execution logic.
|
||||
///
|
||||
/// It's automatically implemented for [`GraphQLSubscriptionValue`] and [`GraphQLType`]
|
||||
/// implementors, so doesn't require manual or code-generated implementation.
|
||||
/// implementers, so doesn't require manual or code-generated implementation.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Subscription
|
||||
pub trait GraphQLSubscriptionType<S = DefaultScalarValue>:
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::env;
|
|||
use actix_cors::Cors;
|
||||
use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
|
||||
use juniper::{
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
EmptyMutation, EmptySubscription, RootNode,
|
||||
};
|
||||
use juniper_actix::{
|
||||
|
|
|
@ -489,7 +489,7 @@ mod tests {
|
|||
use juniper::{
|
||||
futures::stream::StreamExt,
|
||||
http::tests::{run_http_test_suite, HttpIntegration, TestResponse},
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
EmptyMutation, EmptySubscription, RootNode,
|
||||
};
|
||||
|
||||
|
@ -666,7 +666,7 @@ mod tests {
|
|||
#[actix_web::rt::test]
|
||||
async fn batch_request_works() {
|
||||
use juniper::{
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
EmptyMutation, EmptySubscription, RootNode,
|
||||
};
|
||||
|
||||
|
|
47
juniper_codegen/src/common/gen.rs
Normal file
47
juniper_codegen/src/common/gen.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
//! Common code generated parts, used by this crate.
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
/// Generate the code resolving some [GraphQL type][1] in a synchronous manner.
|
||||
///
|
||||
/// Value of a [GraphQL type][1] should be stored in a `res` binding in the generated code, before
|
||||
/// including this piece of code.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Types
|
||||
pub(crate) fn sync_resolving_code() -> TokenStream {
|
||||
quote! {
|
||||
::juniper::IntoResolvable::into(res, executor.context())
|
||||
.and_then(|res| match res {
|
||||
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r),
|
||||
None => Ok(::juniper::Value::null()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the code resolving some [GraphQL type][1] in an asynchronous manner.
|
||||
///
|
||||
/// Value of a [GraphQL type][1] should be resolvable with `fut` binding representing a [`Future`]
|
||||
/// in the generated code, before including this piece of code.
|
||||
///
|
||||
/// Optional `ty` argument may be used to annotate a concrete type of the resolving
|
||||
/// [GraphQL type][1] (the [`Future::Output`]).
|
||||
///
|
||||
/// [`Future`]: std::future::Future
|
||||
/// [`Future::Output`]: std::future::Future::Output
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Types
|
||||
pub(crate) fn async_resolving_code(ty: Option<&syn::Type>) -> TokenStream {
|
||||
let ty = ty.map(|t| quote! { : #t });
|
||||
|
||||
quote! {
|
||||
Box::pin(::juniper::futures::FutureExt::then(fut, move |res #ty| async move {
|
||||
match ::juniper::IntoResolvable::into(res, executor.context())? {
|
||||
Some((ctx, r)) => {
|
||||
let subexec = executor.replaced_context(ctx);
|
||||
subexec.resolve_with_ctx_async(info, &r).await
|
||||
},
|
||||
None => Ok(::juniper::Value::null()),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
89
juniper_codegen/src/common/mod.rs
Normal file
89
juniper_codegen/src/common/mod.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
//! Common functions, definitions and extensions for code generation, used by this crate.
|
||||
|
||||
pub(crate) mod gen;
|
||||
pub(crate) mod parse;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_quote;
|
||||
|
||||
/// [`ScalarValue`] parametrization of the code generation.
|
||||
///
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum ScalarValueType {
|
||||
/// Concrete Rust type is specified as [`ScalarValue`].
|
||||
///
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
Concrete(syn::Type),
|
||||
|
||||
/// One of type parameters of the original type is specified as [`ScalarValue`].
|
||||
///
|
||||
/// The original type is the type that the code is generated for.
|
||||
///
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
ExplicitGeneric(syn::Ident),
|
||||
|
||||
/// [`ScalarValue`] parametrization is assumed to be a generic and is not specified explicitly.
|
||||
///
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
ImplicitGeneric,
|
||||
}
|
||||
|
||||
impl ScalarValueType {
|
||||
/// Indicates whether this [`ScalarValueType`] is generic.
|
||||
#[must_use]
|
||||
pub(crate) fn is_generic(&self) -> bool {
|
||||
matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric)
|
||||
}
|
||||
|
||||
/// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ExplicitGeneric`].
|
||||
#[must_use]
|
||||
pub(crate) fn is_explicit_generic(&self) -> bool {
|
||||
matches!(self, Self::ExplicitGeneric(_))
|
||||
}
|
||||
|
||||
/// Indicates whether this [`ScalarValueType`] is [`ScalarValueType::ImplicitGeneric`].
|
||||
#[must_use]
|
||||
pub(crate) fn is_implicit_generic(&self) -> bool {
|
||||
matches!(self, Self::ImplicitGeneric)
|
||||
}
|
||||
|
||||
/// Returns a type identifier which represents this [`ScalarValueType`].
|
||||
#[must_use]
|
||||
pub(crate) fn ty(&self) -> syn::Type {
|
||||
match self {
|
||||
Self::Concrete(ty) => ty.clone(),
|
||||
Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param },
|
||||
Self::ImplicitGeneric => parse_quote! { __S },
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a type parameter identifier that suits this [`ScalarValueType`].
|
||||
#[must_use]
|
||||
pub(crate) fn generic_ty(&self) -> syn::Type {
|
||||
match self {
|
||||
Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param },
|
||||
Self::ImplicitGeneric | Self::Concrete(_) => parse_quote! { __S },
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a default [`ScalarValue`] type that is compatible with this [`ScalarValueType`].
|
||||
///
|
||||
/// [`ScalarValue`]: juniper::ScalarValue
|
||||
#[must_use]
|
||||
pub(crate) fn default_ty(&self) -> syn::Type {
|
||||
match self {
|
||||
Self::Concrete(ty) => ty.clone(),
|
||||
Self::ExplicitGeneric(_) | Self::ImplicitGeneric => {
|
||||
parse_quote! { ::juniper::DefaultScalarValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ScalarValueType {
|
||||
fn to_tokens(&self, into: &mut TokenStream) {
|
||||
self.ty().to_tokens(into)
|
||||
}
|
||||
}
|
101
juniper_codegen/src/common/parse/attr.rs
Normal file
101
juniper_codegen/src/common/parse/attr.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
//! Common functions, definitions and extensions for parsing and modifying Rust attributes, used by
|
||||
//! this crate.
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use syn::parse_quote;
|
||||
|
||||
use crate::util::path_eq_single;
|
||||
|
||||
/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given
|
||||
/// `attr_path` and `attr_args`.
|
||||
///
|
||||
/// This function is generally used for uniting `proc_macro_attribute` with its body attributes.
|
||||
pub(crate) fn unite(
|
||||
(attr_path, attr_args): (&str, &TokenStream),
|
||||
attrs: &[syn::Attribute],
|
||||
) -> Vec<syn::Attribute> {
|
||||
let mut full_attrs = Vec::with_capacity(attrs.len() + 1);
|
||||
let attr_path = syn::Ident::new(attr_path, Span::call_site());
|
||||
full_attrs.push(parse_quote! { #[#attr_path(#attr_args)] });
|
||||
full_attrs.extend_from_slice(attrs);
|
||||
full_attrs
|
||||
}
|
||||
|
||||
/// Strips all `attr_path` attributes from the given `attrs` collection.
|
||||
///
|
||||
/// This function is generally used for removing duplicate attributes during `proc_macro_attribute`
|
||||
/// expansion, so avoid unnecessary expansion duplication.
|
||||
pub(crate) fn strip(attr_path: &str, attrs: Vec<syn::Attribute>) -> Vec<syn::Attribute> {
|
||||
attrs
|
||||
.into_iter()
|
||||
.filter(|attr| !path_eq_single(&attr.path, attr_path))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Common errors of parsing Rust attributes, appeared in this crate.
|
||||
pub(crate) mod err {
|
||||
use proc_macro2::Span;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
/// Creates "duplicated argument" [`syn::Error`] for the given `name` pointing to the given
|
||||
/// `span`.
|
||||
#[must_use]
|
||||
pub(crate) fn dup_arg<S: AsSpan>(span: S) -> syn::Error {
|
||||
syn::Error::new(span.as_span(), "duplicated attribute argument found")
|
||||
}
|
||||
|
||||
/// Creates "unknown argument" [`syn::Error`] for the given `name` pointing to the given `span`.
|
||||
#[must_use]
|
||||
pub(crate) fn unknown_arg<S: AsSpan>(span: S, name: &str) -> syn::Error {
|
||||
syn::Error::new(
|
||||
span.as_span(),
|
||||
format!("unknown `{}` attribute argument", name),
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper coercion for [`Span`] and [`Spanned`] types to use in function arguments.
|
||||
pub(crate) trait AsSpan {
|
||||
/// Returns the coerced [`Span`].
|
||||
#[must_use]
|
||||
fn as_span(&self) -> Span;
|
||||
}
|
||||
|
||||
impl AsSpan for Span {
|
||||
#[inline]
|
||||
fn as_span(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Spanned> AsSpan for &T {
|
||||
#[inline]
|
||||
fn as_span(&self) -> Span {
|
||||
self.span()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handy extension of [`Option`] methods, used in this crate.
|
||||
pub(crate) trait OptionExt {
|
||||
type Inner;
|
||||
|
||||
/// Transforms the `Option<T>` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)`
|
||||
/// to `Err(err(v))`.
|
||||
fn none_or_else<E, F>(self, err: F) -> Result<(), E>
|
||||
where
|
||||
F: FnOnce(Self::Inner) -> E;
|
||||
}
|
||||
|
||||
impl<T> OptionExt for Option<T> {
|
||||
type Inner = T;
|
||||
|
||||
fn none_or_else<E, F>(self, err: F) -> Result<(), E>
|
||||
where
|
||||
F: FnOnce(T) -> E,
|
||||
{
|
||||
match self {
|
||||
Some(v) => Err(err(v)),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
94
juniper_codegen/src/common/parse/downcaster.rs
Normal file
94
juniper_codegen/src/common/parse/downcaster.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
//! Common functions, definitions and extensions for parsing downcasting functions, used by GraphQL
|
||||
//! [interfaces][1] and [unions][2] definitions to downcast its type to a concrete implementer type.
|
||||
//!
|
||||
//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
//! [2]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{ext::IdentExt as _, spanned::Spanned as _};
|
||||
|
||||
use crate::common::parse::TypeExt as _;
|
||||
|
||||
/// Parses downcasting output type from the downcaster method return type.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If return type is invalid (not `Option<&OutputType>`), then returns the [`Span`] to display the
|
||||
/// corresponding error at.
|
||||
pub(crate) fn output_type(ret_ty: &syn::ReturnType) -> Result<syn::Type, Span> {
|
||||
let ret_ty = match &ret_ty {
|
||||
syn::ReturnType::Type(_, ty) => &*ty,
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
|
||||
let path = match ret_ty.unparenthesized() {
|
||||
syn::Type::Path(syn::TypePath { qself: None, path }) => path,
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
|
||||
let (ident, args) = match path.segments.last() {
|
||||
Some(syn::PathSegment {
|
||||
ident,
|
||||
arguments: syn::PathArguments::AngleBracketed(generic),
|
||||
}) => (ident, &generic.args),
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
|
||||
if ident.unraw() != "Option" {
|
||||
return Err(ret_ty.span());
|
||||
}
|
||||
if args.len() != 1 {
|
||||
return Err(ret_ty.span());
|
||||
}
|
||||
|
||||
let out_ty = match args.first() {
|
||||
Some(syn::GenericArgument::Type(inner_ty)) => match inner_ty.unparenthesized() {
|
||||
syn::Type::Reference(inner_ty) => {
|
||||
if inner_ty.mutability.is_some() {
|
||||
return Err(inner_ty.span());
|
||||
}
|
||||
inner_ty.elem.unparenthesized().clone()
|
||||
}
|
||||
_ => return Err(ret_ty.span()),
|
||||
},
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
Ok(out_ty)
|
||||
}
|
||||
|
||||
/// Parses context type used for downcasting from the downcaster method signature.
|
||||
///
|
||||
/// Returns [`None`] if downcaster method doesn't accept context.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at.
|
||||
pub(crate) fn context_ty(sig: &syn::Signature) -> Result<Option<syn::Type>, Span> {
|
||||
match sig.receiver() {
|
||||
Some(syn::FnArg::Receiver(rcv)) => {
|
||||
if rcv.reference.is_none() || rcv.mutability.is_some() {
|
||||
return Err(rcv.span());
|
||||
}
|
||||
}
|
||||
_ => return Err(sig.span()),
|
||||
}
|
||||
|
||||
if sig.inputs.len() > 2 {
|
||||
return Err(sig.inputs.span());
|
||||
}
|
||||
|
||||
let second_arg_ty = match sig.inputs.iter().nth(1) {
|
||||
Some(syn::FnArg::Typed(arg)) => &*arg.ty,
|
||||
None => return Ok(None),
|
||||
_ => return Err(sig.inputs.span()),
|
||||
};
|
||||
match second_arg_ty.unparenthesized() {
|
||||
syn::Type::Reference(ref_ty) => {
|
||||
if ref_ty.mutability.is_some() {
|
||||
return Err(ref_ty.span());
|
||||
}
|
||||
Ok(Some(ref_ty.elem.unparenthesized().clone()))
|
||||
}
|
||||
ty => Err(ty.span()),
|
||||
}
|
||||
}
|
261
juniper_codegen/src/common/parse/mod.rs
Normal file
261
juniper_codegen/src/common/parse/mod.rs
Normal file
|
@ -0,0 +1,261 @@
|
|||
//! Common functions, definitions and extensions for parsing, normalizing and modifying Rust syntax,
|
||||
//! used by this crate.
|
||||
|
||||
pub(crate) mod attr;
|
||||
pub(crate) mod downcaster;
|
||||
|
||||
use std::{
|
||||
any::TypeId,
|
||||
iter::{self, FromIterator as _},
|
||||
mem,
|
||||
};
|
||||
|
||||
use proc_macro2::Span;
|
||||
use syn::{
|
||||
ext::IdentExt as _,
|
||||
parse::{Parse, ParseBuffer},
|
||||
parse_quote,
|
||||
punctuated::Punctuated,
|
||||
token::{self, Token},
|
||||
};
|
||||
|
||||
/// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing.
|
||||
pub(crate) trait ParseBufferExt {
|
||||
/// Tries to parse `T` as the next token.
|
||||
///
|
||||
/// Doesn't move [`ParseStream`]'s cursor if there is no `T`.
|
||||
fn try_parse<T: Default + Parse + Token>(&self) -> syn::Result<Option<T>>;
|
||||
|
||||
/// Checks whether next token is `T`.
|
||||
///
|
||||
/// Doesn't move [`ParseStream`]'s cursor.
|
||||
#[must_use]
|
||||
fn is_next<T: Default + Token>(&self) -> bool;
|
||||
|
||||
/// Parses next token as [`syn::Ident`] _allowing_ Rust keywords, while default [`Parse`]
|
||||
/// implementation for [`syn::Ident`] disallows keywords.
|
||||
///
|
||||
/// Always moves [`ParseStream`]'s cursor.
|
||||
fn parse_any_ident(&self) -> syn::Result<syn::Ident>;
|
||||
|
||||
/// Checks whether next token is a wrapper `W` and if yes, then parses the wrapped tokens as `T`
|
||||
/// [`Punctuated`] with `P`. Otherwise, parses just `T`.
|
||||
///
|
||||
/// Always moves [`ParseStream`]'s cursor.
|
||||
fn parse_maybe_wrapped_and_punctuated<T, W, P>(&self) -> syn::Result<Punctuated<T, P>>
|
||||
where
|
||||
T: Parse,
|
||||
W: Default + Token + 'static,
|
||||
P: Default + Parse + Token;
|
||||
}
|
||||
|
||||
impl<'a> ParseBufferExt for ParseBuffer<'a> {
|
||||
fn try_parse<T: Default + Parse + Token>(&self) -> syn::Result<Option<T>> {
|
||||
Ok(if self.is_next::<T>() {
|
||||
Some(self.parse()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn is_next<T: Default + Token>(&self) -> bool {
|
||||
self.lookahead1().peek(|_| T::default())
|
||||
}
|
||||
|
||||
fn parse_any_ident(&self) -> syn::Result<syn::Ident> {
|
||||
self.call(syn::Ident::parse_any)
|
||||
}
|
||||
|
||||
fn parse_maybe_wrapped_and_punctuated<T, W, P>(&self) -> syn::Result<Punctuated<T, P>>
|
||||
where
|
||||
T: Parse,
|
||||
W: Default + Token + 'static,
|
||||
P: Default + Parse + Token,
|
||||
{
|
||||
Ok(if self.is_next::<W>() {
|
||||
let inner;
|
||||
if TypeId::of::<W>() == TypeId::of::<token::Bracket>() {
|
||||
let _ = syn::bracketed!(inner in self);
|
||||
} else if TypeId::of::<W>() == TypeId::of::<token::Brace>() {
|
||||
let _ = syn::braced!(inner in self);
|
||||
} else if TypeId::of::<W>() == TypeId::of::<token::Paren>() {
|
||||
let _ = syn::parenthesized!(inner in self);
|
||||
} else {
|
||||
unimplemented!(
|
||||
"ParseBufferExt::parse_maybe_wrapped_and_punctuated supports only brackets, \
|
||||
braces and parentheses as wrappers.",
|
||||
);
|
||||
}
|
||||
Punctuated::parse_terminated(&inner)?
|
||||
} else {
|
||||
Punctuated::from_iter(iter::once(self.parse::<T>()?))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension of [`syn::Type`] providing common function widely used by this crate for parsing.
|
||||
pub(crate) trait TypeExt {
|
||||
/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested
|
||||
/// [`syn::TypeParen`]s asap).
|
||||
#[must_use]
|
||||
fn unparenthesized(&self) -> &Self;
|
||||
|
||||
/// Retrieves the inner [`syn::Type`] from the given reference type, or just returns "as is" if
|
||||
/// the type is not a reference.
|
||||
///
|
||||
/// Also, makes the type [`TypeExt::unparenthesized`], if possible.
|
||||
#[must_use]
|
||||
fn unreferenced(&self) -> &Self;
|
||||
|
||||
fn lifetimes_anonymized(&mut self);
|
||||
}
|
||||
|
||||
impl TypeExt for syn::Type {
|
||||
fn unparenthesized(&self) -> &Self {
|
||||
match self {
|
||||
Self::Paren(ty) => ty.elem.unparenthesized(),
|
||||
ty => ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn unreferenced(&self) -> &Self {
|
||||
match self.unparenthesized() {
|
||||
Self::Reference(ref_ty) => &*ref_ty.elem,
|
||||
ty => ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn lifetimes_anonymized(&mut self) {
|
||||
use syn::{GenericArgument as GA, Type as T};
|
||||
|
||||
match self {
|
||||
T::Array(syn::TypeArray { elem, .. })
|
||||
| T::Group(syn::TypeGroup { elem, .. })
|
||||
| T::Paren(syn::TypeParen { elem, .. })
|
||||
| T::Ptr(syn::TypePtr { elem, .. })
|
||||
| T::Slice(syn::TypeSlice { elem, .. }) => (&mut *elem).lifetimes_anonymized(),
|
||||
|
||||
T::Tuple(syn::TypeTuple { elems, .. }) => {
|
||||
for ty in elems.iter_mut() {
|
||||
ty.lifetimes_anonymized();
|
||||
}
|
||||
}
|
||||
|
||||
T::ImplTrait(syn::TypeImplTrait { bounds, .. })
|
||||
| T::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
|
||||
for bound in bounds.iter_mut() {
|
||||
match bound {
|
||||
syn::TypeParamBound::Lifetime(lt) => {
|
||||
lt.ident = syn::Ident::new("_", Span::call_site())
|
||||
}
|
||||
syn::TypeParamBound::Trait(_) => {
|
||||
todo!("Anonymizing lifetimes in trait is not yet supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T::Reference(ref_ty) => {
|
||||
if let Some(lt) = ref_ty.lifetime.as_mut() {
|
||||
lt.ident = syn::Ident::new("_", Span::call_site());
|
||||
}
|
||||
(&mut *ref_ty.elem).lifetimes_anonymized();
|
||||
}
|
||||
|
||||
T::Path(ty) => {
|
||||
for seg in ty.path.segments.iter_mut() {
|
||||
match &mut seg.arguments {
|
||||
syn::PathArguments::AngleBracketed(angle) => {
|
||||
for arg in angle.args.iter_mut() {
|
||||
match arg {
|
||||
GA::Lifetime(lt) => {
|
||||
lt.ident = syn::Ident::new("_", Span::call_site());
|
||||
}
|
||||
GA::Type(ty) => ty.lifetimes_anonymized(),
|
||||
GA::Binding(b) => b.ty.lifetimes_anonymized(),
|
||||
GA::Constraint(_) | GA::Const(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::PathArguments::Parenthesized(args) => {
|
||||
for ty in args.inputs.iter_mut() {
|
||||
ty.lifetimes_anonymized();
|
||||
}
|
||||
if let syn::ReturnType::Type(_, ty) = &mut args.output {
|
||||
(&mut *ty).lifetimes_anonymized();
|
||||
}
|
||||
}
|
||||
syn::PathArguments::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These types unlikely will be used as GraphQL types.
|
||||
T::BareFn(_)
|
||||
| T::Infer(_)
|
||||
| T::Macro(_)
|
||||
| T::Never(_)
|
||||
| T::Verbatim(_)
|
||||
| T::__Nonexhaustive => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension of [`syn::Generics`] providing common function widely used by this crate for parsing.
|
||||
pub(crate) trait GenericsExt {
|
||||
/// Removes all default types out of type parameters and const parameters in these
|
||||
/// [`syn::Generics`].
|
||||
fn remove_defaults(&mut self);
|
||||
|
||||
/// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`].
|
||||
fn move_bounds_to_where_clause(&mut self);
|
||||
}
|
||||
|
||||
impl GenericsExt for syn::Generics {
|
||||
fn remove_defaults(&mut self) {
|
||||
use syn::GenericParam as P;
|
||||
|
||||
for p in &mut self.params {
|
||||
match p {
|
||||
P::Type(p) => {
|
||||
p.eq_token = None;
|
||||
p.default = None;
|
||||
}
|
||||
P::Lifetime(_) => {}
|
||||
P::Const(p) => {
|
||||
p.eq_token = None;
|
||||
p.default = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_bounds_to_where_clause(&mut self) {
|
||||
use syn::GenericParam as P;
|
||||
|
||||
let _ = self.make_where_clause();
|
||||
let where_clause = self.where_clause.as_mut().unwrap();
|
||||
|
||||
for p in &mut self.params {
|
||||
match p {
|
||||
P::Type(p) => {
|
||||
if p.colon_token.is_some() {
|
||||
p.colon_token = None;
|
||||
let bounds = mem::take(&mut p.bounds);
|
||||
let ty = &p.ident;
|
||||
where_clause.predicates.push(parse_quote! { #ty: #bounds });
|
||||
}
|
||||
}
|
||||
P::Lifetime(p) => {
|
||||
if p.colon_token.is_some() {
|
||||
p.colon_token = None;
|
||||
let bounds = mem::take(&mut p.bounds);
|
||||
let lt = &p.lifetime;
|
||||
where_clause.predicates.push(parse_quote! { #lt: #bounds });
|
||||
}
|
||||
}
|
||||
P::Const(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,7 +138,7 @@ pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<Toke
|
|||
fields,
|
||||
// NOTICE: only unit variants allow -> no generics possible
|
||||
generics: syn::Generics::default(),
|
||||
interfaces: None,
|
||||
interfaces: vec![],
|
||||
include_type_generics: true,
|
||||
generic_scalar: true,
|
||||
no_async: attrs.no_async.is_some(),
|
||||
|
|
|
@ -138,7 +138,7 @@ pub fn impl_input_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Res
|
|||
description: attrs.description.map(SpanContainer::into_inner),
|
||||
fields,
|
||||
generics: ast.generics,
|
||||
interfaces: None,
|
||||
interfaces: vec![],
|
||||
include_type_generics: true,
|
||||
generic_scalar: true,
|
||||
no_async: attrs.no_async.is_some(),
|
||||
|
|
|
@ -89,12 +89,6 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R
|
|||
// Early abort after checking all fields
|
||||
proc_macro_error::abort_if_dirty();
|
||||
|
||||
if !attrs.interfaces.is_empty() {
|
||||
attrs.interfaces.iter().for_each(|elm| {
|
||||
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(duplicates) =
|
||||
crate::util::duplicate::Duplicate::find_by_key(&fields, |field| field.name.as_str())
|
||||
{
|
||||
|
@ -124,7 +118,11 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R
|
|||
description: attrs.description.map(SpanContainer::into_inner),
|
||||
fields,
|
||||
generics: ast.generics,
|
||||
interfaces: None,
|
||||
interfaces: attrs
|
||||
.interfaces
|
||||
.into_iter()
|
||||
.map(SpanContainer::into_inner)
|
||||
.collect(),
|
||||
include_type_generics: true,
|
||||
generic_scalar: true,
|
||||
no_async: attrs.no_async.is_some(),
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::{
|
||||
common::parse::ParseBufferExt as _,
|
||||
result::GraphQLScope,
|
||||
util::{self, span_container::SpanContainer},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{self, spanned::Spanned, Data, Fields, Ident, Variant};
|
||||
use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TransparentAttributes {
|
||||
|
@ -25,12 +26,12 @@ impl syn::parse::Parse for TransparentAttributes {
|
|||
let ident: syn::Ident = input.parse()?;
|
||||
match ident.to_string().as_str() {
|
||||
"name" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.name = Some(val.value());
|
||||
}
|
||||
"description" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.description = Some(val.value());
|
||||
}
|
||||
|
@ -39,9 +40,7 @@ impl syn::parse::Parse for TransparentAttributes {
|
|||
}
|
||||
_ => return Err(syn::Error::new(ident.span(), "unknown attribute")),
|
||||
}
|
||||
if input.lookahead1().peek(syn::Token![,]) {
|
||||
input.parse::<syn::Token![,]>()?;
|
||||
}
|
||||
input.try_parse::<token::Comma>()?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
|
679
juniper_codegen/src/graphql_interface/attr.rs
Normal file
679
juniper_codegen/src/graphql_interface/attr.rs
Normal file
|
@ -0,0 +1,679 @@
|
|||
//! Code generation for `#[graphql_interface]` macro.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens as _};
|
||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
parse::{self, TypeExt as _},
|
||||
ScalarValueType,
|
||||
},
|
||||
result::GraphQLScope,
|
||||
util::{path_eq_single, span_container::SpanContainer, to_camel_case},
|
||||
};
|
||||
|
||||
use super::{
|
||||
inject_async_trait, ArgumentMeta, Definition, EnumType, Field, FieldArgument, ImplMeta,
|
||||
Implementer, ImplementerDowncast, MethodArgument, MethodMeta, TraitMeta, TraitObjectType, Type,
|
||||
};
|
||||
|
||||
/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
|
||||
const ERR: GraphQLScope = GraphQLScope::InterfaceAttr;
|
||||
|
||||
/// Expands `#[graphql_interface]` macro into generated code.
|
||||
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
||||
if let Ok(mut ast) = syn::parse2::<syn::ItemTrait>(body.clone()) {
|
||||
let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs);
|
||||
ast.attrs = parse::attr::strip("graphql_interface", ast.attrs);
|
||||
return expand_on_trait(trait_attrs, ast);
|
||||
} else if let Ok(mut ast) = syn::parse2::<syn::ItemImpl>(body) {
|
||||
if ast.trait_.is_some() {
|
||||
let impl_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs);
|
||||
ast.attrs = parse::attr::strip("graphql_interface", ast.attrs);
|
||||
return expand_on_impl(impl_attrs, ast);
|
||||
}
|
||||
}
|
||||
|
||||
Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
"#[graphql_interface] attribute is applicable to trait definitions and trait \
|
||||
implementations only",
|
||||
))
|
||||
}
|
||||
|
||||
/// Expands `#[graphql_interface]` macro placed on trait definition.
|
||||
pub fn expand_on_trait(
|
||||
attrs: Vec<syn::Attribute>,
|
||||
mut ast: syn::ItemTrait,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let meta = TraitMeta::from_attrs("graphql_interface", &attrs)?;
|
||||
|
||||
let trait_ident = &ast.ident;
|
||||
let trait_span = ast.span();
|
||||
|
||||
let name = meta
|
||||
.name
|
||||
.clone()
|
||||
.map(SpanContainer::into_inner)
|
||||
.unwrap_or_else(|| trait_ident.unraw().to_string());
|
||||
if !meta.is_internal && name.starts_with("__") {
|
||||
ERR.no_double_underscore(
|
||||
meta.name
|
||||
.as_ref()
|
||||
.map(SpanContainer::span_ident)
|
||||
.unwrap_or_else(|| trait_ident.span()),
|
||||
);
|
||||
}
|
||||
|
||||
let scalar = meta
|
||||
.scalar
|
||||
.as_ref()
|
||||
.map(|sc| {
|
||||
ast.generics
|
||||
.params
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
if let syn::GenericParam::Type(tp) = p {
|
||||
let ident = &tp.ident;
|
||||
let ty: syn::Type = parse_quote! { #ident };
|
||||
if &ty == sc.as_ref() {
|
||||
return Some(&tp.ident);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.map(|ident| ScalarValueType::ExplicitGeneric(ident.clone()))
|
||||
.unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone()))
|
||||
})
|
||||
.unwrap_or_else(|| ScalarValueType::ImplicitGeneric);
|
||||
|
||||
let mut implementers: Vec<_> = meta
|
||||
.implementers
|
||||
.iter()
|
||||
.map(|ty| Implementer {
|
||||
ty: ty.as_ref().clone(),
|
||||
downcast: None,
|
||||
context_ty: None,
|
||||
scalar: scalar.clone(),
|
||||
})
|
||||
.collect();
|
||||
for (ty, downcast) in &meta.external_downcasts {
|
||||
match implementers.iter_mut().find(|i| &i.ty == ty) {
|
||||
Some(impler) => {
|
||||
impler.downcast = Some(ImplementerDowncast::External {
|
||||
path: downcast.inner().clone(),
|
||||
});
|
||||
}
|
||||
None => err_only_implementer_downcast(&downcast.span_joined()),
|
||||
}
|
||||
}
|
||||
|
||||
proc_macro_error::abort_if_dirty();
|
||||
|
||||
let mut fields = vec![];
|
||||
for item in &mut ast.items {
|
||||
if let syn::TraitItem::Method(m) = item {
|
||||
match TraitMethod::parse(m) {
|
||||
Some(TraitMethod::Field(f)) => fields.push(f),
|
||||
Some(TraitMethod::Downcast(d)) => {
|
||||
match implementers.iter_mut().find(|i| i.ty == d.ty) {
|
||||
Some(impler) => {
|
||||
if let Some(external) = &impler.downcast {
|
||||
err_duplicate_downcast(m, external, &impler.ty);
|
||||
} else {
|
||||
impler.downcast = d.downcast;
|
||||
impler.context_ty = d.context_ty;
|
||||
}
|
||||
}
|
||||
None => err_only_implementer_downcast(&m.sig),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc_macro_error::abort_if_dirty();
|
||||
|
||||
if fields.is_empty() {
|
||||
ERR.emit_custom(trait_span, "must have at least one field");
|
||||
}
|
||||
|
||||
if !all_fields_different(&fields) {
|
||||
ERR.emit_custom(trait_span, "must have a different name for each field");
|
||||
}
|
||||
|
||||
proc_macro_error::abort_if_dirty();
|
||||
|
||||
let context = meta
|
||||
.context
|
||||
.as_ref()
|
||||
.map(|c| c.as_ref().clone())
|
||||
.or_else(|| {
|
||||
fields.iter().find_map(|f| {
|
||||
f.arguments
|
||||
.iter()
|
||||
.find_map(MethodArgument::context_ty)
|
||||
.cloned()
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
implementers
|
||||
.iter()
|
||||
.find_map(|impler| impler.context_ty.as_ref())
|
||||
.cloned()
|
||||
});
|
||||
|
||||
let is_trait_object = meta.r#dyn.is_some();
|
||||
|
||||
let is_async_trait = meta.asyncness.is_some()
|
||||
|| ast
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
syn::TraitItem::Method(m) => m.sig.asyncness,
|
||||
_ => None,
|
||||
})
|
||||
.is_some();
|
||||
let has_default_async_methods = ast.items.iter().any(|item| match item {
|
||||
syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let ty = if is_trait_object {
|
||||
Type::TraitObject(Box::new(TraitObjectType::new(
|
||||
&ast,
|
||||
&meta,
|
||||
scalar.clone(),
|
||||
context.clone(),
|
||||
)))
|
||||
} else {
|
||||
Type::Enum(Box::new(EnumType::new(
|
||||
&ast,
|
||||
&meta,
|
||||
&implementers,
|
||||
scalar.clone(),
|
||||
)))
|
||||
};
|
||||
|
||||
let generated_code = Definition {
|
||||
ty,
|
||||
|
||||
name,
|
||||
description: meta.description.map(SpanContainer::into_inner),
|
||||
|
||||
context,
|
||||
scalar: scalar.clone(),
|
||||
|
||||
fields,
|
||||
implementers,
|
||||
};
|
||||
|
||||
// Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used.
|
||||
if is_trait_object {
|
||||
ast.attrs.push(parse_quote! {
|
||||
#[allow(unused_qualifications, clippy::type_repetition_in_bounds)]
|
||||
});
|
||||
|
||||
let scalar_ty = scalar.generic_ty();
|
||||
if !scalar.is_explicit_generic() {
|
||||
let default_ty = scalar.default_ty();
|
||||
ast.generics
|
||||
.params
|
||||
.push(parse_quote! { #scalar_ty = #default_ty });
|
||||
}
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { #scalar_ty: ::juniper::ScalarValue });
|
||||
ast.supertraits
|
||||
.push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> });
|
||||
}
|
||||
|
||||
if is_async_trait {
|
||||
if has_default_async_methods {
|
||||
// Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits
|
||||
ast.supertraits.push(parse_quote! { Sync });
|
||||
}
|
||||
inject_async_trait(
|
||||
&mut ast.attrs,
|
||||
ast.items.iter_mut().filter_map(|i| {
|
||||
if let syn::TraitItem::Method(m) = i {
|
||||
Some(&mut m.sig)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
&ast.generics,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#ast
|
||||
|
||||
#generated_code
|
||||
})
|
||||
}
|
||||
|
||||
/// Expands `#[graphql_interface]` macro placed on trait implementation block.
|
||||
pub fn expand_on_impl(
|
||||
attrs: Vec<syn::Attribute>,
|
||||
mut ast: syn::ItemImpl,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let meta = ImplMeta::from_attrs("graphql_interface", &attrs)?;
|
||||
|
||||
let is_async_trait = meta.asyncness.is_some()
|
||||
|| ast
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
syn::ImplItem::Method(m) => m.sig.asyncness,
|
||||
_ => None,
|
||||
})
|
||||
.is_some();
|
||||
|
||||
let is_trait_object = meta.r#dyn.is_some();
|
||||
|
||||
if is_trait_object {
|
||||
let scalar = meta
|
||||
.scalar
|
||||
.as_ref()
|
||||
.map(|sc| {
|
||||
ast.generics
|
||||
.params
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
if let syn::GenericParam::Type(tp) = p {
|
||||
let ident = &tp.ident;
|
||||
let ty: syn::Type = parse_quote! { #ident };
|
||||
if &ty == sc.as_ref() {
|
||||
return Some(&tp.ident);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.map(|ident| ScalarValueType::ExplicitGeneric(ident.clone()))
|
||||
.unwrap_or_else(|| ScalarValueType::Concrete(sc.as_ref().clone()))
|
||||
})
|
||||
.unwrap_or_else(|| ScalarValueType::ImplicitGeneric);
|
||||
|
||||
ast.attrs.push(parse_quote! {
|
||||
#[allow(unused_qualifications, clippy::type_repetition_in_bounds)]
|
||||
});
|
||||
|
||||
if scalar.is_implicit_generic() {
|
||||
ast.generics.params.push(parse_quote! { #scalar });
|
||||
}
|
||||
if scalar.is_generic() {
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync });
|
||||
}
|
||||
|
||||
if !scalar.is_explicit_generic() {
|
||||
let (_, trait_path, _) = ast.trait_.as_mut().unwrap();
|
||||
let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments;
|
||||
if let syn::PathArguments::None = trait_params {
|
||||
*trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> });
|
||||
}
|
||||
if let syn::PathArguments::AngleBracketed(a) = trait_params {
|
||||
a.args.push(parse_quote! { #scalar });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_async_trait {
|
||||
inject_async_trait(
|
||||
&mut ast.attrs,
|
||||
ast.items.iter_mut().filter_map(|i| {
|
||||
if let syn::ImplItem::Method(m) = i {
|
||||
Some(&mut m.sig)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
&ast.generics,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(quote! { #ast })
|
||||
}
|
||||
|
||||
/// Representation of parsed Rust trait method for `#[graphql_interface]` macro code generation.
|
||||
enum TraitMethod {
|
||||
/// Method represents a [`Field`] of [GraphQL interface][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
Field(Field),
|
||||
|
||||
/// Method represents a custom downcasting function into the [`Implementer`] of
|
||||
/// [GraphQL interface][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
Downcast(Box<Implementer>),
|
||||
}
|
||||
|
||||
impl TraitMethod {
|
||||
/// Parses this [`TraitMethod`] from the given trait method definition.
|
||||
///
|
||||
/// Returns [`None`] if the trait method marked with `#[graphql_interface(ignore)]` attribute,
|
||||
/// or parsing fails.
|
||||
#[must_use]
|
||||
fn parse(method: &mut syn::TraitItemMethod) -> Option<Self> {
|
||||
let method_attrs = method.attrs.clone();
|
||||
|
||||
// Remove repeated attributes from the method, to omit incorrect expansion.
|
||||
method.attrs = mem::take(&mut method.attrs)
|
||||
.into_iter()
|
||||
.filter(|attr| !path_eq_single(&attr.path, "graphql_interface"))
|
||||
.collect();
|
||||
|
||||
let meta = MethodMeta::from_attrs("graphql_interface", &method_attrs)
|
||||
.map_err(|e| proc_macro_error::emit_error!(e))
|
||||
.ok()?;
|
||||
|
||||
if meta.ignore.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if meta.downcast.is_some() {
|
||||
return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?)));
|
||||
}
|
||||
|
||||
Some(Self::Field(Self::parse_field(method, meta)?))
|
||||
}
|
||||
|
||||
/// Parses [`TraitMethod::Downcast`] from the given trait method definition.
|
||||
///
|
||||
/// Returns [`None`] if parsing fails.
|
||||
#[must_use]
|
||||
fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option<Implementer> {
|
||||
let method_ident = &method.sig.ident;
|
||||
|
||||
let ty = parse::downcaster::output_type(&method.sig.output)
|
||||
.map_err(|span| {
|
||||
ERR.emit_custom(
|
||||
span,
|
||||
"expects trait method return type to be `Option<&ImplementerType>` only",
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
let context_ty = parse::downcaster::context_ty(&method.sig)
|
||||
.map_err(|span| {
|
||||
ERR.emit_custom(
|
||||
span,
|
||||
"expects trait method to accept `&self` only and, optionally, `&Context`",
|
||||
)
|
||||
})
|
||||
.ok()?;
|
||||
if let Some(is_async) = &method.sig.asyncness {
|
||||
ERR.emit_custom(
|
||||
is_async.span(),
|
||||
"async downcast to interface implementer is not supported",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let downcast = ImplementerDowncast::Method {
|
||||
name: method_ident.clone(),
|
||||
with_context: context_ty.is_some(),
|
||||
};
|
||||
|
||||
Some(Implementer {
|
||||
ty,
|
||||
downcast: Some(downcast),
|
||||
context_ty,
|
||||
scalar: ScalarValueType::ImplicitGeneric,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses [`TraitMethod::Field`] from the given trait method definition.
|
||||
///
|
||||
/// Returns [`None`] if parsing fails.
|
||||
#[must_use]
|
||||
fn parse_field(method: &mut syn::TraitItemMethod, meta: MethodMeta) -> Option<Field> {
|
||||
let method_ident = &method.sig.ident;
|
||||
|
||||
let name = meta
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|m| m.as_ref().value())
|
||||
.unwrap_or_else(|| to_camel_case(&method_ident.unraw().to_string()));
|
||||
if name.starts_with("__") {
|
||||
ERR.no_double_underscore(
|
||||
meta.name
|
||||
.as_ref()
|
||||
.map(SpanContainer::span_ident)
|
||||
.unwrap_or_else(|| method_ident.span()),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let arguments = {
|
||||
if method.sig.inputs.is_empty() {
|
||||
return err_no_method_receiver(&method.sig.inputs);
|
||||
}
|
||||
let mut args_iter = method.sig.inputs.iter_mut();
|
||||
match args_iter.next().unwrap() {
|
||||
syn::FnArg::Receiver(rcv) => {
|
||||
if rcv.reference.is_none() || rcv.mutability.is_some() {
|
||||
return err_invalid_method_receiver(rcv);
|
||||
}
|
||||
}
|
||||
syn::FnArg::Typed(arg) => {
|
||||
if let syn::Pat::Ident(a) = &*arg.pat {
|
||||
if a.ident.to_string().as_str() != "self" {
|
||||
return err_invalid_method_receiver(arg);
|
||||
}
|
||||
}
|
||||
return err_no_method_receiver(arg);
|
||||
}
|
||||
};
|
||||
args_iter
|
||||
.filter_map(|arg| match arg {
|
||||
syn::FnArg::Receiver(_) => None,
|
||||
syn::FnArg::Typed(arg) => Self::parse_field_argument(arg),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut ty = match &method.sig.output {
|
||||
syn::ReturnType::Default => parse_quote! { () },
|
||||
syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(),
|
||||
};
|
||||
ty.lifetimes_anonymized();
|
||||
|
||||
let description = meta.description.as_ref().map(|d| d.as_ref().value());
|
||||
let deprecated = meta
|
||||
.deprecated
|
||||
.as_ref()
|
||||
.map(|d| d.as_ref().as_ref().map(syn::LitStr::value));
|
||||
|
||||
Some(Field {
|
||||
name,
|
||||
ty,
|
||||
description,
|
||||
deprecated,
|
||||
method: method_ident.clone(),
|
||||
arguments,
|
||||
is_async: method.sig.asyncness.is_some(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses [`MethodArgument`] from the given trait method argument definition.
|
||||
///
|
||||
/// Returns [`None`] if parsing fails.
|
||||
#[must_use]
|
||||
fn parse_field_argument(argument: &mut syn::PatType) -> Option<MethodArgument> {
|
||||
let argument_attrs = argument.attrs.clone();
|
||||
|
||||
// Remove repeated attributes from the method, to omit incorrect expansion.
|
||||
argument.attrs = mem::take(&mut argument.attrs)
|
||||
.into_iter()
|
||||
.filter(|attr| !path_eq_single(&attr.path, "graphql_interface"))
|
||||
.collect();
|
||||
|
||||
let meta = ArgumentMeta::from_attrs("graphql_interface", &argument_attrs)
|
||||
.map_err(|e| proc_macro_error::emit_error!(e))
|
||||
.ok()?;
|
||||
|
||||
if meta.context.is_some() {
|
||||
return Some(MethodArgument::Context(argument.ty.unreferenced().clone()));
|
||||
}
|
||||
if meta.executor.is_some() {
|
||||
return Some(MethodArgument::Executor);
|
||||
}
|
||||
if let syn::Pat::Ident(name) = &*argument.pat {
|
||||
let arg = match name.ident.unraw().to_string().as_str() {
|
||||
"context" | "ctx" => {
|
||||
Some(MethodArgument::Context(argument.ty.unreferenced().clone()))
|
||||
}
|
||||
"executor" => Some(MethodArgument::Executor),
|
||||
_ => None,
|
||||
};
|
||||
if arg.is_some() {
|
||||
ensure_no_regular_field_argument_meta(&meta)?;
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
let name = if let Some(name) = meta.name.as_ref() {
|
||||
name.as_ref().value()
|
||||
} else if let syn::Pat::Ident(name) = &*argument.pat {
|
||||
to_camel_case(&name.ident.unraw().to_string())
|
||||
} else {
|
||||
ERR.custom(
|
||||
argument.pat.span(),
|
||||
"trait method argument should be declared as a single identifier",
|
||||
)
|
||||
.note(String::from(
|
||||
"use `#[graphql_interface(name = ...)]` attribute to specify custom argument's \
|
||||
name without requiring it being a single identifier",
|
||||
))
|
||||
.emit();
|
||||
return None;
|
||||
};
|
||||
if name.starts_with("__") {
|
||||
ERR.no_double_underscore(
|
||||
meta.name
|
||||
.as_ref()
|
||||
.map(SpanContainer::span_ident)
|
||||
.unwrap_or_else(|| argument.pat.span()),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(MethodArgument::Regular(FieldArgument {
|
||||
name,
|
||||
ty: argument.ty.as_ref().clone(),
|
||||
description: meta.description.as_ref().map(|d| d.as_ref().value()),
|
||||
default: meta.default.as_ref().map(|v| v.as_ref().clone()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the given [`ArgumentMeta`] doesn't contain arguments related to
|
||||
/// [`FieldArgument`].
|
||||
#[must_use]
|
||||
fn ensure_no_regular_field_argument_meta(meta: &ArgumentMeta) -> Option<()> {
|
||||
if let Some(span) = &meta.name {
|
||||
return err_disallowed_attr(&span, "name");
|
||||
}
|
||||
if let Some(span) = &meta.description {
|
||||
return err_disallowed_attr(&span, "description");
|
||||
}
|
||||
if let Some(span) = &meta.default {
|
||||
return err_disallowed_attr(&span, "default");
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Emits "argument is not allowed" [`syn::Error`] for the given `arg` pointing to the given `span`.
|
||||
#[must_use]
|
||||
fn err_disallowed_attr<T, S: Spanned>(span: &S, arg: &str) -> Option<T> {
|
||||
ERR.custom(
|
||||
span.span(),
|
||||
format!(
|
||||
"attribute argument `#[graphql_interface({} = ...)]` is not allowed here",
|
||||
arg,
|
||||
),
|
||||
)
|
||||
.emit();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given `span`.
|
||||
#[must_use]
|
||||
fn err_invalid_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
||||
ERR.custom(
|
||||
span.span(),
|
||||
"trait method receiver can only be a shared reference `&self`",
|
||||
)
|
||||
.emit();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Emits "no trait method receiver" [`syn::Error`] pointing to the given `span`.
|
||||
#[must_use]
|
||||
fn err_no_method_receiver<T, S: Spanned>(span: &S) -> Option<T> {
|
||||
ERR.custom(
|
||||
span.span(),
|
||||
"trait method should have a shared reference receiver `&self`",
|
||||
)
|
||||
.emit();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given `span`.
|
||||
fn err_only_implementer_downcast<S: Spanned>(span: &S) {
|
||||
ERR.custom(
|
||||
span.span(),
|
||||
"downcasting is possible only to interface implementers",
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
|
||||
/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and `external`
|
||||
/// [`ImplementerDowncast`] function.
|
||||
fn err_duplicate_downcast(
|
||||
method: &syn::TraitItemMethod,
|
||||
external: &ImplementerDowncast,
|
||||
impler_ty: &syn::Type,
|
||||
) {
|
||||
let external = match external {
|
||||
ImplementerDowncast::External { path } => path,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
ERR.custom(
|
||||
method.span(),
|
||||
format!(
|
||||
"trait method `{}` conflicts with the external downcast function `{}` declared on the \
|
||||
trait to downcast into the implementer type `{}`",
|
||||
method.sig.ident,
|
||||
external.to_token_stream(),
|
||||
impler_ty.to_token_stream(),
|
||||
),
|
||||
)
|
||||
.note(String::from(
|
||||
"use `#[graphql_interface(ignore)]` attribute argument to ignore this trait method for \
|
||||
interface implementers downcasting",
|
||||
))
|
||||
.emit()
|
||||
}
|
||||
|
||||
/// Checks whether all [GraphQL interface][1] fields have different names.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
|
||||
fn all_fields_different(fields: &[Field]) -> bool {
|
||||
let mut names: Vec<_> = fields.iter().map(|f| &f.name).collect();
|
||||
names.dedup();
|
||||
names.len() == fields.len()
|
||||
}
|
2316
juniper_codegen/src/graphql_interface/mod.rs
Normal file
2316
juniper_codegen/src/graphql_interface/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,15 @@
|
|||
//! Code generation for `#[graphql_union]` macro.
|
||||
|
||||
use std::{mem, ops::Deref as _};
|
||||
use std::mem;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens as _};
|
||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
|
||||
|
||||
use crate::{
|
||||
common::parse,
|
||||
result::GraphQLScope,
|
||||
util::{path_eq_single, span_container::SpanContainer, unparenthesize},
|
||||
util::{path_eq_single, span_container::SpanContainer},
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -27,17 +28,8 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
|
|||
"#[graphql_union] attribute is applicable to trait definitions only",
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut trait_attrs = Vec::with_capacity(ast.attrs.len() + 1);
|
||||
trait_attrs.push(parse_quote! { #[graphql_union(#attr_args)] });
|
||||
trait_attrs.extend_from_slice(&ast.attrs);
|
||||
|
||||
// Remove repeated attributes from the definition, to omit duplicate expansion.
|
||||
ast.attrs = ast
|
||||
.attrs
|
||||
.into_iter()
|
||||
.filter(|attr| !path_eq_single(&attr.path, "graphql_union"))
|
||||
.collect();
|
||||
let trait_attrs = parse::attr::unite(("graphql_union", &attr_args), &ast.attrs);
|
||||
ast.attrs = parse::attr::strip("graphql_union", ast.attrs);
|
||||
|
||||
let meta = UnionMeta::from_attrs("graphql_union", &trait_attrs)?;
|
||||
|
||||
|
@ -98,7 +90,6 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStr
|
|||
scalar: meta.scalar.map(SpanContainer::into_inner),
|
||||
generics: ast.generics.clone(),
|
||||
variants,
|
||||
span: trait_span,
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
|
@ -149,7 +140,7 @@ fn parse_variant_from_trait_method(
|
|||
let method_span = method.sig.span();
|
||||
let method_ident = &method.sig.ident;
|
||||
|
||||
let ty = parse_trait_method_output_type(&method.sig)
|
||||
let ty = parse::downcaster::output_type(&method.sig.output)
|
||||
.map_err(|span| {
|
||||
ERR.emit_custom(
|
||||
span,
|
||||
|
@ -157,7 +148,7 @@ fn parse_variant_from_trait_method(
|
|||
)
|
||||
})
|
||||
.ok()?;
|
||||
let method_context_ty = parse_trait_method_input_args(&method.sig)
|
||||
let method_context_ty = parse::downcaster::context_ty(&method.sig)
|
||||
.map_err(|span| {
|
||||
ERR.emit_custom(
|
||||
span,
|
||||
|
@ -168,7 +159,7 @@ fn parse_variant_from_trait_method(
|
|||
if let Some(is_async) = &method.sig.asyncness {
|
||||
ERR.emit_custom(
|
||||
is_async.span(),
|
||||
"doesn't support async union variants resolvers yet",
|
||||
"async downcast to union variants is not supported",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
@ -220,85 +211,3 @@ fn parse_variant_from_trait_method(
|
|||
span: method_span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses type of [GraphQL union][1] variant from the return type of trait method.
|
||||
///
|
||||
/// If return type is invalid, then returns the [`Span`] to display the corresponding error at.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
fn parse_trait_method_output_type(sig: &syn::Signature) -> Result<syn::Type, Span> {
|
||||
let ret_ty = match &sig.output {
|
||||
syn::ReturnType::Type(_, ty) => ty.deref(),
|
||||
_ => return Err(sig.span()),
|
||||
};
|
||||
|
||||
let path = match unparenthesize(ret_ty) {
|
||||
syn::Type::Path(syn::TypePath { qself: None, path }) => path,
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
|
||||
let (ident, args) = match path.segments.last() {
|
||||
Some(syn::PathSegment {
|
||||
ident,
|
||||
arguments: syn::PathArguments::AngleBracketed(generic),
|
||||
}) => (ident, &generic.args),
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
|
||||
if ident.unraw() != "Option" {
|
||||
return Err(ret_ty.span());
|
||||
}
|
||||
|
||||
if args.len() != 1 {
|
||||
return Err(ret_ty.span());
|
||||
}
|
||||
let var_ty = match args.first() {
|
||||
Some(syn::GenericArgument::Type(inner_ty)) => match unparenthesize(inner_ty) {
|
||||
syn::Type::Reference(inner_ty) => {
|
||||
if inner_ty.mutability.is_some() {
|
||||
return Err(inner_ty.span());
|
||||
}
|
||||
unparenthesize(inner_ty.elem.deref()).clone()
|
||||
}
|
||||
_ => return Err(ret_ty.span()),
|
||||
},
|
||||
_ => return Err(ret_ty.span()),
|
||||
};
|
||||
Ok(var_ty)
|
||||
}
|
||||
|
||||
/// Parses trait method input arguments and validates them to be acceptable for resolving into
|
||||
/// [GraphQL union][1] variant type. Returns type of the context used in input arguments, if any.
|
||||
///
|
||||
/// If input arguments are invalid, then returns the [`Span`] to display the corresponding error at.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
fn parse_trait_method_input_args(sig: &syn::Signature) -> Result<Option<syn::Type>, Span> {
|
||||
match sig.receiver() {
|
||||
Some(syn::FnArg::Receiver(rcv)) => {
|
||||
if rcv.reference.is_none() || rcv.mutability.is_some() {
|
||||
return Err(rcv.span());
|
||||
}
|
||||
}
|
||||
_ => return Err(sig.span()),
|
||||
}
|
||||
|
||||
if sig.inputs.len() > 2 {
|
||||
return Err(sig.inputs.span());
|
||||
}
|
||||
|
||||
let second_arg_ty = match sig.inputs.iter().nth(1) {
|
||||
Some(syn::FnArg::Typed(arg)) => arg.ty.deref(),
|
||||
None => return Ok(None),
|
||||
_ => return Err(sig.inputs.span()),
|
||||
};
|
||||
match unparenthesize(second_arg_ty) {
|
||||
syn::Type::Reference(ref_ty) => {
|
||||
if ref_ty.mutability.is_some() {
|
||||
return Err(ref_ty.span());
|
||||
}
|
||||
Ok(Some(ref_ty.elem.deref().clone()))
|
||||
}
|
||||
ty => Err(ty.span()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,7 @@ use quote::{quote, ToTokens};
|
|||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields};
|
||||
|
||||
use crate::{
|
||||
result::GraphQLScope,
|
||||
util::{span_container::SpanContainer, unparenthesize},
|
||||
common::parse::TypeExt as _, result::GraphQLScope, util::span_container::SpanContainer,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -85,7 +84,6 @@ fn expand_enum(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
|
|||
scalar: meta.scalar.map(SpanContainer::into_inner),
|
||||
generics: ast.generics,
|
||||
variants,
|
||||
span: enum_span,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -115,7 +113,7 @@ fn parse_variant_from_enum_variant(
|
|||
let mut iter = fields.unnamed.iter();
|
||||
let first = iter.next().unwrap();
|
||||
if iter.next().is_none() {
|
||||
Ok(unparenthesize(&first.ty).clone())
|
||||
Ok(first.ty.unparenthesized().clone())
|
||||
} else {
|
||||
Err(fields.span())
|
||||
}
|
||||
|
@ -214,6 +212,5 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<UnionDefinition> {
|
|||
scalar: meta.scalar.map(SpanContainer::into_inner),
|
||||
generics: ast.generics,
|
||||
variants,
|
||||
span: struct_span,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,60 +13,16 @@ use syn::{
|
|||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
spanned::Spanned as _,
|
||||
token,
|
||||
};
|
||||
|
||||
use crate::util::{filter_attrs, get_doc_comment, span_container::SpanContainer, OptionExt as _};
|
||||
|
||||
/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of
|
||||
/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related
|
||||
/// to the `$another` struct (a later one).
|
||||
///
|
||||
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
|
||||
/// By default, [`SpanContainer::span_ident`] is used.
|
||||
macro_rules! try_merge_opt {
|
||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||
if let Some(v) = $self.$field {
|
||||
$another
|
||||
.$field
|
||||
.replace(v)
|
||||
.none_or_else(|dup| dup_attr_err(dup.$span()))?;
|
||||
}
|
||||
$another.$field
|
||||
}};
|
||||
|
||||
($field:ident: $self:ident, $another:ident) => {
|
||||
try_merge_opt!($field: $self, $another => span_ident)
|
||||
};
|
||||
}
|
||||
|
||||
/// Attempts to merge a [`HashMap`]ed `$field` of a `$self` struct with the same `$field` of
|
||||
/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error
|
||||
/// with a [`Span`] related to the `$another` struct (a later one).
|
||||
///
|
||||
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
|
||||
/// By default, [`SpanContainer::span_ident`] is used.
|
||||
macro_rules! try_merge_hashmap {
|
||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||
if !$self.$field.is_empty() {
|
||||
for (ty, rslvr) in $self.$field {
|
||||
$another
|
||||
.$field
|
||||
.insert(ty, rslvr)
|
||||
.none_or_else(|dup| dup_attr_err(dup.$span()))?;
|
||||
}
|
||||
}
|
||||
$another.$field
|
||||
}};
|
||||
|
||||
($field:ident: $self:ident, $another:ident) => {
|
||||
try_merge_hashmap!($field: $self, $another => span_ident)
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates and returns duplication error pointing to the given `span`.
|
||||
fn dup_attr_err(span: Span) -> syn::Error {
|
||||
syn::Error::new(span, "duplicated attribute")
|
||||
}
|
||||
use crate::{
|
||||
common::parse::{
|
||||
attr::{err, OptionExt as _},
|
||||
ParseBufferExt as _,
|
||||
},
|
||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer},
|
||||
};
|
||||
|
||||
/// Helper alias for the type of [`UnionMeta::external_resolvers`] field.
|
||||
type UnionMetaResolvers = HashMap<syn::Type, SpanContainer<syn::ExprPath>>;
|
||||
|
@ -131,10 +87,10 @@ impl Parse for UnionMeta {
|
|||
let mut output = Self::default();
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: syn::Ident = input.parse()?;
|
||||
let ident = input.parse::<syn::Ident>()?;
|
||||
match ident.to_string().as_str() {
|
||||
"name" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let name = input.parse::<syn::LitStr>()?;
|
||||
output
|
||||
.name
|
||||
|
@ -143,10 +99,10 @@ impl Parse for UnionMeta {
|
|||
Some(name.span()),
|
||||
name.value(),
|
||||
))
|
||||
.none_or_else(|_| dup_attr_err(ident.span()))?
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"desc" | "description" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let desc = input.parse::<syn::LitStr>()?;
|
||||
output
|
||||
.description
|
||||
|
@ -155,45 +111,43 @@ impl Parse for UnionMeta {
|
|||
Some(desc.span()),
|
||||
desc.value(),
|
||||
))
|
||||
.none_or_else(|_| dup_attr_err(ident.span()))?
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"ctx" | "context" | "Context" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let ctx = input.parse::<syn::Type>()?;
|
||||
output
|
||||
.context
|
||||
.replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx))
|
||||
.none_or_else(|_| dup_attr_err(ident.span()))?
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"scalar" | "Scalar" | "ScalarValue" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let scl = input.parse::<syn::Type>()?;
|
||||
output
|
||||
.scalar
|
||||
.replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
|
||||
.none_or_else(|_| dup_attr_err(ident.span()))?
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
"on" => {
|
||||
let ty = input.parse::<syn::Type>()?;
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let rslvr = input.parse::<syn::ExprPath>()?;
|
||||
let rslvr_spanned = SpanContainer::new(ident.span(), Some(ty.span()), rslvr);
|
||||
let rslvr_span = rslvr_spanned.span_joined();
|
||||
output
|
||||
.external_resolvers
|
||||
.insert(ty, rslvr_spanned)
|
||||
.none_or_else(|_| dup_attr_err(rslvr_span))?
|
||||
.none_or_else(|_| err::dup_arg(rslvr_span))?
|
||||
}
|
||||
"internal" => {
|
||||
output.is_internal = true;
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new(ident.span(), "unknown attribute"));
|
||||
name => {
|
||||
return Err(err::unknown_arg(&ident, name));
|
||||
}
|
||||
}
|
||||
if input.lookahead1().peek(syn::Token![,]) {
|
||||
input.parse::<syn::Token![,]>()?;
|
||||
}
|
||||
input.try_parse::<token::Comma>()?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
@ -201,19 +155,22 @@ impl Parse for UnionMeta {
|
|||
}
|
||||
|
||||
impl UnionMeta {
|
||||
/// Tries to merge two [`UnionMeta`]s into single one, reporting about duplicates, if any.
|
||||
/// Tries to merge two [`UnionMeta`]s into a single one, reporting about duplicates, if any.
|
||||
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
name: try_merge_opt!(name: self, another),
|
||||
description: try_merge_opt!(description: self, another),
|
||||
context: try_merge_opt!(context: self, another),
|
||||
scalar: try_merge_opt!(scalar: self, another),
|
||||
external_resolvers: try_merge_hashmap!(external_resolvers: self, another => span_joined),
|
||||
external_resolvers: try_merge_hashmap!(
|
||||
external_resolvers: self, another => span_joined
|
||||
),
|
||||
is_internal: self.is_internal || another.is_internal,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses [`UnionMeta`] from the given multiple `name`d attributes placed on type definition.
|
||||
/// Parses [`UnionMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a type
|
||||
/// definition.
|
||||
pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
|
||||
let mut meta = filter_attrs(name, attrs)
|
||||
.map(|attr| attr.parse_args())
|
||||
|
@ -254,27 +211,25 @@ impl Parse for UnionVariantMeta {
|
|||
let mut output = Self::default();
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: syn::Ident = input.parse()?;
|
||||
let ident = input.parse::<syn::Ident>()?;
|
||||
match ident.to_string().as_str() {
|
||||
"ignore" | "skip" => output
|
||||
.ignore
|
||||
.replace(SpanContainer::new(ident.span(), None, ident.clone()))
|
||||
.none_or_else(|_| dup_attr_err(ident.span()))?,
|
||||
.none_or_else(|_| err::dup_arg(&ident))?,
|
||||
"with" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let rslvr = input.parse::<syn::ExprPath>()?;
|
||||
output
|
||||
.external_resolver
|
||||
.replace(SpanContainer::new(ident.span(), Some(rslvr.span()), rslvr))
|
||||
.none_or_else(|_| dup_attr_err(ident.span()))?
|
||||
.none_or_else(|_| err::dup_arg(&ident))?
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new(ident.span(), "unknown attribute"));
|
||||
name => {
|
||||
return Err(err::unknown_arg(&ident, name));
|
||||
}
|
||||
}
|
||||
if input.lookahead1().peek(syn::Token![,]) {
|
||||
input.parse::<syn::Token![,]>()?;
|
||||
}
|
||||
input.try_parse::<token::Comma>()?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
@ -282,7 +237,7 @@ impl Parse for UnionVariantMeta {
|
|||
}
|
||||
|
||||
impl UnionVariantMeta {
|
||||
/// Tries to merge two [`UnionVariantMeta`]s into single one, reporting about duplicates, if
|
||||
/// Tries to merge two [`UnionVariantMeta`]s into a single one, reporting about duplicates, if
|
||||
/// any.
|
||||
fn try_merge(self, mut another: Self) -> syn::Result<Self> {
|
||||
Ok(Self {
|
||||
|
@ -291,7 +246,7 @@ impl UnionVariantMeta {
|
|||
})
|
||||
}
|
||||
|
||||
/// Parses [`UnionVariantMeta`] from the given multiple `name`d attributes placed on
|
||||
/// Parses [`UnionVariantMeta`] from the given multiple `name`d [`syn::Attribute`]s placed on a
|
||||
/// variant/field/method definition.
|
||||
pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result<Self> {
|
||||
filter_attrs(name, attrs)
|
||||
|
@ -356,6 +311,8 @@ struct UnionDefinition {
|
|||
pub ty: syn::Type,
|
||||
|
||||
/// Generics of the Rust type that this [GraphQL union][1] is implemented for.
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
pub generics: syn::Generics,
|
||||
|
||||
/// Indicator whether code should be generated for a trait object, rather than for a regular
|
||||
|
@ -390,11 +347,6 @@ struct UnionDefinition {
|
|||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
pub variants: Vec<UnionVariantDefinition>,
|
||||
|
||||
/// [`Span`] that points to the Rust source code which defines this [GraphQL union][1].
|
||||
///
|
||||
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl ToTokens for UnionDefinition {
|
||||
|
@ -432,7 +384,7 @@ impl ToTokens for UnionDefinition {
|
|||
let var_check = &var.resolver_check;
|
||||
quote! {
|
||||
if #var_check {
|
||||
return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(&())
|
||||
return <#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)
|
||||
.unwrap().to_string();
|
||||
}
|
||||
}
|
||||
|
@ -442,15 +394,15 @@ impl ToTokens for UnionDefinition {
|
|||
let resolve_into_type = self.variants.iter().zip(match_resolves.iter()).map(|(var, expr)| {
|
||||
let var_ty = &var.ty;
|
||||
|
||||
let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(&())) };
|
||||
let get_name = quote! { (<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info)) };
|
||||
quote! {
|
||||
if type_name == #get_name.unwrap() {
|
||||
return ::juniper::IntoResolvable::into(
|
||||
{ #expr },
|
||||
executor.context()
|
||||
executor.context(),
|
||||
)
|
||||
.and_then(|res| match res {
|
||||
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(&(), &r),
|
||||
Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r),
|
||||
None => Ok(::juniper::Value::null()),
|
||||
});
|
||||
}
|
||||
|
@ -464,19 +416,19 @@ impl ToTokens for UnionDefinition {
|
|||
let var_ty = &var.ty;
|
||||
|
||||
let get_name = quote! {
|
||||
(<#var_ty as ::juniper::GraphQLType<#scalar>>::name(&()))
|
||||
(<#var_ty as ::juniper::GraphQLType<#scalar>>::name(info))
|
||||
};
|
||||
quote! {
|
||||
if type_name == #get_name.unwrap() {
|
||||
let res = ::juniper::IntoResolvable::into(
|
||||
{ #expr },
|
||||
executor.context()
|
||||
executor.context(),
|
||||
);
|
||||
return Box::pin(async move {
|
||||
match res? {
|
||||
Some((ctx, r)) => {
|
||||
let subexec = executor.replaced_context(ctx);
|
||||
subexec.resolve_with_ctx_async(&(), &r).await
|
||||
subexec.resolve_with_ctx_async(info, &r).await
|
||||
},
|
||||
None => Ok(::juniper::Value::null()),
|
||||
}
|
||||
|
@ -494,8 +446,7 @@ impl ToTokens for UnionDefinition {
|
|||
if self.scalar.is_none() {
|
||||
ext_generics.params.push(parse_quote! { #scalar });
|
||||
ext_generics
|
||||
.where_clause
|
||||
.get_or_insert_with(|| parse_quote! { where })
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
.push(parse_quote! { #scalar: ::juniper::ScalarValue });
|
||||
}
|
||||
|
@ -531,10 +482,10 @@ impl ToTokens for UnionDefinition {
|
|||
) -> ::juniper::meta::MetaType<'r, #scalar>
|
||||
where #scalar: 'r,
|
||||
{
|
||||
let types = &[
|
||||
#( registry.get_type::<&#var_types>(&(())), )*
|
||||
let types = [
|
||||
#( registry.get_type::<#var_types>(info), )*
|
||||
];
|
||||
registry.build_union_type::<#ty_full>(info, types)
|
||||
registry.build_union_type::<#ty_full>(info, &types)
|
||||
#description
|
||||
.into_meta()
|
||||
}
|
||||
|
@ -556,7 +507,7 @@ impl ToTokens for UnionDefinition {
|
|||
fn concrete_type_name(
|
||||
&self,
|
||||
context: &Self::Context,
|
||||
_: &Self::TypeInfo,
|
||||
info: &Self::TypeInfo,
|
||||
) -> String {
|
||||
#( #match_names )*
|
||||
panic!(
|
||||
|
@ -568,7 +519,7 @@ impl ToTokens for UnionDefinition {
|
|||
|
||||
fn resolve_into_type(
|
||||
&self,
|
||||
_: &Self::TypeInfo,
|
||||
info: &Self::TypeInfo,
|
||||
type_name: &str,
|
||||
_: Option<&[::juniper::Selection<#scalar>]>,
|
||||
executor: &::juniper::Executor<Self::Context, #scalar>,
|
||||
|
@ -590,7 +541,7 @@ impl ToTokens for UnionDefinition {
|
|||
{
|
||||
fn resolve_into_type_async<'b>(
|
||||
&'b self,
|
||||
_: &'b Self::TypeInfo,
|
||||
info: &'b Self::TypeInfo,
|
||||
type_name: &str,
|
||||
_: Option<&'b [::juniper::Selection<'b, #scalar>]>,
|
||||
executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar>
|
||||
|
|
|
@ -201,18 +201,12 @@ fn create(
|
|||
description: _impl.description,
|
||||
fields,
|
||||
generics: _impl.generics.clone(),
|
||||
interfaces: if !_impl.attrs.interfaces.is_empty() {
|
||||
Some(
|
||||
_impl
|
||||
.attrs
|
||||
.interfaces
|
||||
.into_iter()
|
||||
.map(SpanContainer::into_inner)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
interfaces: _impl
|
||||
.attrs
|
||||
.interfaces
|
||||
.into_iter()
|
||||
.map(SpanContainer::into_inner)
|
||||
.collect(),
|
||||
include_type_generics: false,
|
||||
generic_scalar: false,
|
||||
no_async: _impl.attrs.no_async.is_some(),
|
||||
|
|
|
@ -26,12 +26,10 @@ fn get_first_method_arg(
|
|||
inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
|
||||
) -> Option<syn::Ident> {
|
||||
if let Some(fn_arg) = inputs.first() {
|
||||
match fn_arg {
|
||||
syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
|
||||
syn::Pat::Ident(pat_ident) => return Some(pat_ident.ident.clone()),
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
if let syn::FnArg::Typed(pat_type) = fn_arg {
|
||||
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
|
||||
return Some(pat_ident.ident.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,19 +68,11 @@ fn get_enum_type(return_type: &Option<syn::Type>) -> Option<syn::PathSegment> {
|
|||
}
|
||||
});
|
||||
|
||||
if let Some(generic_type_arg) = generic_type_arg {
|
||||
match generic_type_arg {
|
||||
syn::GenericArgument::Type(the_type) => match the_type {
|
||||
syn::Type::Path(type_path) => {
|
||||
if let Some(path_segment) =
|
||||
type_path.path.segments.first()
|
||||
{
|
||||
return Some(path_segment.clone());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
if let Some(syn::GenericArgument::Type(syn::Type::Path(type_path))) =
|
||||
generic_type_arg
|
||||
{
|
||||
if let Some(path_segment) = type_path.path.segments.first() {
|
||||
return Some(path_segment.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,13 +105,10 @@ impl syn::parse::Parse for ScalarCodegenInput {
|
|||
let custom_data_type_is_struct: bool =
|
||||
!parse_custom_scalar_value_impl.generics.params.is_empty();
|
||||
|
||||
match *parse_custom_scalar_value_impl.self_ty {
|
||||
syn::Type::Path(type_path) => {
|
||||
if let Some(path_segment) = type_path.path.segments.first() {
|
||||
impl_for_type = Some(path_segment.clone());
|
||||
}
|
||||
if let syn::Type::Path(type_path) = *parse_custom_scalar_value_impl.self_ty {
|
||||
if let Some(path_segment) = type_path.path.segments.first() {
|
||||
impl_for_type = Some(path_segment.clone());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
for impl_item in parse_custom_scalar_value_impl.items {
|
||||
|
|
|
@ -12,6 +12,102 @@ extern crate proc_macro;
|
|||
mod result;
|
||||
mod util;
|
||||
|
||||
// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
|
||||
// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
|
||||
// and so we cannot move the definition into a sub-module and use the `#[macro_export]`
|
||||
// attribute.
|
||||
/// Attempts to merge an [`Option`]ed `$field` of a `$self` struct with the same `$field` of
|
||||
/// `$another` struct. If both are [`Some`], then throws a duplication error with a [`Span`] related
|
||||
/// to the `$another` struct (a later one).
|
||||
///
|
||||
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
|
||||
/// By default, [`SpanContainer::span_ident`] is used.
|
||||
///
|
||||
/// [`Span`]: proc_macro2::Span
|
||||
/// [`SpanContainer`]: crate::util::span_container::SpanContainer
|
||||
/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
|
||||
macro_rules! try_merge_opt {
|
||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||
if let Some(v) = $self.$field {
|
||||
$another
|
||||
.$field
|
||||
.replace(v)
|
||||
.none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?;
|
||||
}
|
||||
$another.$field
|
||||
}};
|
||||
|
||||
($field:ident: $self:ident, $another:ident) => {
|
||||
try_merge_opt!($field: $self, $another => span_ident)
|
||||
};
|
||||
}
|
||||
|
||||
// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
|
||||
// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
|
||||
// and so we cannot move the definition into a sub-module and use the `#[macro_export]`
|
||||
// attribute.
|
||||
/// Attempts to merge a [`HashMap`] `$field` of a `$self` struct with the same `$field` of
|
||||
/// `$another` struct. If some [`HashMap`] entries are duplicated, then throws a duplication error
|
||||
/// with a [`Span`] related to the `$another` struct (a later one).
|
||||
///
|
||||
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
|
||||
/// By default, [`SpanContainer::span_ident`] is used.
|
||||
///
|
||||
/// [`HashMap`]: std::collections::HashMap
|
||||
/// [`Span`]: proc_macro2::Span
|
||||
/// [`SpanContainer`]: crate::util::span_container::SpanContainer
|
||||
/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
|
||||
macro_rules! try_merge_hashmap {
|
||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||
if !$self.$field.is_empty() {
|
||||
for (ty, rslvr) in $self.$field {
|
||||
$another
|
||||
.$field
|
||||
.insert(ty, rslvr)
|
||||
.none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?;
|
||||
}
|
||||
}
|
||||
$another.$field
|
||||
}};
|
||||
|
||||
($field:ident: $self:ident, $another:ident) => {
|
||||
try_merge_hashmap!($field: $self, $another => span_ident)
|
||||
};
|
||||
}
|
||||
|
||||
// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
|
||||
// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
|
||||
// and so we cannot move the definition into a sub-module and use the `#[macro_export]`
|
||||
// attribute.
|
||||
/// Attempts to merge a [`HashSet`] `$field` of a `$self` struct with the same `$field` of
|
||||
/// `$another` struct. If some [`HashSet`] entries are duplicated, then throws a duplication error
|
||||
/// with a [`Span`] related to the `$another` struct (a later one).
|
||||
///
|
||||
/// The type of [`Span`] may be explicitly specified as one of the [`SpanContainer`] methods.
|
||||
/// By default, [`SpanContainer::span_ident`] is used.
|
||||
///
|
||||
/// [`HashSet`]: std::collections::HashSet
|
||||
/// [`Span`]: proc_macro2::Span
|
||||
/// [`SpanContainer`]: crate::util::span_container::SpanContainer
|
||||
/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
|
||||
macro_rules! try_merge_hashset {
|
||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||
if !$self.$field.is_empty() {
|
||||
for ty in $self.$field {
|
||||
$another
|
||||
.$field
|
||||
.replace(ty)
|
||||
.none_or_else(|dup| crate::common::parse::attr::err::dup_arg(&dup.$span()))?;
|
||||
}
|
||||
}
|
||||
$another.$field
|
||||
}};
|
||||
|
||||
($field:ident: $self:ident, $another:ident) => {
|
||||
try_merge_hashset!($field: $self, $another => span_ident)
|
||||
};
|
||||
}
|
||||
|
||||
mod derive_enum;
|
||||
mod derive_input_object;
|
||||
mod derive_object;
|
||||
|
@ -19,6 +115,8 @@ mod derive_scalar_value;
|
|||
mod impl_object;
|
||||
mod impl_scalar;
|
||||
|
||||
mod common;
|
||||
mod graphql_interface;
|
||||
mod graphql_union;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
@ -452,6 +550,14 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
|
|||
))
|
||||
}
|
||||
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
|
||||
self::graphql_interface::attr::expand(attr.into(), body.into())
|
||||
.unwrap_or_abort()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// `#[derive(GraphQLUnion)]` macro for deriving a [GraphQL union][1] implementation for enums and
|
||||
/// structs.
|
||||
///
|
||||
|
|
|
@ -10,10 +10,11 @@ pub const SPEC_URL: &str = "https://spec.graphql.org/June2018/";
|
|||
|
||||
#[allow(unused_variables)]
|
||||
pub enum GraphQLScope {
|
||||
InterfaceAttr,
|
||||
UnionAttr,
|
||||
UnionDerive,
|
||||
DeriveObject,
|
||||
DeriveInputObject,
|
||||
UnionDerive,
|
||||
DeriveEnum,
|
||||
DeriveScalar,
|
||||
ImplScalar,
|
||||
|
@ -23,9 +24,10 @@ pub enum GraphQLScope {
|
|||
impl GraphQLScope {
|
||||
pub fn spec_section(&self) -> &str {
|
||||
match self {
|
||||
Self::InterfaceAttr => "#sec-Interfaces",
|
||||
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
|
||||
Self::DeriveObject | Self::ImplObject => "#sec-Objects",
|
||||
Self::DeriveInputObject => "#sec-Input-Objects",
|
||||
Self::UnionAttr | Self::UnionDerive => "#sec-Unions",
|
||||
Self::DeriveEnum => "#sec-Enums",
|
||||
Self::DeriveScalar | Self::ImplScalar => "#sec-Scalars",
|
||||
}
|
||||
|
@ -35,9 +37,10 @@ impl GraphQLScope {
|
|||
impl fmt::Display for GraphQLScope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let name = match self {
|
||||
Self::InterfaceAttr => "interface",
|
||||
Self::UnionAttr | Self::UnionDerive => "union",
|
||||
Self::DeriveObject | Self::ImplObject => "object",
|
||||
Self::DeriveInputObject => "input object",
|
||||
Self::UnionAttr | Self::UnionDerive => "union",
|
||||
Self::DeriveEnum => "enum",
|
||||
Self::DeriveScalar | Self::ImplScalar => "scalar",
|
||||
};
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
#![allow(clippy::single_match)]
|
||||
|
||||
pub mod duplicate;
|
||||
pub mod option_ext;
|
||||
pub mod parse_impl;
|
||||
pub mod span_container;
|
||||
|
||||
use std::ops::Deref as _;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use proc_macro_error::abort;
|
||||
use quote::quote;
|
||||
use span_container::SpanContainer;
|
||||
use std::collections::HashMap;
|
||||
use syn::{
|
||||
parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, Lit, Meta, MetaList,
|
||||
MetaNameValue, NestedMeta, Token,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token, Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
|
||||
};
|
||||
|
||||
pub use self::option_ext::OptionExt;
|
||||
use crate::common::parse::ParseBufferExt as _;
|
||||
|
||||
/// Returns the name of a type.
|
||||
/// If the type does not end in a simple ident, `None` is returned.
|
||||
|
@ -74,15 +75,6 @@ pub fn type_is_identifier_ref(ty: &syn::Type, name: &str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Retrieves the innermost non-parenthesized [`syn::Type`] from the given one (unwraps nested
|
||||
/// [`syn::TypeParen`]s asap).
|
||||
pub fn unparenthesize(ty: &syn::Type) -> &syn::Type {
|
||||
match ty {
|
||||
syn::Type::Paren(ty) => unparenthesize(ty.elem.deref()),
|
||||
_ => ty,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeprecationAttr {
|
||||
pub reason: Option<String>,
|
||||
|
@ -232,11 +224,18 @@ fn get_doc_attr(attrs: &[Attribute]) -> Option<Vec<MetaNameValue>> {
|
|||
pub fn to_camel_case(s: &str) -> String {
|
||||
let mut dest = String::new();
|
||||
|
||||
// handle '_' to be more friendly with the
|
||||
// _var convention for unused variables
|
||||
let s_iter = if s.starts_with('_') { &s[1..] } else { s }
|
||||
.split('_')
|
||||
.enumerate();
|
||||
// Handle `_` and `__` to be more friendly with the `_var` convention for unused variables, and
|
||||
// GraphQL introspection identifiers.
|
||||
let s_iter = if s.starts_with("__") {
|
||||
dest.push_str("__");
|
||||
&s[2..]
|
||||
} else if s.starts_with('_') {
|
||||
&s[1..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
.split('_')
|
||||
.enumerate();
|
||||
|
||||
for (i, part) in s_iter {
|
||||
if i > 0 && part.len() == 1 {
|
||||
|
@ -307,15 +306,15 @@ pub struct ObjectAttributes {
|
|||
pub is_internal: bool,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for ObjectAttributes {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
|
||||
impl Parse for ObjectAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut output = Self::default();
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: syn::Ident = input.parse()?;
|
||||
let ident = input.parse_any_ident()?;
|
||||
match ident.to_string().as_str() {
|
||||
"name" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.name = Some(SpanContainer::new(
|
||||
ident.span(),
|
||||
|
@ -324,7 +323,7 @@ impl syn::parse::Parse for ObjectAttributes {
|
|||
));
|
||||
}
|
||||
"description" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.description = Some(SpanContainer::new(
|
||||
ident.span(),
|
||||
|
@ -333,7 +332,7 @@ impl syn::parse::Parse for ObjectAttributes {
|
|||
));
|
||||
}
|
||||
"context" | "Context" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
// TODO: remove legacy support for string based Context.
|
||||
let ctx = if let Ok(val) = input.parse::<syn::LitStr>() {
|
||||
eprintln!("DEPRECATION WARNING: using a string literal for the Context is deprecated");
|
||||
|
@ -345,19 +344,15 @@ impl syn::parse::Parse for ObjectAttributes {
|
|||
output.context = Some(SpanContainer::new(ident.span(), Some(ctx.span()), ctx));
|
||||
}
|
||||
"scalar" | "Scalar" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let val = input.parse::<syn::Type>()?;
|
||||
output.scalar = Some(SpanContainer::new(ident.span(), Some(val.span()), val));
|
||||
}
|
||||
"interfaces" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
let content;
|
||||
syn::bracketed!(content in input);
|
||||
output.interfaces =
|
||||
syn::punctuated::Punctuated::<syn::Type, syn::Token![,]>::parse_terminated(
|
||||
&content,
|
||||
)?
|
||||
.into_iter()
|
||||
"impl" | "implements" | "interfaces" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
output.interfaces = input.parse_maybe_wrapped_and_punctuated::<
|
||||
syn::Type, token::Bracket, token::Comma,
|
||||
>()?.into_iter()
|
||||
.map(|interface| {
|
||||
SpanContainer::new(ident.span(), Some(interface.span()), interface)
|
||||
})
|
||||
|
@ -374,9 +369,7 @@ impl syn::parse::Parse for ObjectAttributes {
|
|||
return Err(syn::Error::new(ident.span(), "unknown attribute"));
|
||||
}
|
||||
}
|
||||
if input.lookahead1().peek(syn::Token![,]) {
|
||||
input.parse::<syn::Token![,]>()?;
|
||||
}
|
||||
input.try_parse::<token::Comma>()?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
@ -384,7 +377,7 @@ impl syn::parse::Parse for ObjectAttributes {
|
|||
}
|
||||
|
||||
impl ObjectAttributes {
|
||||
pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::parse::Result<Self> {
|
||||
pub fn from_attrs(attrs: &[syn::Attribute]) -> syn::Result<Self> {
|
||||
let attr_opt = find_graphql_attr(attrs);
|
||||
if let Some(attr) = attr_opt {
|
||||
// Need to unwrap outer (), which are not present for proc macro attributes,
|
||||
|
@ -411,8 +404,8 @@ pub struct FieldAttributeArgument {
|
|||
pub description: Option<syn::LitStr>,
|
||||
}
|
||||
|
||||
impl parse::Parse for FieldAttributeArgument {
|
||||
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
|
||||
impl Parse for FieldAttributeArgument {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
|
||||
let mut arg = Self {
|
||||
|
@ -426,7 +419,7 @@ impl parse::Parse for FieldAttributeArgument {
|
|||
syn::parenthesized!(content in input);
|
||||
while !content.is_empty() {
|
||||
let name = content.parse::<syn::Ident>()?;
|
||||
content.parse::<Token![=]>()?;
|
||||
content.parse::<token::Eq>()?;
|
||||
|
||||
match name.to_string().as_str() {
|
||||
"name" => {
|
||||
|
@ -443,7 +436,7 @@ impl parse::Parse for FieldAttributeArgument {
|
|||
}
|
||||
|
||||
// Discard trailing comma.
|
||||
content.parse::<Token![,]>().ok();
|
||||
content.parse::<token::Comma>().ok();
|
||||
}
|
||||
|
||||
Ok(arg)
|
||||
|
@ -465,13 +458,13 @@ enum FieldAttribute {
|
|||
Default(SpanContainer<Option<syn::Expr>>),
|
||||
}
|
||||
|
||||
impl parse::Parse for FieldAttribute {
|
||||
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
|
||||
impl Parse for FieldAttribute {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let ident = input.parse::<syn::Ident>()?;
|
||||
|
||||
match ident.to_string().as_str() {
|
||||
"name" => {
|
||||
input.parse::<Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let lit = input.parse::<syn::LitStr>()?;
|
||||
let raw = lit.value();
|
||||
if !is_valid_name(&raw) {
|
||||
|
@ -485,7 +478,7 @@ impl parse::Parse for FieldAttribute {
|
|||
}
|
||||
}
|
||||
"description" => {
|
||||
input.parse::<Token![=]>()?;
|
||||
input.parse::<token::Eq>()?;
|
||||
let lit = input.parse::<syn::LitStr>()?;
|
||||
Ok(FieldAttribute::Description(SpanContainer::new(
|
||||
ident.span(),
|
||||
|
@ -494,8 +487,8 @@ impl parse::Parse for FieldAttribute {
|
|||
)))
|
||||
}
|
||||
"deprecated" | "deprecation" => {
|
||||
let reason = if input.peek(Token![=]) {
|
||||
input.parse::<Token![=]>()?;
|
||||
let reason = if input.peek(token::Eq) {
|
||||
input.parse::<token::Eq>()?;
|
||||
Some(input.parse::<syn::LitStr>()?)
|
||||
} else {
|
||||
None
|
||||
|
@ -516,7 +509,7 @@ impl parse::Parse for FieldAttribute {
|
|||
"arguments" => {
|
||||
let arg_content;
|
||||
syn::parenthesized!(arg_content in input);
|
||||
let args = Punctuated::<FieldAttributeArgument, Token![,]>::parse_terminated(
|
||||
let args = Punctuated::<FieldAttributeArgument, token::Comma>::parse_terminated(
|
||||
&arg_content,
|
||||
)?;
|
||||
let map = args
|
||||
|
@ -526,8 +519,8 @@ impl parse::Parse for FieldAttribute {
|
|||
Ok(FieldAttribute::Arguments(map))
|
||||
}
|
||||
"default" => {
|
||||
let default_expr = if input.peek(Token![=]) {
|
||||
input.parse::<Token![=]>()?;
|
||||
let default_expr = if input.peek(token::Eq) {
|
||||
input.parse::<token::Eq>()?;
|
||||
let lit = input.parse::<syn::LitStr>()?;
|
||||
let default_expr = lit.parse::<syn::Expr>()?;
|
||||
SpanContainer::new(ident.span(), Some(lit.span()), Some(default_expr))
|
||||
|
@ -555,9 +548,9 @@ pub struct FieldAttributes {
|
|||
pub default: Option<SpanContainer<Option<syn::Expr>>>,
|
||||
}
|
||||
|
||||
impl parse::Parse for FieldAttributes {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
|
||||
let items = Punctuated::<FieldAttribute, Token![,]>::parse_terminated(&input)?;
|
||||
impl Parse for FieldAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let items = Punctuated::<FieldAttribute, token::Comma>::parse_terminated(&input)?;
|
||||
|
||||
let mut output = Self::default();
|
||||
|
||||
|
@ -596,7 +589,7 @@ impl FieldAttributes {
|
|||
pub fn from_attrs(
|
||||
attrs: &[syn::Attribute],
|
||||
_mode: FieldAttributeParseMode,
|
||||
) -> syn::parse::Result<Self> {
|
||||
) -> syn::Result<Self> {
|
||||
let doc_comment = get_doc_comment(&attrs);
|
||||
let deprecation = get_deprecated(&attrs);
|
||||
|
||||
|
@ -669,7 +662,7 @@ pub struct GraphQLTypeDefiniton {
|
|||
pub description: Option<String>,
|
||||
pub fields: Vec<GraphQLTypeDefinitionField>,
|
||||
pub generics: syn::Generics,
|
||||
pub interfaces: Option<Vec<syn::Type>>,
|
||||
pub interfaces: Vec<syn::Type>,
|
||||
// Due to syn parsing differences,
|
||||
// when parsing an impl the type generics are included in the type
|
||||
// directly, but in syn::DeriveInput, the type generics are
|
||||
|
@ -813,13 +806,17 @@ impl GraphQLTypeDefiniton {
|
|||
.as_ref()
|
||||
.map(|description| quote!( .description(#description) ));
|
||||
|
||||
let interfaces = self.interfaces.as_ref().map(|items| {
|
||||
quote!(
|
||||
let interfaces = if !self.interfaces.is_empty() {
|
||||
let interfaces_ty = &self.interfaces;
|
||||
|
||||
Some(quote!(
|
||||
.interfaces(&[
|
||||
#( registry.get_type::< #items >(&()) ,)*
|
||||
#( registry.get_type::<#interfaces_ty>(&()) ,)*
|
||||
])
|
||||
)
|
||||
});
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Preserve the original type_generics before modification,
|
||||
// since alteration makes them invalid if self.generic_scalar
|
||||
|
@ -928,7 +925,29 @@ impl GraphQLTypeDefiniton {
|
|||
.push(parse_quote!( #scalar: Send + Sync ));
|
||||
where_async.predicates.push(parse_quote!(Self: Sync));
|
||||
|
||||
// FIXME: add where clause for interfaces.
|
||||
let as_dyn_value = if !self.interfaces.is_empty() {
|
||||
Some(quote! {
|
||||
#[automatically_derived]
|
||||
impl#impl_generics ::juniper::AsDynGraphQLValue<#scalar> for #ty #type_generics_tokens
|
||||
#where_async
|
||||
{
|
||||
type Context = <Self as ::juniper::GraphQLValue<#scalar>>::Context;
|
||||
type TypeInfo = <Self as ::juniper::GraphQLValue<#scalar>>::TypeInfo;
|
||||
|
||||
#[inline]
|
||||
fn as_dyn_graphql_value(&self) -> &::juniper::DynGraphQLValue<#scalar, Self::Context, Self::TypeInfo> {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn as_dyn_graphql_value_async(&self) -> &::juniper::DynGraphQLValueAsync<#scalar, Self::Context, Self::TypeInfo> {
|
||||
self
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
quote!(
|
||||
impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #type_generics_tokens
|
||||
|
@ -956,6 +975,8 @@ impl GraphQLTypeDefiniton {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#as_dyn_value
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1144,13 +1165,17 @@ impl GraphQLTypeDefiniton {
|
|||
.as_ref()
|
||||
.map(|description| quote!( .description(#description) ));
|
||||
|
||||
let interfaces = self.interfaces.as_ref().map(|items| {
|
||||
quote!(
|
||||
let interfaces = if !self.interfaces.is_empty() {
|
||||
let interfaces_ty = &self.interfaces;
|
||||
|
||||
Some(quote!(
|
||||
.interfaces(&[
|
||||
#( registry.get_type::< #items >(&()) ,)*
|
||||
#( registry.get_type::<#interfaces_ty>(&()) ,)*
|
||||
])
|
||||
)
|
||||
});
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Preserve the original type_generics before modification,
|
||||
// since alteration makes them invalid if self.generic_scalar
|
||||
|
@ -1917,6 +1942,7 @@ mod test {
|
|||
fn test_to_camel_case() {
|
||||
assert_eq!(&to_camel_case("test")[..], "test");
|
||||
assert_eq!(&to_camel_case("_test")[..], "test");
|
||||
assert_eq!(&to_camel_case("__test")[..], "__test");
|
||||
assert_eq!(&to_camel_case("first_second")[..], "firstSecond");
|
||||
assert_eq!(&to_camel_case("first_")[..], "first");
|
||||
assert_eq!(&to_camel_case("a_b_c")[..], "aBC");
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
/// Handy extension of [`Option`] methods used in this crate.
|
||||
pub trait OptionExt {
|
||||
type Inner;
|
||||
|
||||
/// Transforms the `Option<T>` into a `Result<(), E>`, mapping `None` to `Ok(())` and `Some(v)`
|
||||
/// to `Err(err(v))`.
|
||||
fn none_or_else<E, F>(self, err: F) -> Result<(), E>
|
||||
where
|
||||
F: FnOnce(Self::Inner) -> E;
|
||||
}
|
||||
|
||||
impl<T> OptionExt for Option<T> {
|
||||
type Inner = T;
|
||||
|
||||
fn none_or_else<E, F>(self, err: F) -> Result<(), E>
|
||||
where
|
||||
F: FnOnce(T) -> E,
|
||||
{
|
||||
match self {
|
||||
Some(v) => Err(err(v)),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ use hyper::{
|
|||
Body, Method, Response, Server, StatusCode,
|
||||
};
|
||||
use juniper::{
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
EmptyMutation, EmptySubscription, RootNode,
|
||||
};
|
||||
|
||||
|
|
|
@ -319,7 +319,7 @@ mod tests {
|
|||
};
|
||||
use juniper::{
|
||||
http::tests as http_tests,
|
||||
tests::fixtures::starwars::{model::Database, schema::Query},
|
||||
tests::fixtures::starwars::schema::{Database, Query},
|
||||
EmptyMutation, EmptySubscription, RootNode,
|
||||
};
|
||||
use reqwest::{self, blocking::Response as ReqwestResponse};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue