Change Unions to enums instead of structs
This commit is contained in:
parent
6f70924967
commit
b7bc7fea12
|
@ -2,24 +2,24 @@ use crate::args;
|
|||
use crate::utils::{check_reserved_name, get_crate_name, get_rustdoc};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::collections::HashSet;
|
||||
use syn::{Data, DeriveInput, Error, Fields, Result, Type};
|
||||
|
||||
pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<TokenStream> {
|
||||
let crate_name = get_crate_name(union_args.internal);
|
||||
let ident = &input.ident;
|
||||
let generics = &input.generics;
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let s = match &input.data {
|
||||
Data::Struct(s) => s,
|
||||
_ => return Err(Error::new_spanned(input, "It should be a struct.")),
|
||||
};
|
||||
let fields = match &s.fields {
|
||||
Fields::Unnamed(fields) => fields,
|
||||
_ => return Err(Error::new_spanned(input, "All fields must be unnamed.")),
|
||||
Data::Enum(s) => s,
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
"Unions can only be applied to an enum.",
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut enum_names = Vec::new();
|
||||
let mut enum_items = Vec::new();
|
||||
let mut enum_items = HashSet::new();
|
||||
let mut type_into_impls = Vec::new();
|
||||
let gql_typename = union_args.name.clone().unwrap_or_else(|| ident.to_string());
|
||||
check_reserved_name(&gql_typename, union_args.internal)?;
|
||||
|
@ -36,11 +36,39 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
|
|||
let mut collect_inline_fields = Vec::new();
|
||||
let mut get_introspection_typename = Vec::new();
|
||||
|
||||
for field in &fields.unnamed {
|
||||
for variant in s.variants.iter() {
|
||||
let enum_name = &variant.ident;
|
||||
let field = match &variant.fields {
|
||||
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => fields.unnamed.first().unwrap(),
|
||||
Fields::Unnamed(_) => {
|
||||
return Err(Error::new_spanned(
|
||||
variant,
|
||||
"Only single value variants are supported",
|
||||
))
|
||||
}
|
||||
Fields::Unit => {
|
||||
return Err(Error::new_spanned(
|
||||
variant,
|
||||
"Empty variants are not supported",
|
||||
))
|
||||
}
|
||||
Fields::Named(_) => {
|
||||
return Err(Error::new_spanned(
|
||||
variant,
|
||||
"Variants with named fields are not supported",
|
||||
))
|
||||
}
|
||||
};
|
||||
if let Type::Path(p) = &field.ty {
|
||||
let enum_name = &p.path.segments.last().unwrap().ident;
|
||||
// This validates that the field type wasn't already used
|
||||
if enum_items.insert(p) == false {
|
||||
return Err(Error::new_spanned(
|
||||
field,
|
||||
"This type already used in another variant",
|
||||
));
|
||||
}
|
||||
|
||||
enum_names.push(enum_name);
|
||||
enum_items.push(quote! { #enum_name(#p) });
|
||||
type_into_impls.push(quote! {
|
||||
impl #generics From<#p> for #ident #generics {
|
||||
fn from(obj: #p) -> Self {
|
||||
|
@ -68,8 +96,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
|
|||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#(#attrs)*
|
||||
#vis enum #ident #generics { #(#enum_items),* }
|
||||
#input
|
||||
|
||||
#(#type_into_impls)*
|
||||
|
||||
|
|
63
src/lib.rs
63
src/lib.rs
|
@ -529,7 +529,68 @@ pub use async_graphql_derive::Interface;
|
|||
|
||||
/// Define a GraphQL union
|
||||
///
|
||||
/// It's similar to Interface, but it doesn't have fields.
|
||||
///
|
||||
/// # Macro parameters
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
/// | name | Object name | string | Y |
|
||||
/// | desc | Object description | string | Y |
|
||||
///
|
||||
/// # Define a union
|
||||
///
|
||||
/// Define TypeA, TypeB, ... as MyUnion
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_graphql::*;
|
||||
///
|
||||
/// #[SimpleObject]
|
||||
/// struct TypeA {
|
||||
/// value_a: i32,
|
||||
/// }
|
||||
///
|
||||
/// #[SimpleObject]
|
||||
/// struct TypeB {
|
||||
/// value_b: i32
|
||||
/// }
|
||||
///
|
||||
/// #[Union]
|
||||
/// enum MyUnion {
|
||||
/// TypeA(TypeA),
|
||||
/// TypeB(TypeB),
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// async fn all_data(&self) -> Vec<MyUnion> {
|
||||
/// vec![TypeA { value_a: 10 }.into(), TypeB { value_b: 20 }.into()]
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[async_std::main]
|
||||
/// async fn main() {
|
||||
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let res = schema.execute(r#"
|
||||
/// {
|
||||
/// allData {
|
||||
/// ... on TypeA {
|
||||
/// valueA
|
||||
/// }
|
||||
/// ... on TypeB {
|
||||
/// valueB
|
||||
/// }
|
||||
/// }
|
||||
/// }"#).await.unwrap().data;
|
||||
/// assert_eq!(res, serde_json::json!({
|
||||
/// "allData": [
|
||||
/// { "valueA": 10 },
|
||||
/// { "valueB": 20 },
|
||||
/// ]
|
||||
/// }));
|
||||
/// }
|
||||
/// ```
|
||||
pub use async_graphql_derive::Union;
|
||||
|
||||
/// Define a GraphQL subscription
|
||||
|
|
|
@ -86,7 +86,10 @@ impl Cat {
|
|||
}
|
||||
|
||||
#[Union(internal)]
|
||||
struct CatOrDog(Cat, Dog);
|
||||
enum CatOrDog {
|
||||
Cat(Cat),
|
||||
Dog(Dog),
|
||||
}
|
||||
|
||||
struct Human;
|
||||
|
||||
|
@ -127,10 +130,16 @@ impl Alien {
|
|||
}
|
||||
|
||||
#[Union(internal)]
|
||||
struct DogOrHuman(Dog, Human);
|
||||
enum DogOrHuman {
|
||||
Dog(Dog),
|
||||
Human(Human),
|
||||
}
|
||||
|
||||
#[Union(internal)]
|
||||
struct HumanOrAlien(Human, Alien);
|
||||
enum HumanOrAlien {
|
||||
Human(Human),
|
||||
Alien(Alien),
|
||||
}
|
||||
|
||||
#[Interface(
|
||||
internal,
|
||||
|
|
287
tests/union.rs
Normal file
287
tests/union.rs
Normal file
|
@ -0,0 +1,287 @@
|
|||
use async_graphql::*;
|
||||
|
||||
#[async_std::test]
|
||||
pub async fn test_union_simple_object() {
|
||||
#[async_graphql::SimpleObject]
|
||||
struct MyObj {
|
||||
id: i32,
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum Node {
|
||||
MyObj(MyObj),
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn node(&self) -> Node {
|
||||
MyObj {
|
||||
id: 33,
|
||||
title: "haha".to_string(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
let query = r#"{
|
||||
node {
|
||||
... on MyObj {
|
||||
id
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.unwrap().data,
|
||||
serde_json::json!({
|
||||
"node": {
|
||||
"id": 33,
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
pub async fn test_union_simple_object2() {
|
||||
#[async_graphql::SimpleObject]
|
||||
struct MyObj {
|
||||
#[field(ref)]
|
||||
id: i32,
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum Node {
|
||||
MyObj(MyObj),
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn node(&self) -> Node {
|
||||
MyObj {
|
||||
id: 33,
|
||||
title: "haha".to_string(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
let query = r#"{
|
||||
node {
|
||||
... on MyObj {
|
||||
id
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.unwrap().data,
|
||||
serde_json::json!({
|
||||
"node": {
|
||||
"id": 33,
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
pub async fn test_multiple_unions() {
|
||||
struct MyObj;
|
||||
|
||||
#[async_graphql::Object]
|
||||
impl MyObj {
|
||||
async fn value_a(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
async fn value_b(&self) -> i32 {
|
||||
2
|
||||
}
|
||||
|
||||
async fn value_c(&self) -> i32 {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum UnionA {
|
||||
MyObj(MyObj),
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum UnionB {
|
||||
MyObj(MyObj),
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn union_a(&self) -> UnionA {
|
||||
MyObj.into()
|
||||
}
|
||||
async fn union_b(&self) -> UnionB {
|
||||
MyObj.into()
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||
.register_type::<UnionA>() // `UnionA` is not directly referenced, so manual registration is required.
|
||||
.finish();
|
||||
let query = r#"{
|
||||
unionA {
|
||||
... on MyObj {
|
||||
valueA
|
||||
valueB
|
||||
valueC
|
||||
}
|
||||
}
|
||||
unionB {
|
||||
... on MyObj {
|
||||
valueA
|
||||
valueB
|
||||
valueC
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.unwrap().data,
|
||||
serde_json::json!({
|
||||
"unionA": {
|
||||
"valueA": 1,
|
||||
"valueB": 2,
|
||||
"valueC": 3,
|
||||
},
|
||||
"unionB": {
|
||||
"valueA": 1,
|
||||
"valueB": 2,
|
||||
"valueC": 3,
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
pub async fn test_multiple_objects_in_multiple_unions() {
|
||||
struct MyObjOne;
|
||||
|
||||
#[async_graphql::Object]
|
||||
impl MyObjOne {
|
||||
async fn value_a(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
async fn value_b(&self) -> i32 {
|
||||
2
|
||||
}
|
||||
|
||||
async fn value_c(&self) -> i32 {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
struct MyObjTwo;
|
||||
|
||||
#[async_graphql::Object]
|
||||
impl MyObjTwo {
|
||||
async fn value_a(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum UnionA {
|
||||
MyObjOne(MyObjOne),
|
||||
MyObjTwo(MyObjTwo),
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum UnionB {
|
||||
MyObjOne(MyObjOne),
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn my_obj(&self) -> Vec<UnionA> {
|
||||
vec![MyObjOne.into(), MyObjTwo.into()]
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||
.register_type::<UnionB>() // `UnionB` is not directly referenced, so manual registration is required.
|
||||
.finish();
|
||||
let query = r#"{
|
||||
myObj {
|
||||
... on MyObjTwo {
|
||||
valueA
|
||||
}
|
||||
... on MyObjOne {
|
||||
valueA
|
||||
valueB
|
||||
valueC
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.unwrap().data,
|
||||
serde_json::json!({
|
||||
"myObj": [{
|
||||
"valueA": 1,
|
||||
"valueB": 2,
|
||||
"valueC": 3,
|
||||
}, {
|
||||
"valueA": 1
|
||||
}]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
pub async fn test_union_field_result() {
|
||||
struct MyObj;
|
||||
|
||||
#[async_graphql::Object]
|
||||
impl MyObj {
|
||||
async fn value(&self) -> FieldResult<i32> {
|
||||
Ok(10)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_graphql::Union]
|
||||
enum Node {
|
||||
MyObj(MyObj),
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn node(&self) -> Node {
|
||||
MyObj.into()
|
||||
}
|
||||
}
|
||||
|
||||
let query = r#"{
|
||||
node {
|
||||
... on MyObj {
|
||||
value
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.unwrap().data,
|
||||
serde_json::json!({
|
||||
"node": {
|
||||
"value": 10,
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user