diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 1750b2e0..54fa76fe 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -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 { // 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 Character 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 diff --git a/docs/book/content/types/unions.md b/docs/book/content/types/unions.md index e8209dab..6eb16a4e 100644 --- a/docs/book/content/types/unions.md +++ b/docs/book/content/types/unions.md @@ -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 diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index a0d498b2..4e4e05e2 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -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, +/// droids: HashMap, +/// } +/// 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 { +/// 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 Character 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, +/// droids: HashMap, +/// } +/// 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