Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions docs/book/content/types/unions.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
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.

In Juniper, the `graphql_union!` has identical syntax to the [interface
macro](interfaces.md), but does not support defining fields. Therefore, the same
considerations about using traits, placeholder types, or enums still apply to
unions.
In Juniper, the `graphql_union!` has identical syntax to the
[interface macro](interfaces.md), 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 `#[derive(GraphQLUnion)]` for enums.

If we look at the same examples as in the interfaces chapter, we see the
similarities and the tradeoffs:
Expand Down Expand Up @@ -154,7 +155,7 @@ impl GraphQLUnion for Character {
# fn main() {}
```

## Enums
## Enums (Impl)

```rust
#[derive(juniper::GraphQLObject)]
Expand Down Expand Up @@ -187,3 +188,32 @@ impl Character {

# fn main() {}
```

## Enums (Derive)

This example is similar to `Enums (Impl)`. 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
`std::convert::Into<T>` converter for each variant.

```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() {}
```
268 changes: 268 additions & 0 deletions integration_tests/juniper_tests/src/codegen/derive_union.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// Test for union's derive macro

#[cfg(test)]
use fnv::FnvHashMap;

#[cfg(test)]
use juniper::{
self, execute, DefaultScalarValue, EmptyMutation, EmptySubscription, GraphQLType, RootNode,
Value, Variables,
};

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

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

#[derive(juniper::GraphQLUnion)]
#[graphql(description = "A Collection of things")]
pub enum Character {
One(Human),
Two(Droid),
}

// Context Test
pub struct CustomContext {
is_left: bool,
}

impl juniper::Context for CustomContext {}

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

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

/// A Collection of things
#[derive(juniper::GraphQLUnion)]
#[graphql(Context = CustomContext)]
pub enum CharacterContext {
One(HumanContext),
Two(DroidContext),
}

// #[juniper::object] compatibility

pub struct HumanCompat {
id: String,
home_planet: String,
}

#[juniper::graphql_object]
impl HumanCompat {
fn id(&self) -> &String {
&self.id
}

fn home_planet(&self) -> &String {
&self.home_planet
}
}

pub struct DroidCompat {
id: String,
primary_function: String,
}

#[juniper::graphql_object]
impl DroidCompat {
fn id(&self) -> &String {
&self.id
}

fn primary_function(&self) -> &String {
&self.primary_function
}
}

// NOTICE: this can not compile due to generic implementation of GraphQLType<__S>
// #[derive(juniper::GraphQLUnion)]
// pub enum CharacterCompatFail {
// One(HumanCompat),
// Two(DroidCompat),
// }

/// A Collection of things
#[derive(juniper::GraphQLUnion)]
#[graphql(Scalar = juniper::DefaultScalarValue)]
pub enum CharacterCompat {
One(HumanCompat),
Two(DroidCompat),
}

pub struct Query;

#[juniper::graphql_object(
Context = CustomContext,
)]
impl Query {
fn context(&self, ctx: &CustomContext) -> CharacterContext {
if ctx.is_left {
HumanContext {
id: "human-32".to_string(),
home_planet: "earth".to_string(),
}
.into()
} else {
DroidContext {
id: "droid-99".to_string(),
primary_function: "run".to_string(),
}
.into()
}
}
}

#[tokio::test]
async fn test_derived_union_doc_macro() {
assert_eq!(
<Character as GraphQLType<DefaultScalarValue>>::name(&()),
Some("Character")
);

let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
let meta = Character::meta(&(), &mut registry);

assert_eq!(meta.name(), Some("Character"));
assert_eq!(
meta.description(),
Some(&"A Collection of things".to_string())
);
}

#[tokio::test]
async fn test_derived_union_doc_string() {
assert_eq!(
<CharacterContext as GraphQLType<DefaultScalarValue>>::name(&()),
Some("CharacterContext")
);

let mut registry: juniper::Registry = juniper::Registry::new(FnvHashMap::default());
let meta = CharacterContext::meta(&(), &mut registry);

assert_eq!(meta.name(), Some("CharacterContext"));
assert_eq!(
meta.description(),
Some(&"A Collection of things".to_string())
);
}

#[tokio::test]
async fn test_derived_union_left() {
let doc = r#"
{
context {
... on HumanContext {
humanId: id
homePlanet
}
... on DroidContext {
droidId: id
primaryFunction
}
}
}"#;

let schema = RootNode::new(
Query,
EmptyMutation::<CustomContext>::new(),
EmptySubscription::<CustomContext>::new(),
);

assert_eq!(
execute(
doc,
None,
&schema,
&Variables::new(),
&CustomContext { is_left: true }
)
.await,
Ok((
Value::object(
vec![(
"context",
Value::object(
vec![
("humanId", Value::scalar("human-32".to_string())),
("homePlanet", Value::scalar("earth".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}

#[tokio::test]
async fn test_derived_union_right() {
let doc = r#"
{
context {
... on HumanContext {
humanId: id
homePlanet
}
... on DroidContext {
droidId: id
primaryFunction
}
}
}"#;

let schema = RootNode::new(
Query,
EmptyMutation::<CustomContext>::new(),
EmptySubscription::<CustomContext>::new(),
);

assert_eq!(
execute(
doc,
None,
&schema,
&Variables::new(),
&CustomContext { is_left: false }
)
.await,
Ok((
Value::object(
vec![(
"context",
Value::object(
vec![
("droidId", Value::scalar("droid-99".to_string())),
("primaryFunction", Value::scalar("run".to_string())),
]
.into_iter()
.collect(),
),
)]
.into_iter()
.collect()
),
vec![]
))
);
}
1 change: 1 addition & 0 deletions integration_tests/juniper_tests/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ mod derive_enum;
mod derive_input_object;
mod derive_object;
mod derive_object_with_raw_idents;
mod derive_union;
mod impl_union;
mod scalar_value_transparent;
5 changes: 5 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ See [#419](https://github.com/graphql-rust/juniper/pull/419).

See [#569](https://github.com/graphql-rust/juniper/pull/569).

- GraphQLUnion derive support ("#[derive(GraphqQLUnion)]")
- implements GraphQLAsyncType

See [#618](https://github.com/graphql-rust/juniper/pull/618).

## Breaking Changes

- `juniper::graphiql` has moved to `juniper::http::graphiql`
Expand Down
2 changes: 1 addition & 1 deletion juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ extern crate bson;
// functionality automatically.
pub use juniper_codegen::{
graphql_object, graphql_subscription, graphql_union, GraphQLEnum, GraphQLInputObject,
GraphQLObject, GraphQLScalarValue,
GraphQLObject, GraphQLScalarValue, GraphQLUnion,
};
// Internal macros are not exported,
// but declared at the root to make them easier to use.
Expand Down
Loading