#[graphql_union]
Expand description
#[graphql_union]
macro for deriving a GraphQL union implementation for traits.
Specifying multiple #[graphql_union]
attributes on the same definition is totally okay. They
all will be treated as a single attribute.
A trait has to be object safe, because schema resolvers will need to return a
trait object to specify a GraphQL union behind it. The trait object has to be
Send
and Sync
.
use juniper::{graphql_union, GraphQLObject};
#[derive(GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
#[graphql_union]
trait Character {
// NOTICE: The method signature must contain `&self` and return `Option<&VariantType>`.
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
}
impl Character for Human {
fn as_human(&self) -> Option<&Human> { Some(&self) }
}
impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}
§Custom name and description
The name of GraphQL union may be overriden with a name
attribute’s argument. By default,
a type name is used.
The description of GraphQL union may be specified either with a description
/desc
attribute’s argument, or with a regular Rust doc comment.
#[graphql_union]
#[graphql(name = "Character", desc = "Possible episode characters.")]
trait Chrctr {
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
}
// NOTICE: Rust docs are used as GraphQL description.
/// Possible episode characters.
trait CharacterWithDocs {
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
}
// NOTICE: `description` argument takes precedence over Rust docs.
/// Not a GraphQL description anymore.
#[graphql_union]
#[graphql(description = "Possible episode characters.")]
trait CharacterWithDescription {
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
}
§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
attribute’s argument.
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap<String, Human>,
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
#[graphql_union]
#[graphql(context = Database)]
trait Character {
fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> { None }
fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> { None }
}
impl Character for Human {
fn as_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
ctx.humans.get(&self.id)
}
}
impl Character for Droid {
fn as_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid> {
ctx.droids.get(&self.id)
}
}
§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 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
attribute’s argument.
#[derive(GraphQLObject)]
#[graphql(scalar = DefaultScalarValue)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
// NOTICE: Removing `scalar` argument will fail compilation.
#[graphql_union]
#[graphql(scalar = DefaultScalarValue)]
trait Character {
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
}
§Ignoring trait methods
To omit some trait method to be assumed as a GraphQL union variant and
ignore it, use an ignore
attribute’s argument directly on that method.
#[graphql_union]
trait Character {
fn as_human(&self) -> Option<&Human> { None }
fn as_droid(&self) -> Option<&Droid> { None }
#[graphql(ignore)]
fn id(&self) -> &str;
}
§External resolver functions
It’s not mandatory to use trait methods as GraphQL union variant resolvers, and instead
custom functions may be specified with an on
attribute’s argument.
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
id: String,
home_planet: String,
}
#[derive(GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap<String, Human>,
droids: HashMap<String, Droid>,
}
impl juniper::Context for Database {}
#[graphql_union]
#[graphql(context = Database)]
#[graphql(
on Human = DynCharacter::get_human,
on Droid = get_droid,
)]
trait Character {
#[graphql(ignore)]
fn id(&self) -> &str;
}
impl Character for Human {
fn id(&self) -> &str { self.id.as_str() }
}
impl Character for Droid {
fn id(&self) -> &str { self.id.as_str() }
}
// NOTICE: The trait object is always `Send` and `Sync`.
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
impl<'a> DynCharacter<'a> {
fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
ctx.humans.get(self.id())
}
}
// NOTICE: Custom resolver 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: &DynCharacter<'_>, ctx: &'db Database) -> Option<&'db Droid> {
ctx.droids.get(ch.id())
}