[][src]Attribute Macro juniper::graphql_union

#[graphql_union]

#[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(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(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/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(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/Scalar/ScalarValue 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(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/skip 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)]  // or `#[graphql(skip)]`, your choice
    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(Context = Database)]
#[graphql_union(
    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())
}