Derive Macro juniper_codegen::GraphQLUnion[][src]

#[derive(GraphQLUnion)]
{
    // Attributes available to this derive:
    #[graphql]
}
Expand description

#[derive(GraphQLUnion)] macro for deriving a GraphQL union implementation for enums and structs.

The #[graphql] helper attribute is used for configuring the derived implementation. Specifying multiple #[graphql] attributes on the same definition is totally okay. They all will be treated as a single attribute.

use derive_more::From;
use juniper::{GraphQLObject, GraphQLUnion};

#[derive(GraphQLObject)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(GraphQLObject)]
struct Droid {
    id: String,
    primary_function: String,
}

#[derive(From, GraphQLUnion)]
enum CharacterEnum {
    Human(Human),
    Droid(Droid),
}

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.

#[derive(GraphQLUnion)]
#[graphql(name = "Character", desc = "Possible episode characters.")]
enum Chrctr {
    Human(Human),
    Droid(Droid),
}

// NOTICE: Rust docs are used as GraphQL description.
/// Possible episode characters.
#[derive(GraphQLUnion)]
enum CharacterWithDocs {
    Human(Human),
    Droid(Droid),
}

// NOTICE: `description` argument takes precedence over Rust docs.
/// Not a GraphQL description anymore.
#[derive(GraphQLUnion)]
#[graphql(description = "Possible episode characters.")]
enum CharacterWithDescription {
    Human(Human),
    Droid(Droid),
}

Custom context

By default, the generated implementation uses unit type () as Context. To use a custom Context type for GraphQL union variants types or external resolver functions, specify it with context/Context attribute’s argument.

#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Droid {
    id: String,
    primary_function: String,
}

pub struct CustomContext;
impl juniper::Context for CustomContext {}

#[derive(GraphQLUnion)]
#[graphql(Context = CustomContext)]
enum Character {
    Human(Human),
    Droid(Droid),
}

Custom ScalarValue

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 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.
#[derive(GraphQLUnion)]
#[graphql(Scalar = DefaultScalarValue)]
enum Character {
    Human(Human),
    Droid(Droid),
}

Ignoring enum variants

To omit exposing an enum variant in the GraphQL schema, use an ignore/skip attribute’s argument directly on that variant.

WARNING: It’s the library user’s responsibility to ensure that ignored enum variant is never returned from resolvers, otherwise resolving the GraphQL query will panic at runtime.

use derive_more::From;
use juniper::{GraphQLObject, GraphQLUnion};

#[derive(GraphQLObject)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(GraphQLObject)]
struct Droid {
    id: String,
    primary_function: String,
}

#[derive(From, GraphQLUnion)]
enum Character<S> {
    Human(Human),
    Droid(Droid),
    #[from(ignore)]
    #[graphql(ignore)]  // or `#[graphql(skip)]`, your choice
    _State(PhantomData<S>),
}

External resolver functions

To use a custom logic for resolving a GraphQL union variant, an external resolver function may be specified with:

  • either a with attribute’s argument on an enum variant;
  • or an on attribute’s argument on an enum/struct itself.
#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(GraphQLObject)]
#[graphql(Context = CustomContext)]
struct Droid {
    id: String,
    primary_function: String,
}

pub struct CustomContext {
    droid: Droid,
}
impl juniper::Context for CustomContext {}

#[derive(GraphQLUnion)]
#[graphql(Context = CustomContext)]
enum Character {
    Human(Human),
    #[graphql(with = Character::droid_from_context)]
    Droid(Droid),
}

impl Character {
    // NOTICE: The function signature must contain `&self` and `&Context`,
    //         and return `Option<&VariantType>`.
    fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> {
        Some(&ctx.droid)
    }
}

#[derive(GraphQLUnion)]
#[graphql(Context = CustomContext)]
#[graphql(on Droid = CharacterWithoutDroid::droid_from_context)]
enum CharacterWithoutDroid {
    Human(Human),
    #[graphql(ignore)]
    Droid,
}

impl CharacterWithoutDroid {
    fn droid_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Droid> {
        if let Self::Droid = self {
            Some(&ctx.droid)
        } else {
            None
        }
    }
}

Deriving structs

Specifying external resolver functions is mandatory for using a struct as a GraphQL union, because this is the only way to declare GraphQL union variants in this case.

#[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 {}

#[derive(GraphQLUnion)]
#[graphql(
    Context = Database,
    on Human = Character::get_human,
    on Droid = Character::get_droid,
)]
struct Character {
    id: String,
}

impl Character {
    fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human>{
        ctx.humans.get(&self.id)
    }

    fn get_droid<'db>(&self, ctx: &'db Database) -> Option<&'db Droid>{
        ctx.droids.get(&self.id)
    }
}