Fill up missing docs for #[graphql_interface] macro (#682)

This commit is contained in:
tyranron 2020-10-07 10:09:01 +02:00
parent b1a0366112
commit 6d4a0a8709
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
3 changed files with 390 additions and 24 deletions

View file

@ -235,7 +235,7 @@ trait Character {
### Custom context
If a context is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
If a [`Context`][6] is required in a trait method to resolve a [GraphQL interface][1] field, specify it as an argument.
```rust
# extern crate juniper;
@ -288,7 +288,7 @@ impl Character for Human {
### 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.
If an [`Executor`][4] 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.
@ -296,14 +296,13 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`]
# 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
#[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` 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>(
@ -343,7 +342,7 @@ impl<S: ScalarValue> Character<S> for Human {
### Downcasting
By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` is used).
By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` macro argument is used).
However, if some custom logic is needed to downcast a [GraphQL interface][1] implementer, you may specify either an external function or a trait method to do so.
@ -406,6 +405,8 @@ fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid>
# fn main() {}
```
The attribute syntax `#[graphql_interface(on ImplementerType = resolver_fn)]` follows the [GraphQL syntax for downcasting interface implementer](https://spec.graphql.org/June2018/#example-5cc55).
@ -424,7 +425,7 @@ trait Character {
}
#[derive(GraphQLObject)]
#[graphql(Scalar = DefaultScalarValue)]
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
struct Human {
id: String,
home_planet: String,
@ -437,6 +438,7 @@ impl Character for Human {
}
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
struct Droid {
id: String,
primary_function: String,
@ -460,3 +462,4 @@ impl Character for Droid {
[3]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
[4]: https://docs.rs/juniper/latest/juniper/struct.Executor.html
[5]: https://spec.graphql.org/June2018/#sec-Objects
[6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html

View file

@ -1,9 +1,9 @@
Unions
======
From the server's point of view, [GraphQL unions][1] are similar to interfaces - the only exception is that they don't contain fields on their own.
From the server's point of view, [GraphQL unions][1] are somewhat similar to [interfaces][5] - the main difference is that they don't contain fields on their own.
For implementing [GraphQL unions][1] Juniper provides:
The most obvious and straightforward way to represent a [GraphQL union][1] in Rust is enum. However, we also can do so either with trait or a regular struct. That's why, for implementing [GraphQL unions][1] Juniper provides:
- `#[derive(GraphQLUnion)]` macro for enums and structs.
- `#[graphql_union]` for traits.
@ -288,7 +288,7 @@ impl Character for Droid {
### Custom context
If a context is required in a trait method to resolve a [GraphQL union][1] variant, specify it as an argument.
If a [`Context`][6] is required in a trait method to resolve a [GraphQL union][1] variant, specify it as an argument.
```rust
# #![allow(unused_variables)]
@ -487,3 +487,5 @@ enum Character {
[1]: https://spec.graphql.org/June2018/#sec-Unions
[2]: https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html
[5]: https://spec.graphql.org/June2018/#sec-Interfaces
[6]: https://docs.rs/juniper/0.14.2/juniper/trait.Context.html

View file

@ -550,6 +550,363 @@ pub fn graphql_subscription(args: TokenStream, input: TokenStream) -> TokenStrea
))
}
/// `#[graphql_interface]` macro for generating a [GraphQL interface][1] implementation for traits
/// and its implementers.
///
/// Specifying multiple `#[graphql_interface]` attributes on the same definition is totally okay.
/// They all will be treated as a single attribute.
///
/// The main difference between [GraphQL interface][1] type and Rust trait is that the former serves
/// both as an _abstraction_ and a _value downcastable to concrete implementers_, while in Rust, a
/// trait is an _abstraction only_ and you need a separate type to downcast into a concrete
/// implementer, like enum or [trait object][3], because trait doesn't represent a type itself.
/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by default, however
/// [trait object][3] may be used too (use `dyn` attribute argument for that).
///
/// A __trait has to be [object safe][2]__ if its values are represented by [trait object][3],
/// because schema resolvers will need to return that [trait object][3]. The [trait object][3] has
/// to be [`Send`] and [`Sync`], and the macro automatically generate a convenien type alias for
/// such [trait object][3].
///
/// ```
/// use juniper::{graphql_interface, GraphQLObject};
///
/// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this
/// // GraphQL interface.
/// #[graphql_interface(for = [Human, Droid])] // enumerating all implementers is mandatory
/// trait Character {
/// fn id(&self) -> &str;
/// }
///
/// // NOTICE: `dyn` attribute argument enables trait object usage to represent values of this
/// // GraphQL interface. Also, for trait objects a trait is slightly modified
/// // under-the-hood by adding a `ScalarValue` type parameter.
/// #[graphql_interface(dyn = DynSerial, for = Droid)]
/// trait Serial {
/// fn number(&self) -> i32;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = [CharacterValue, DynSerial<__S>])] // notice the trait object referred by alias
/// struct Droid { // and its parametrization by generic
/// id: String, // `ScalarValue`
/// primary_function: String,
/// }
/// #[graphql_interface]
/// impl Character for Droid {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
/// #[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too
/// impl Serial for Droid {
/// fn number(&self) -> i32 {
/// 78953
/// }
/// }
/// ```
///
/// # Custom name, description, deprecation and argument defaults
///
/// The name of [GraphQL interface][1], its field, or a field argument may be overriden with a
/// `name` attribute's argument. By default, a type name is used or `camelCased` method/argument
/// name.
///
/// The description of [GraphQL interface][1], its field, or a field argument may be specified
/// either with a `description`/`desc` attribute's argument, or with a regular Rust doc comment.
///
/// A field of [GraphQL interface][1] may be deprecated by specifying a `deprecated` attribute's
/// argument, or with regulat Rust `#[deprecated]` attribute.
///
/// The default value of a field argument may be specified with a `default` attribute argument (if
/// no exact value is specified then [`Default::default`] is used).
///
/// ```
/// # #![allow(deprecated)]
/// # use juniper::graphql_interface;
/// #
/// #[graphql_interface(name = "Character", desc = "Possible episode characters.")]
/// trait Chrctr {
/// #[graphql_interface(name = "id", desc = "ID of the character.")]
/// #[graphql_interface(deprecated = "Don't use it")]
/// fn some_id(
/// &self,
/// #[graphql_interface(name = "number", desc = "Arbitrary number.")]
/// #[graphql_interface(default = 5)]
/// num: i32,
/// ) -> &str;
/// }
///
/// // NOTICE: Rust docs are used as GraphQL description.
/// /// Possible episode characters.
/// #[graphql_interface]
/// trait CharacterWithDocs {
/// /// ID of the character.
/// #[deprecated]
/// fn id(&self, #[graphql_interface(default)] num: i32) -> &str;
/// }
/// ```
///
/// # Custom context
///
/// By default, the generated implementation tries to infer [`Context`] type from signatures of
/// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments.
///
/// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly
/// with `context`/`Context` attribute's argument.
///
/// If trait method represents a [GraphQL interface][1] field and its argument is named as `context`
/// or `ctx` then this argument is assumed as [`Context`] and will be omited in GraphQL schema.
/// Additionally, any argument may be marked as [`Context`] with a `context` attribute's argument.
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{graphql_interface, GraphQLObject};
/// #
/// struct Database {
/// humans: HashMap<String, Human>,
/// droids: HashMap<String, Droid>,
/// }
/// impl juniper::Context for Database {}
///
/// #[graphql_interface(for = [Human, Droid], Context = Database)]
/// trait Character {
/// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str>;
/// fn info<'db>(&self, #[graphql_interface(context)] db: &'db Database) -> Option<&'db str>;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// db.humans.get(&self.id).map(|h| h.id.as_str())
/// }
/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// db.humans.get(&self.id).map(|h| h.home_planet.as_str())
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
/// #[graphql_interface]
/// impl Character for Droid {
/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// db.droids.get(&self.id).map(|h| h.id.as_str())
/// }
/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> {
/// db.droids.get(&self.id).map(|h| h.primary_function.as_str())
/// }
/// }
/// ```
///
/// # Using `Executor`
///
/// If an [`Executor`] is required in a trait method to resolve a [GraphQL interface][1] field,
/// specify it as an argument named as `executor` or explicitly marked with an `executor`
/// attribute's argument. Such method argument will be omited in GraphQL schema.
///
/// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so.
///
/// ```
/// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue};
/// #
/// // NOTICE: Specifying `ScalarValue` as existing type parameter.
/// #[graphql_interface(for = Human, Scalar = S)]
/// trait Character<S: ScalarValue> {
/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str
/// where
/// S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯
///
/// 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
/// }
/// }
/// ```
///
/// # Custom `ScalarValue`
///
/// By default, `#[graphql_interface]` macro generates code, which is generic over a [`ScalarValue`]
/// type. This may introduce a problem when at least one of [GraphQL interface][1] implementers is
/// restricted to a concrete [`ScalarValue`] type in its implementation. To resolve such problem, a
/// concrete [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue`
/// attribute's argument.
///
/// ```
/// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject};
/// #
/// // NOTICE: Removing `Scalar` argument will fail compilation.
/// #[graphql_interface(for = [Human, Droid], Scalar = DefaultScalarValue)]
/// trait Character {
/// fn id(&self) -> &str;
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
/// struct Human {
/// id: String,
/// home_planet: String,
/// }
/// #[graphql_interface(Scalar = DefaultScalarValue)]
/// impl Character for Human {
/// fn id(&self) -> &str{
/// &self.id
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)]
/// struct Droid {
/// id: String,
/// primary_function: String,
/// }
/// #[graphql_interface(Scalar = DefaultScalarValue)]
/// impl Character for Droid {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
/// ```
///
/// # Ignoring trait methods
///
/// To omit some trait method to be assumed as a [GraphQL interface][1] field and ignore it, use an
/// `ignore`/`skip` attribute's argument directly on that method.
///
/// ```
/// # use juniper::graphql_interface;
/// #
/// #[graphql_interface]
/// trait Character {
/// fn id(&self) -> &str;
///
/// #[graphql_interface(ignore)] // or `#[graphql_interface(skip)]`, your choice
/// fn kaboom(&mut self);
/// }
/// ```
///
/// # Downcasting
///
/// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via
/// matching the enum variant or downcasting the trait object (if `dyn` attribute's argument is
/// used).
///
/// To use a custom logic for downcasting a [GraphQL interface][1] into its implementer, there may
/// be specified:
/// - either a `downcast` attribute's argument directly on a trait method;
/// - or an `on` attribute's argument on aa trait definition referring an exteranl function.
///
/// ```
/// # use std::collections::HashMap;
/// # use juniper::{graphql_interface, GraphQLObject};
/// #
/// struct Database {
/// humans: HashMap<String, Human>,
/// droids: HashMap<String, Droid>,
/// }
/// impl juniper::Context for Database {}
///
/// #[graphql_interface(for = [Human, Droid], Context = Database)]
/// #[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()`
/// trait Character {
/// fn id(&self) -> &str;
///
/// #[graphql_interface(downcast)] // makes method a downcast to `Human`, not a field
/// // NOTICE: The method signature may optionally contain `&Database` context argument.
/// fn as_human(&self) -> Option<&Human> {
/// None
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Human {
/// id: String,
/// }
/// #[graphql_interface]
/// impl Character for Human {
/// fn id(&self) -> &str {
/// &self.id
/// }
///
/// fn as_human(&self) -> Option<&Self> {
/// Some(self)
/// }
/// }
///
/// #[derive(GraphQLObject)]
/// #[graphql(impl = CharacterValue, Context = Database)]
/// struct Droid {
/// id: String,
/// }
/// #[graphql_interface]
/// impl Character for Droid {
/// fn id(&self) -> &str {
/// &self.id
/// }
/// }
///
/// // External downcast function doesn't have to be a method of a type.
/// // It's only a matter of the function signature to match the requirements.
/// fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> {
/// db.droids.get(ch.id())
/// }
/// ```
///
/// [`Context`]: juniper::Context
/// [`Executor`]: juniper::Executor
/// [`ScalarValue`]: juniper::ScalarValue
/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
/// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety
/// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html
/// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html
#[proc_macro_error]
#[proc_macro_attribute]
pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
@ -638,9 +995,9 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
///
/// # Custom context
///
/// By default, the generated implementation uses [unit type `()`][4] as context. To use a custom
/// context type for [GraphQL union][1] variants types or external resolver functions, specify it
/// with `context`/`Context` attribute's argument.
/// By default, the generated implementation uses [unit type `()`][4] as [`Context`]. To use a
/// custom [`Context`] type for [GraphQL union][1] variants types or external resolver functions,
/// specify it with `context`/`Context` attribute's argument.
///
/// ```
/// # use juniper::{GraphQLObject, GraphQLUnion};
@ -672,10 +1029,10 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
///
/// # Custom `ScalarValue`
///
/// By default, this macro generates code, which is generic over a `ScalarValue` type.
/// By default, this macro generates code, which is generic over a [`ScalarValue`] type.
/// This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a
/// concrete `ScalarValue` type in its implementation. To resolve such problem, a concrete
/// `ScalarValue` type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's
/// concrete [`ScalarValue`] type in its implementation. To resolve such problem, a concrete
/// [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's
/// argument.
///
/// ```
@ -854,6 +1211,8 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream {
/// }
/// ```
///
/// [`Context`]: juniper::Context
/// [`ScalarValue`]: juniper::ScalarValue
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [4]: https://doc.rust-lang.org/stable/std/primitive.unit.html
#[proc_macro_error]
@ -958,11 +1317,11 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
///
/// # Custom context
///
/// By default, the generated implementation tries to infer `juniper::Context` type from signatures
/// of trait methods, and uses [unit type `()`][4] if signatures contains no context arguments.
/// By default, the generated implementation tries to infer [`Context`] type from signatures of
/// trait methods, and uses [unit type `()`][4] if signatures contains no [`Context`] arguments.
///
/// If `juniper::Context` type cannot be inferred or is inferred incorrectly, then specify it
/// explicitly with `context`/`Context` attribute's argument.
/// If [`Context`] type cannot be inferred or is inferred incorrectly, then specify it explicitly
/// with `context`/`Context` attribute's argument.
///
/// ```
/// # use std::collections::HashMap;
@ -1009,11 +1368,11 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
///
/// # Custom `ScalarValue`
///
/// By default, `#[graphql_union]` macro generates code, which is generic over a `ScalarValue` type.
/// This may introduce a problem when at least one of [GraphQL union][1] variants is restricted to a
/// concrete `ScalarValue` type in its implementation. To resolve such problem, a concrete
/// `ScalarValue` type should be specified with a `scalar`/`Scalar`/`ScalarValue` attribute's
/// argument.
/// By default, `#[graphql_union]` macro generates code, which is generic over a [`ScalarValue`]
/// type. This may introduce a problem when at least one of [GraphQL union][1] variants is
/// restricted to a concrete [`ScalarValue`] type in its implementation. To resolve such problem, a
/// concrete [`ScalarValue`] type should be specified with a `scalar`/`Scalar`/`ScalarValue`
/// attribute's argument.
///
/// ```
/// # use juniper::{graphql_union, DefaultScalarValue, GraphQLObject};
@ -1142,6 +1501,8 @@ pub fn derive_union(input: TokenStream) -> TokenStream {
/// }
/// ```
///
/// [`Context`]: juniper::Context
/// [`ScalarValue`]: juniper::ScalarValue
/// [1]: https://spec.graphql.org/June2018/#sec-Unions
/// [2]: https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety
/// [3]: https://doc.rust-lang.org/stable/reference/types/trait-object.html