Add entity lookup support for MergedObject.

Add some GraphQL specification constraints for all derived macros.

Use `Registry::create_dummy_type` to create a merged type.
This commit is contained in:
Sunli 2020-10-20 11:49:31 +08:00
parent b5e602342d
commit e3d693da28
26 changed files with 300 additions and 82 deletions

View File

@ -83,6 +83,8 @@ pub struct SimpleObject {
#[darling(default)] #[darling(default)]
pub internal: bool, pub internal: bool,
#[darling(default)] #[darling(default)]
pub dummy: bool,
#[darling(default)]
pub name: Option<String>, pub name: Option<String>,
#[darling(default)] #[darling(default)]
pub rename_fields: Option<RenameRule>, pub rename_fields: Option<RenameRule>,

View File

@ -111,6 +111,14 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
None None
}; };
if schema_enum_items.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Enum type must define one or more unique enum values.",
)
.into());
}
let expanded = quote! { let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
impl #crate_name::resolver_utils::EnumType for #ident { impl #crate_name::resolver_utils::EnumType for #ident {

View File

@ -67,9 +67,9 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
schema_fields.push(quote! { schema_fields.push(quote! {
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::InputObjectType); #crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::InputObjectType);
#ty::create_type_info(registry); #ty::create_type_info(registry);
if let Some(#crate_name::registry::MetaType::InputObject { input_fields, .. }) = if let #crate_name::registry::MetaType::InputObject { input_fields, .. } =
registry.types.get(&*<#ty as #crate_name::Type>::type_name()) { registry.create_dummy_type::<#ty>() {
fields.extend(::std::clone::Clone::clone(input_fields)); fields.extend(input_fields);
} }
}); });
@ -151,6 +151,14 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
}) })
} }
if get_fields.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Input Object type must define one or more input fields.",
)
.into());
}
let expanded = quote! { let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #ident { impl #crate_name::Type for #ident {

View File

@ -121,6 +121,14 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
let mut schema_fields = Vec::new(); let mut schema_fields = Vec::new();
let mut resolvers = Vec::new(); let mut resolvers = Vec::new();
if interface_args.fields.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Interface type must define one or more fields.",
)
.into());
}
for InterfaceField { for InterfaceField {
name, name,
method, method,

View File

@ -62,18 +62,16 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
registry.create_type::<Self, _>(|registry| { registry.create_type::<Self, _>(|registry| {
#merged_type::create_type_info(registry);
let mut fields = ::std::default::Default::default(); let mut fields = ::std::default::Default::default();
let mut cache_control = ::std::default::Default::default(); let mut cache_control = ::std::default::Default::default();
if let ::std::option::Option::Some(#crate_name::registry::MetaType::Object { if let #crate_name::registry::MetaType::Object {
fields: obj_fields, fields: obj_fields,
cache_control: obj_cache_control, cache_control: obj_cache_control,
.. ..
}) = registry.types.get(&*#merged_type::type_name()) { } = registry.create_dummy_type::<#merged_type>() {
fields = ::std::clone::Clone::clone(obj_fields); fields = obj_fields;
cache_control = *obj_cache_control; cache_control = obj_cache_control;
} }
#crate_name::registry::MetaType::Object { #crate_name::registry::MetaType::Object {
@ -94,6 +92,10 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#create_merged_obj.resolve_field(ctx).await #create_merged_obj.resolve_field(ctx).await
} }
async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#create_merged_obj.find_entity(ctx, params).await
}
} }
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]

View File

@ -53,15 +53,13 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String { fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
registry.create_type::<Self, _>(|registry| { registry.create_type::<Self, _>(|registry| {
#merged_type::create_type_info(registry);
let mut fields = ::std::default::Default::default(); let mut fields = ::std::default::Default::default();
if let ::std::option::Option::Some(#crate_name::registry::MetaType::Object { if let #crate_name::registry::MetaType::Object {
fields: obj_fields, fields: obj_fields,
.. ..
}) = registry.types.get(&*#merged_type::type_name()) { } = registry.create_dummy_type::<#merged_type>() {
fields = ::std::clone::Clone::clone(obj_fields); fields = obj_fields;
} }
#crate_name::registry::MetaType::Object { #crate_name::registry::MetaType::Object {

View File

@ -471,6 +471,14 @@ pub fn generate(
find_entities.sort_by(|(a, _), (b, _)| b.cmp(a)); find_entities.sort_by(|(a, _), (b, _)| b.cmp(a));
let find_entities_iter = find_entities.iter().map(|(_, code)| code); let find_entities_iter = find_entities.iter().map(|(_, code)| code);
if resolvers.is_empty() && create_entity_types.is_empty() {
return Err(Error::new_spanned(
&self_ty,
"An GraphQL Object type must define one or more fields.",
)
.into());
}
let expanded = quote! { let expanded = quote! {
#item_impl #item_impl

View File

@ -129,6 +129,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
}); });
} }
if !object_args.dummy && resolvers.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Object type must define one or more fields.",
)
.into());
}
let cache_control = { let cache_control = {
let public = object_args.cache_control.is_public(); let public = object_args.cache_control.is_public();
let max_age = object_args.cache_control.max_age; let max_age = object_args.cache_control.max_age;
@ -181,7 +189,6 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics #where_clause { impl #generics #crate_name::OutputValueType for #ident #generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await #crate_name::resolver_utils::resolve_container(ctx, self).await
} }

