Updated book for master ***NO_CI***
This commit is contained in:
parent
ce6327d090
commit
492354b25d
4 changed files with 678 additions and 244 deletions
|
@ -1587,31 +1587,236 @@ where
|
|||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#unions" id="unions"><h1>Unions</h1></a>
|
||||
<p>From a server's point of view, GraphQL unions are similar to interfaces: the
|
||||
only exception is that they don't contain fields on their own.</p>
|
||||
<p>In Juniper, the <code>graphql_union!</code> has identical syntax to the
|
||||
<a href="interfaces.html">interface macro</a>, but does not support defining
|
||||
fields. Therefore, the same considerations about using traits,
|
||||
placeholder types, or enums still apply to unions. For simple
|
||||
situations, Juniper provides <code>#[derive(GraphQLUnion)]</code> for enums.</p>
|
||||
<p>If we look at the same examples as in the interfaces chapter, we see the
|
||||
similarities and the tradeoffs:</p>
|
||||
<a class="header" href="#traits-1" id="traits-1"><h2>Traits</h2></a>
|
||||
<a class="header" href="#downcasting-via-accessor-methods-1" id="downcasting-via-accessor-methods-1"><h3>Downcasting via accessor methods</h3></a>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
||||
<p>From the server's point of view, <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL unions</a> are similar to interfaces - the only exception is that they don't contain fields on their own.</p>
|
||||
<p>For implementing <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL unions</a> Juniper provides:</p>
|
||||
<ul>
|
||||
<li><code>#[derive(GraphQLUnion)]</code> macro for enums and structs.</li>
|
||||
<li><code>#[graphql_union]</code> for traits.</li>
|
||||
</ul>
|
||||
<a class="header" href="#enums-2" id="enums-2"><h2>Enums</h2></a>
|
||||
<p>Most of the time, we just need a trivial and straightforward Rust enum to represent a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a>.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use derive_more::From;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
#[derive(From, GraphQLUnion)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#ignoring-enum-variants" id="ignoring-enum-variants"><h3>Ignoring enum variants</h3></a>
|
||||
<p>In some rare situations we may want to omit exposing an enum variant in the GraphQL schema.</p>
|
||||
<p>As an example, let's consider the situation where we need to bind some type parameter <code>T</code> for doing interesting type-level stuff in our resolvers. To achieve this we need to have <code>PhantomData<T></code>, but we don't want it exposed in the GraphQL schema.</p>
|
||||
<blockquote>
|
||||
<p><strong>WARNING</strong>:<br />
|
||||
It's the <em>library user's responsibility</em> to ensure that ignored enum variant is <em>never</em> returned from resolvers, otherwise resolving the GraphQL query will <strong>panic at runtime</strong>.</p>
|
||||
</blockquote>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::marker::PhantomData;
|
||||
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>),
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#external-resolver-functions" id="external-resolver-functions"><h3>External resolver functions</h3></a>
|
||||
<p>If some custom logic is needed to resolve a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant, you may specify an external function to do so:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<p>With an external resolver function we can even declare a new <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant where the Rust type is absent in the initial enum definition. The attribute syntax <code>#[graphql(on VariantType = resolver_fn)]</code> follows the <a href="https://spec.graphql.org/June2018/#example-f8163">GraphQL syntax for dispatching union variants</a>.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
struct Ewok {
|
||||
id: String,
|
||||
is_funny: bool,
|
||||
}
|
||||
|
||||
pub struct CustomContext {
|
||||
ewok: Ewok,
|
||||
}
|
||||
impl juniper::Context for CustomContext {}
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
#[graphql(on Ewok = Character::ewok_from_context)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
#[graphql(ignore)] // or `#[graphql(skip)]`, your choice
|
||||
Ewok,
|
||||
}
|
||||
|
||||
impl Character {
|
||||
fn ewok_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Ewok> {
|
||||
if let Self::Ewok = self {
|
||||
Some(&ctx.ewok)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#structs" id="structs"><h2>Structs</h2></a>
|
||||
<p>Using Rust structs as <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL unions</a> is very similar to using enums, with the nuance that specifying an external resolver function is the only way to declare a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#traits-1" id="traits-1"><h2>Traits</h2></a>
|
||||
<p>To use a Rust trait definition as a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> you need to use the <code>#[graphql_union]</code> macro. <a href="https://doc.rust-lang.org/stable/reference/procedural-macros.html#derive-macros">Rust doesn't allow derive macros on traits</a>, so using <code>#[derive(GraphQLUnion)]</code> on traits doesn't work.</p>
|
||||
<blockquote>
|
||||
<p><strong>NOTICE</strong>:<br />
|
||||
A <strong>trait has to be <a href="https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety">object safe</a></strong>, because schema resolvers will need to return a <a href="https://doc.rust-lang.org/stable/reference/types/trait-object.html">trait object</a> to specify a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> behind it.</p>
|
||||
</blockquote>
|
||||
<pre><pre class="playpen"><code class="language-rust">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 {
|
||||
// Downcast methods, each concrete class will need to implement one of these
|
||||
// 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 }
|
||||
}
|
||||
|
@ -1623,30 +1828,23 @@ impl Character for Human {
|
|||
impl Character for Droid {
|
||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||
}
|
||||
|
||||
#[juniper::graphql_union]
|
||||
impl<'a> GraphQLUnion for &'a dyn Character {
|
||||
fn resolve(&self) {
|
||||
match self {
|
||||
Human => self.as_human(),
|
||||
Droid => self.as_droid(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#using-an-extra-database-lookup-1" id="using-an-extra-database-lookup-1"><h3>Using an extra database lookup</h3></a>
|
||||
<p>FIXME: This example does not compile at the moment</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
<a class="header" href="#custom-context" id="custom-context"><h3>Custom context</h3></a>
|
||||
<p>If a context is required in a trait method to resolve a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant, specify it as an argument.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(unused_variables)]
|
||||
# use std::collections::HashMap;
|
||||
use juniper::{graphql_union, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
|
@ -1657,10 +1855,97 @@ struct Database {
|
|||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
#[graphql_union(Context = Database)]
|
||||
trait Character {
|
||||
// NOTICE: The method signature may optionally contain `&Context`.
|
||||
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)
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#ignoring-trait-methods" id="ignoring-trait-methods"><h3>Ignoring trait methods</h3></a>
|
||||
<p>As with enums, we may want to omit some trait methods to be assumed as <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variants and ignore them.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">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 {
|
||||
fn as_human(&self) -> Option<&Human> { None }
|
||||
fn as_droid(&self) -> Option<&Droid> { None }
|
||||
#[graphql_union(ignore)] // or `#[graphql_union(skip)]`, your choice
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn as_human(&self) -> Option<&Human> { Some(&self) }
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#external-resolver-functions-1" id="external-resolver-functions-1"><h3>External resolver functions</h3></a>
|
||||
<p>Similarly to enums and structs, it's not mandatory to use trait methods as <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant resolvers. Instead, custom functions may be specified:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
use juniper::{graphql_union, GraphQLObject};
|
||||
|
||||
#[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_union(ignore)] // or `#[graphql_union(skip)]`, your choice
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
|
@ -1672,116 +1957,48 @@ impl Character for Droid {
|
|||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
// The trait object is always `Send` and `Sync`.
|
||||
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
|
||||
|
||||
#[juniper::graphql_union(
|
||||
Context = Database
|
||||
)]
|
||||
impl<'a> GraphQLUnion for &'a dyn Character {
|
||||
fn resolve(&self, context: &Database) {
|
||||
match self {
|
||||
Human => context.humans.get(self.id()),
|
||||
Droid => context.droids.get(self.id()),
|
||||
}
|
||||
impl<'a> DynCharacter<'a> {
|
||||
fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
|
||||
ctx.humans.get(self.id())
|
||||
}
|
||||
}
|
||||
|
||||
// External 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())
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#placeholder-objects-1" id="placeholder-objects-1"><h2>Placeholder objects</h2></a>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
<a class="header" href="#scalarvalue-considerations" id="scalarvalue-considerations"><h2><code>ScalarValue</code> considerations</h2></a>
|
||||
<p>By default, <code>#[derive(GraphQLUnion)]</code> and <code>#[graphql_union]</code> macros generate code, which is generic over a <a href="https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html"><code>ScalarValue</code></a> type. This may introduce a problem when at least one of <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variants is restricted to a concrete <a href="https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html"><code>ScalarValue</code></a> type in its implementation. To resolve such problem, a concrete <a href="https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html"><code>ScalarValue</code></a> type should be specified:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Scalar = DefaultScalarValue)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
struct Character {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[juniper::graphql_union(
|
||||
Context = Database,
|
||||
)]
|
||||
impl GraphQLUnion for Character {
|
||||
fn resolve(&self, context: &Database) {
|
||||
match self {
|
||||
Human => { context.humans.get(&self.id) },
|
||||
Droid => { context.droids.get(&self.id) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#enums-impl" id="enums-impl"><h2>Enums (Impl)</h2></a>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
# #[allow(dead_code)]
|
||||
#[derive(GraphQLUnion)]
|
||||
#[graphql(Scalar = DefaultScalarValue)] // removing this line will fail compilation
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
|
||||
#[juniper::graphql_union]
|
||||
impl Character {
|
||||
fn resolve(&self) {
|
||||
match self {
|
||||
Human => { match *self { Character::Human(ref h) => Some(h), _ => None } },
|
||||
Droid => { match *self { Character::Droid(ref d) => Some(d), _ => None } },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#enums-derive" id="enums-derive"><h2>Enums (Derive)</h2></a>
|
||||
<p>This example is similar to <code>Enums (Impl)</code>. To successfully use the
|
||||
derive macro, ensure that each variant of the enum has a different
|
||||
type. Since each variant is different, the device macro provides
|
||||
<code>std::convert::Into<T></code> converter for each variant.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLUnion)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#schemas" id="schemas"><h1>Schemas</h1></a>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -137,31 +137,236 @@
|
|||
<div id="content" class="content">
|
||||
<main>
|
||||
<a class="header" href="#unions" id="unions"><h1>Unions</h1></a>
|
||||
<p>From a server's point of view, GraphQL unions are similar to interfaces: the
|
||||
only exception is that they don't contain fields on their own.</p>
|
||||
<p>In Juniper, the <code>graphql_union!</code> has identical syntax to the
|
||||
<a href="interfaces.html">interface macro</a>, but does not support defining
|
||||
fields. Therefore, the same considerations about using traits,
|
||||
placeholder types, or enums still apply to unions. For simple
|
||||
situations, Juniper provides <code>#[derive(GraphQLUnion)]</code> for enums.</p>
|
||||
<p>If we look at the same examples as in the interfaces chapter, we see the
|
||||
similarities and the tradeoffs:</p>
|
||||
<a class="header" href="#traits" id="traits"><h2>Traits</h2></a>
|
||||
<a class="header" href="#downcasting-via-accessor-methods" id="downcasting-via-accessor-methods"><h3>Downcasting via accessor methods</h3></a>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
||||
<p>From the server's point of view, <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL unions</a> are similar to interfaces - the only exception is that they don't contain fields on their own.</p>
|
||||
<p>For implementing <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL unions</a> Juniper provides:</p>
|
||||
<ul>
|
||||
<li><code>#[derive(GraphQLUnion)]</code> macro for enums and structs.</li>
|
||||
<li><code>#[graphql_union]</code> for traits.</li>
|
||||
</ul>
|
||||
<a class="header" href="#enums" id="enums"><h2>Enums</h2></a>
|
||||
<p>Most of the time, we just need a trivial and straightforward Rust enum to represent a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a>.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use derive_more::From;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
#[derive(From, GraphQLUnion)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#ignoring-enum-variants" id="ignoring-enum-variants"><h3>Ignoring enum variants</h3></a>
|
||||
<p>In some rare situations we may want to omit exposing an enum variant in the GraphQL schema.</p>
|
||||
<p>As an example, let's consider the situation where we need to bind some type parameter <code>T</code> for doing interesting type-level stuff in our resolvers. To achieve this we need to have <code>PhantomData<T></code>, but we don't want it exposed in the GraphQL schema.</p>
|
||||
<blockquote>
|
||||
<p><strong>WARNING</strong>:<br />
|
||||
It's the <em>library user's responsibility</em> to ensure that ignored enum variant is <em>never</em> returned from resolvers, otherwise resolving the GraphQL query will <strong>panic at runtime</strong>.</p>
|
||||
</blockquote>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::marker::PhantomData;
|
||||
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>),
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#external-resolver-functions" id="external-resolver-functions"><h3>External resolver functions</h3></a>
|
||||
<p>If some custom logic is needed to resolve a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant, you may specify an external function to do so:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<p>With an external resolver function we can even declare a new <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant where the Rust type is absent in the initial enum definition. The attribute syntax <code>#[graphql(on VariantType = resolver_fn)]</code> follows the <a href="https://spec.graphql.org/June2018/#example-f8163">GraphQL syntax for dispatching union variants</a>.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
struct Ewok {
|
||||
id: String,
|
||||
is_funny: bool,
|
||||
}
|
||||
|
||||
pub struct CustomContext {
|
||||
ewok: Ewok,
|
||||
}
|
||||
impl juniper::Context for CustomContext {}
|
||||
|
||||
#[derive(GraphQLUnion)]
|
||||
#[graphql(Context = CustomContext)]
|
||||
#[graphql(on Ewok = Character::ewok_from_context)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
#[graphql(ignore)] // or `#[graphql(skip)]`, your choice
|
||||
Ewok,
|
||||
}
|
||||
|
||||
impl Character {
|
||||
fn ewok_from_context<'c>(&self, ctx: &'c CustomContext) -> Option<&'c Ewok> {
|
||||
if let Self::Ewok = self {
|
||||
Some(&ctx.ewok)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#structs" id="structs"><h2>Structs</h2></a>
|
||||
<p>Using Rust structs as <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL unions</a> is very similar to using enums, with the nuance that specifying an external resolver function is the only way to declare a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
use juniper::{GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#traits" id="traits"><h2>Traits</h2></a>
|
||||
<p>To use a Rust trait definition as a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> you need to use the <code>#[graphql_union]</code> macro. <a href="https://doc.rust-lang.org/stable/reference/procedural-macros.html#derive-macros">Rust doesn't allow derive macros on traits</a>, so using <code>#[derive(GraphQLUnion)]</code> on traits doesn't work.</p>
|
||||
<blockquote>
|
||||
<p><strong>NOTICE</strong>:<br />
|
||||
A <strong>trait has to be <a href="https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety">object safe</a></strong>, because schema resolvers will need to return a <a href="https://doc.rust-lang.org/stable/reference/types/trait-object.html">trait object</a> to specify a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> behind it.</p>
|
||||
</blockquote>
|
||||
<pre><pre class="playpen"><code class="language-rust">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 {
|
||||
// Downcast methods, each concrete class will need to implement one of these
|
||||
// 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 }
|
||||
}
|
||||
|
@ -173,30 +378,23 @@ impl Character for Human {
|
|||
impl Character for Droid {
|
||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||
}
|
||||
|
||||
#[juniper::graphql_union]
|
||||
impl<'a> GraphQLUnion for &'a dyn Character {
|
||||
fn resolve(&self) {
|
||||
match self {
|
||||
Human => self.as_human(),
|
||||
Droid => self.as_droid(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#using-an-extra-database-lookup" id="using-an-extra-database-lookup"><h3>Using an extra database lookup</h3></a>
|
||||
<p>FIXME: This example does not compile at the moment</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
<a class="header" href="#custom-context" id="custom-context"><h3>Custom context</h3></a>
|
||||
<p>If a context is required in a trait method to resolve a <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant, specify it as an argument.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(unused_variables)]
|
||||
# use std::collections::HashMap;
|
||||
use juniper::{graphql_union, GraphQLObject};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
|
@ -207,10 +405,97 @@ struct Database {
|
|||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
#[graphql_union(Context = Database)]
|
||||
trait Character {
|
||||
// NOTICE: The method signature may optionally contain `&Context`.
|
||||
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)
|
||||
}
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#ignoring-trait-methods" id="ignoring-trait-methods"><h3>Ignoring trait methods</h3></a>
|
||||
<p>As with enums, we may want to omit some trait methods to be assumed as <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variants and ignore them.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">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 {
|
||||
fn as_human(&self) -> Option<&Human> { None }
|
||||
fn as_droid(&self) -> Option<&Droid> { None }
|
||||
#[graphql_union(ignore)] // or `#[graphql_union(skip)]`, your choice
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
impl Character for Human {
|
||||
fn as_human(&self) -> Option<&Human> { Some(&self) }
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
impl Character for Droid {
|
||||
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
|
||||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#external-resolver-functions-1" id="external-resolver-functions-1"><h3>External resolver functions</h3></a>
|
||||
<p>Similarly to enums and structs, it's not mandatory to use trait methods as <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variant resolvers. Instead, custom functions may be specified:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
use juniper::{graphql_union, GraphQLObject};
|
||||
|
||||
#[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_union(ignore)] // or `#[graphql_union(skip)]`, your choice
|
||||
fn id(&self) -> &str;
|
||||
}
|
||||
|
||||
|
@ -222,116 +507,48 @@ impl Character for Droid {
|
|||
fn id(&self) -> &str { self.id.as_str() }
|
||||
}
|
||||
|
||||
// The trait object is always `Send` and `Sync`.
|
||||
type DynCharacter<'a> = dyn Character + Send + Sync + 'a;
|
||||
|
||||
#[juniper::graphql_union(
|
||||
Context = Database
|
||||
)]
|
||||
impl<'a> GraphQLUnion for &'a dyn Character {
|
||||
fn resolve(&self, context: &Database) {
|
||||
match self {
|
||||
Human => context.humans.get(self.id()),
|
||||
Droid => context.droids.get(self.id()),
|
||||
}
|
||||
impl<'a> DynCharacter<'a> {
|
||||
fn get_human<'db>(&self, ctx: &'db Database) -> Option<&'db Human> {
|
||||
ctx.humans.get(self.id())
|
||||
}
|
||||
}
|
||||
|
||||
// External 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())
|
||||
}
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#placeholder-objects" id="placeholder-objects"><h2>Placeholder objects</h2></a>
|
||||
<pre><pre class="playpen"><code class="language-rust"># use std::collections::HashMap;
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
<a class="header" href="#scalarvalue-considerations" id="scalarvalue-considerations"><h2><code>ScalarValue</code> considerations</h2></a>
|
||||
<p>By default, <code>#[derive(GraphQLUnion)]</code> and <code>#[graphql_union]</code> macros generate code, which is generic over a <a href="https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html"><code>ScalarValue</code></a> type. This may introduce a problem when at least one of <a href="https://spec.graphql.org/June2018/#sec-Unions">GraphQL union</a> variants is restricted to a concrete <a href="https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html"><code>ScalarValue</code></a> type in its implementation. To resolve such problem, a concrete <a href="https://docs.rs/juniper/latest/juniper/trait.ScalarValue.html"><code>ScalarValue</code></a> type should be specified:</p>
|
||||
<pre><pre class="playpen"><code class="language-rust"># #![allow(dead_code)]
|
||||
use juniper::{DefaultScalarValue, GraphQLObject, GraphQLUnion};
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
#[graphql(Scalar = DefaultScalarValue)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
#[graphql(Context = Database)]
|
||||
#[derive(GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
struct Database {
|
||||
humans: HashMap<String, Human>,
|
||||
droids: HashMap<String, Droid>,
|
||||
}
|
||||
|
||||
impl juniper::Context for Database {}
|
||||
|
||||
struct Character {
|
||||
id: String,
|
||||
}
|
||||
|
||||
#[juniper::graphql_union(
|
||||
Context = Database,
|
||||
)]
|
||||
impl GraphQLUnion for Character {
|
||||
fn resolve(&self, context: &Database) {
|
||||
match self {
|
||||
Human => { context.humans.get(&self.id) },
|
||||
Droid => { context.droids.get(&self.id) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#enums-impl" id="enums-impl"><h2>Enums (Impl)</h2></a>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
# #[allow(dead_code)]
|
||||
#[derive(GraphQLUnion)]
|
||||
#[graphql(Scalar = DefaultScalarValue)] // removing this line will fail compilation
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
|
||||
#[juniper::graphql_union]
|
||||
impl Character {
|
||||
fn resolve(&self) {
|
||||
match self {
|
||||
Human => { match *self { Character::Human(ref h) => Some(h), _ => None } },
|
||||
Droid => { match *self { Character::Droid(ref d) => Some(d), _ => None } },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
<a class="header" href="#enums-derive" id="enums-derive"><h2>Enums (Derive)</h2></a>
|
||||
<p>This example is similar to <code>Enums (Impl)</code>. To successfully use the
|
||||
derive macro, ensure that each variant of the enum has a different
|
||||
type. Since each variant is different, the device macro provides
|
||||
<code>std::convert::Into<T></code> converter for each variant.</p>
|
||||
<pre><pre class="playpen"><code class="language-rust">#[derive(juniper::GraphQLObject)]
|
||||
struct Human {
|
||||
id: String,
|
||||
home_planet: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
struct Droid {
|
||||
id: String,
|
||||
primary_function: String,
|
||||
}
|
||||
|
||||
#[derive(juniper::GraphQLUnion)]
|
||||
enum Character {
|
||||
Human(Human),
|
||||
Droid(Droid),
|
||||
}
|
||||
|
||||
#
|
||||
# fn main() {}
|
||||
</code></pre></pre>
|
||||
|
||||
|
|
Loading…
Reference in a new issue