Attribute Macro juniper_codegen::graphql_interface [−][src]
#[graphql_interface]
Expand description
#[graphql_interface]
macro for generating a GraphQL interface 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 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, because trait doesn’t represent a type itself.
Macro uses Rust enum to represent a value type of GraphQL interface by default, however
trait object may be used too (use dyn
attribute argument for that).
A trait has to be object safe if its values are represented by trait object,
because schema resolvers will need to return that trait object. The trait object has
to be Send
and Sync
, and the macro automatically generate a convenien type alias for
such trait object.
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, 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, 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 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).
#[graphql_interface(name = "Character", desc = "Possible episode characters.")] trait Chrctr { #[graphql(name = "id", desc = "ID of the character.")] #[graphql(deprecated = "Don't use it")] fn some_id( &self, #[graphql(name = "number", desc = "Arbitrary number.")] #[graphql(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(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 ()
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 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.
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(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 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.
// 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(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 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.
// 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 field and ignore it, use an
ignore
/skip
attribute’s argument directly on that method.
#[graphql_interface] trait Character { fn id(&self) -> &str; #[graphql(ignore)] // or `#[graphql(skip)]`, your choice fn kaboom(&mut self); }
Downcasting
By default, the GraphQL interface 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 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.
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(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()) }