View File

@ -364,6 +364,14 @@ pub fn generate(
} }
} }
if create_stream.is_empty() {
return Err(Error::new_spanned(
&self_ty,
"An GraphQL Object type must define one or more fields.",
)
.into());
}
let expanded = quote! { let expanded = quote! {
#item_impl #item_impl

View File

@ -106,19 +106,18 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
}); });
} }
if !variant.flatten {
registry_types.push(quote! { registry_types.push(quote! {
<#p as #crate_name::Type>::create_type_info(registry); <#p as #crate_name::Type>::create_type_info(registry);
}); });
if !variant.flatten {
possible_types.push(quote! { possible_types.push(quote! {
possible_types.insert(<#p as #crate_name::Type>::type_name().into_owned()); possible_types.insert(<#p as #crate_name::Type>::type_name().into_owned());
}); });
} else { } else {
possible_types.push(quote! { possible_types.push(quote! {
if let Some(#crate_name::registry::MetaType::Union { possible_types: possible_types2, .. }) = if let #crate_name::registry::MetaType::Union { possible_types: possible_types2, .. } =
registry.types.get(&*<#p as #crate_name::Type>::type_name()) { registry.create_dummy_type::<#p>() {
possible_types.extend(::std::clone::Clone::clone(possible_types2)); possible_types.extend(possible_types2);
} }
}); });
} }
@ -141,6 +140,14 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
} }
} }
if possible_types.is_empty() {
return Err(Error::new_spanned(
&ident,
"A GraphQL Union type must include one or more unique member types.",
)
.into());
}
let expanded = quote! { let expanded = quote! {
#(#type_into_impls)* #(#type_into_impls)*

View File

@ -142,8 +142,13 @@ async fn upload() -> Result<()> {
async_std::task::spawn(async move { async_std::task::spawn(async move {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
#[derive(Clone, SimpleObject)] #[derive(Clone, SimpleObject)]
pub struct FileInfo { pub struct FileInfo {

View File

@ -17,7 +17,12 @@ use warp::{Filter, Rejection, Reply};
/// struct QueryRoot; /// struct QueryRoot;
/// ///
/// #[Object] /// #[Object]
/// impl QueryRoot {} /// impl QueryRoot {
/// async fn value(&self) -> i32 {
/// // An GraphQL Object type must define one or more fields.
/// 100
/// }
/// }
/// ///
/// struct SubscriptionRoot; /// struct SubscriptionRoot;
/// ///

View File

@ -254,6 +254,7 @@ pub struct MetaDirective {
pub args: IndexMap<&'static str, MetaInputValue>, pub args: IndexMap<&'static str, MetaInputValue>,
} }
#[derive(Default)]
pub struct Registry { pub struct Registry {
pub types: IndexMap<String, MetaType>, pub types: IndexMap<String, MetaType>,
pub directives: HashMap<String, MetaDirective>, pub directives: HashMap<String, MetaDirective>,
@ -288,6 +289,18 @@ impl Registry {
T::qualified_type_name() T::qualified_type_name()
} }
pub fn create_dummy_type<T: crate::Type>(&mut self) -> MetaType {
let mut dummy_registry = Registry::default();
T::create_type_info(&mut dummy_registry);
if let Some(ty) = dummy_registry.types.remove(&*T::type_name()) {
self.types.extend(dummy_registry.types);
self.implements.extend(dummy_registry.implements);
ty
} else {
unreachable!()
}
}
pub fn add_directive(&mut self, directive: MetaDirective) { pub fn add_directive(&mut self, directive: MetaDirective) {
self.directives self.directives
.insert(directive.name.to_string(), directive); .insert(directive.name.to_string(), directive);

View File

@ -55,6 +55,10 @@ impl<T: ContainerType + Send + Sync> ContainerType for &T {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
T::resolve_field(*self, ctx).await T::resolve_field(*self, ctx).await
} }
async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
T::find_entity(*self, ctx, params).await
}
} }
/// Resolve an container by executing each of the fields concurrently. /// Resolve an container by executing each of the fields concurrently.

View File

@ -70,9 +70,15 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// ```rust /// ```rust
/// use async_graphql::*; /// use async_graphql::*;
/// ///
/// #[derive(SimpleObject)]
/// struct Query; /// struct Query;
/// ///
/// #[Object]
/// impl Query {
/// async fn value(&self) -> i32 {
/// 100
/// }
/// }
///
/// let schema = Schema::build(Query, EmptyMutation,EmptySubscription) /// let schema = Schema::build(Query, EmptyMutation,EmptySubscription)
/// .extension(extensions::Logger) /// .extension(extensions::Logger)
/// .finish(); /// .finish();

View File

@ -52,11 +52,10 @@ where
fn create_type_info(registry: &mut registry::Registry) -> String { fn create_type_info(registry: &mut registry::Registry) -> String {
registry.create_type::<Self, _>(|registry| { registry.create_type::<Self, _>(|registry| {
E::create_type_info(registry); let additional_fields = if let registry::MetaType::Object { fields, .. } =
let additional_fields = if let Some(registry::MetaType::Object { fields, .. }) = registry.create_dummy_type::<E>()
registry.types.get(E::type_name().as_ref())
{ {
fields.clone() fields
} else { } else {
unreachable!() unreachable!()
}; };

View File

@ -16,7 +16,7 @@ pub use page_info::PageInfo;
/// Empty additional fields /// Empty additional fields
#[derive(SimpleObject)] #[derive(SimpleObject)]
#[graphql(internal)] #[graphql(internal, dummy)]
pub struct EmptyFields; pub struct EmptyFields;
/// Parses the parameters and executes the query. /// Parses the parameters and executes the query.

View File

@ -19,7 +19,12 @@ use crate::{
/// struct QueryRoot; /// struct QueryRoot;
/// ///
/// #[Object] /// #[Object]
/// impl QueryRoot {} /// impl QueryRoot {
/// async fn value(&self) -> i32 {
/// // An GraphQL Object type must define one or more fields.
/// 100
/// }
/// }
/// ///
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); /// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
/// ``` /// ```

View File

@ -9,6 +9,7 @@ use crate::{
CacheControl, ContainerType, Context, ContextSelectionSet, ObjectType, OutputValueType, CacheControl, ContainerType, Context, ContextSelectionSet, ObjectType, OutputValueType,
Positioned, ServerResult, SimpleObject, Type, Value, Positioned, ServerResult, SimpleObject, Type, Value,
}; };
use async_graphql_value::ConstValue;
#[doc(hidden)] #[doc(hidden)]
pub struct MergedObject<A, B>(pub A, pub B); pub struct MergedObject<A, B>(pub A, pub B);
@ -23,25 +24,23 @@ impl<A: Type, B: Type> Type for MergedObject<A, B> {
let mut fields = IndexMap::new(); let mut fields = IndexMap::new();
let mut cc = CacheControl::default(); let mut cc = CacheControl::default();
A::create_type_info(registry); if let MetaType::Object {
if let Some(MetaType::Object {
fields: a_fields, fields: a_fields,
cache_control: a_cc, cache_control: a_cc,
.. ..
}) = registry.types.get(&*A::type_name()) } = registry.create_dummy_type::<A>()
{ {
fields.extend(a_fields.clone()); fields.extend(a_fields);
cc = cc.merge(&a_cc); cc = cc.merge(&a_cc);
} }
B::create_type_info(registry); if let MetaType::Object {
if let Some(MetaType::Object {
fields: b_fields, fields: b_fields,
cache_control: b_cc, cache_control: b_cc,
.. ..
}) = registry.types.get(&*B::type_name()) } = registry.create_dummy_type::<B>()
{ {
fields.extend(b_fields.clone()); fields.extend(b_fields);
cc = cc.merge(&b_cc); cc = cc.merge(&b_cc);
} }
@ -70,6 +69,18 @@ where
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
async fn find_entity(
&self,
ctx: &Context<'_>,
params: &ConstValue,
) -> ServerResult<Option<ConstValue>> {
match self.0.find_entity(ctx, params).await {
Ok(Some(value)) => Ok(Some(value)),
Ok(None) => self.1.find_entity(ctx, params).await,
Err(err) => Err(err),
}
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -96,5 +107,5 @@ where
#[doc(hidden)] #[doc(hidden)]
#[derive(SimpleObject, Default)] #[derive(SimpleObject, Default)]
#[graphql(internal)] #[graphql(internal, dummy)]
pub struct MergedObjectTail; pub struct MergedObjectTail;

View File

@ -185,7 +185,11 @@ pub async fn test_merged_subscription() {
struct Query; struct Query;
#[Object] #[Object]
impl Query {} impl Query {
async fn value(&self) -> i32 {
10
}
}
let schema = Schema::new(Query, EmptyMutation, Subscription::default()); let schema = Schema::new(Query, EmptyMutation, Subscription::default());
@ -221,3 +225,68 @@ pub async fn test_merged_subscription() {
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
} }
} }
#[async_std::test]
pub async fn test_merged_entity() {
#[derive(SimpleObject)]
struct Fruit {
id: ID,
name: String,
}
#[derive(SimpleObject)]
struct Vegetable {
id: ID,
name: String,
}
#[derive(Default)]
struct FruitQuery;
#[Object]
impl FruitQuery {
#[graphql(entity)]
async fn get_fruit(&self, id: ID) -> Fruit {
Fruit {
id,
name: "Apple".into(),
}
}
}
#[derive(Default)]
struct VegetableQuery;
#[Object]
impl VegetableQuery {
#[graphql(entity)]
async fn get_vegetable(&self, id: ID) -> Vegetable {
Vegetable {
id,
name: "Carrot".into(),
}
}
}
#[derive(MergedObject, Default)]
struct Query(FruitQuery, VegetableQuery);
let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription);
let query = r#"{
_entities(representations: [{__typename: "Fruit", id: "1"}]) {
__typename
... on Fruit {
id
name
}
}
}"#;
assert_eq!(
schema.execute(query).await.into_result().unwrap().data,
value!({
"_entities": [
{"__typename": "Fruit", "id": "1", "name": "Apple"},
]
})
);
}

View File

@ -7,9 +7,15 @@ use std::time::Duration;
pub async fn test_mutation_execution_order() { pub async fn test_mutation_execution_order() {
type List = Arc<Mutex<Vec<i32>>>; type List = Arc<Mutex<Vec<i32>>>;
#[derive(SimpleObject)]
struct QueryRoot; struct QueryRoot;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct MutationRoot; struct MutationRoot;
#[Object] #[Object]
@ -38,9 +44,15 @@ pub async fn test_mutation_execution_order() {
#[async_std::test] #[async_std::test]
pub async fn test_mutation_fragment() { pub async fn test_mutation_fragment() {
#[derive(SimpleObject)]
struct QueryRoot; struct QueryRoot;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct MutationRoot; struct MutationRoot;
#[Object] #[Object]

View File

@ -105,9 +105,15 @@ pub async fn test_input_object() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription() { pub async fn test_subscription() {
#[derive(SimpleObject)]
struct Query; struct Query;
#[Object]
impl Query {
async fn value(&self) -> i32 {
10
}
}
struct Subscription; struct Subscription;
#[Subscription(rename_fields = "SCREAMING_SNAKE_CASE", rename_args = "lowercase")] #[Subscription(rename_fields = "SCREAMING_SNAKE_CASE", rename_args = "lowercase")]

View File

@ -2,10 +2,17 @@ use async_graphql::*;
#[async_std::test] #[async_std::test]
pub async fn test_schema_default() { pub async fn test_schema_default() {
#[derive(SimpleObject, Default)] #[derive(Default)]
struct Query; struct QueryRoot;
type MySchema = Schema<Query, EmptyMutation, EmptySubscription>; #[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
let _schema = MySchema::default(); let _schema = MySchema::default();
} }

View File

@ -1,19 +1,23 @@
use async_graphql::*; use async_graphql::*;
use futures_util::stream::{Stream, StreamExt, TryStreamExt}; use futures_util::stream::{Stream, StreamExt, TryStreamExt};
#[async_std::test]
pub async fn test_subscription() {
struct QueryRoot; struct QueryRoot;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
#[async_std::test]
pub async fn test_subscription() {
#[derive(SimpleObject)] #[derive(SimpleObject)]
struct Event { struct Event {
a: i32, a: i32,
b: i32, b: i32,
} }
#[Object]
impl QueryRoot {}
struct SubscriptionRoot; struct SubscriptionRoot;
#[Subscription] #[Subscription]
@ -60,7 +64,11 @@ pub async fn test_subscription_with_ctx_data() {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct MyObject; struct MyObject;
@ -106,7 +114,11 @@ pub async fn test_subscription_with_token() {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot; struct SubscriptionRoot;
@ -150,16 +162,20 @@ pub async fn test_subscription_with_token() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription_inline_fragment() { pub async fn test_subscription_inline_fragment() {
struct QueryRoot;
#[derive(SimpleObject)] #[derive(SimpleObject)]
struct Event { struct Event {
a: i32, a: i32,
b: i32, b: i32,
} }
struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot; struct SubscriptionRoot;
@ -197,8 +213,6 @@ pub async fn test_subscription_inline_fragment() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription_fragment() { pub async fn test_subscription_fragment() {
struct QueryRoot;
#[derive(SimpleObject)] #[derive(SimpleObject)]
struct Event { struct Event {
a: i32, a: i32,
@ -211,9 +225,6 @@ pub async fn test_subscription_fragment() {
Event(Event), Event(Event),
} }
#[Object]
impl QueryRoot {}
struct SubscriptionRoot; struct SubscriptionRoot;
#[Subscription] #[Subscription]
@ -252,8 +263,6 @@ pub async fn test_subscription_fragment() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription_fragment2() { pub async fn test_subscription_fragment2() {
struct QueryRoot;
#[derive(SimpleObject)] #[derive(SimpleObject)]
struct Event { struct Event {
a: i32, a: i32,
@ -266,9 +275,6 @@ pub async fn test_subscription_fragment2() {
Event(Event), Event(Event),
} }
#[Object]
impl QueryRoot {}
struct SubscriptionRoot; struct SubscriptionRoot;
#[Subscription] #[Subscription]
@ -308,8 +314,6 @@ pub async fn test_subscription_fragment2() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription_error() { pub async fn test_subscription_error() {
struct QueryRoot;
struct Event { struct Event {
value: i32, value: i32,
} }
@ -325,9 +329,6 @@ pub async fn test_subscription_error() {
} }
} }
#[Object]
impl QueryRoot {}
struct SubscriptionRoot; struct SubscriptionRoot;
#[Subscription] #[Subscription]
@ -370,11 +371,6 @@ pub async fn test_subscription_error() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription_fieldresult() { pub async fn test_subscription_fieldresult() {
struct QueryRoot;
#[Object]
impl QueryRoot {}
struct SubscriptionRoot; struct SubscriptionRoot;
#[Subscription] #[Subscription]

View File

@ -6,7 +6,11 @@ pub async fn test_subscription_ws_transport() {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot; struct SubscriptionRoot;
@ -77,7 +81,11 @@ pub async fn test_subscription_ws_transport_with_token() {
struct QueryRoot; struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot; struct SubscriptionRoot;
@ -161,8 +169,6 @@ pub async fn test_subscription_ws_transport_with_token() {
#[async_std::test] #[async_std::test]
pub async fn test_subscription_ws_transport_error() { pub async fn test_subscription_ws_transport_error() {
struct QueryRoot;
struct Event { struct Event {
value: i32, value: i32,
} }
@ -178,8 +184,14 @@ pub async fn test_subscription_ws_transport_error() {
} }
} }
struct QueryRoot;
#[Object] #[Object]
impl QueryRoot {} impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot; struct SubscriptionRoot;

View File

@ -327,6 +327,10 @@ pub async fn test_union_flatten() {
async fn value2(&self) -> MyUnion { async fn value2(&self) -> MyUnion {
InnerUnion2::B(MyObj2 { value2: 88 }).into() InnerUnion2::B(MyObj2 { value2: 88 }).into()
} }
async fn value3(&self) -> InnerUnion1 {
InnerUnion1::A(MyObj1 { value1: 77 })
}
} }
let schema = Schema::new(Query, EmptyMutation, EmptySubscription); let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
@ -342,6 +346,11 @@ pub async fn test_union_flatten() {
value2 value2
} }
} }
value3 {
... on MyObj1 {
value1
}
}
}"#; }"#;
assert_eq!( assert_eq!(
schema.execute(query).await.into_result().unwrap().data, schema.execute(query).await.into_result().unwrap().data,
@ -351,6 +360,9 @@ pub async fn test_union_flatten() {
}, },
"value2": { "value2": {
"value2": 88, "value2": 88,
},
"value3": {
"value1": 77,
} }
}) })
); );