Hide types that are not referenced by visible fields. #698

This commit is contained in:
Sunli 2021-11-11 15:22:40 +08:00
parent a86ca953c3
commit acb44bdd05
12 changed files with 604 additions and 104 deletions

View File

@ -333,8 +333,6 @@ pub struct UnionItem {
#[darling(default)]
pub flatten: bool,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromField)]
@ -474,6 +472,7 @@ pub struct Subscription {
pub rename_args: Option<RenameRule>,
pub use_type_description: bool,
pub extends: bool,
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]

View File

@ -433,6 +433,8 @@ pub fn generate(
.into());
}
let visible = visible_fn(&subscription_args.visible);
let expanded = quote! {
#item_impl
@ -456,7 +458,7 @@ pub fn generate(
cache_control: ::std::default::Default::default(),
extends: #extends,
keys: ::std::option::Option::None,
visible: ::std::option::Option::None,
visible: #visible,
is_subscription: true,
rust_typename: ::std::any::type_name::<Self>(),
})

View File

@ -33,13 +33,11 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
let mut registry_types = Vec::new();
let mut possible_types = Vec::new();
let mut union_values = Vec::new();
let mut get_introspection_typename = Vec::new();
let mut collect_all_fields = Vec::new();
for variant in s {
let enum_name = &variant.ident;
let union_visible = visible_fn(&variant.visible);
let ty = match variant.fields.style {
Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0],
Style::Tuple => {
@ -72,15 +70,6 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
}
enum_names.push(enum_name);
union_values.push(quote! {
union_values.insert(
<#p as #crate_name::OutputType>::type_name().into_owned(),
#crate_name::registry::MetaUnionValue {
name: <#p as #crate_name::OutputType>::type_name().into_owned(),
visible: #union_visible,
}
);
});
struct RemoveLifetime;
impl VisitMut for RemoveLifetime {
@ -203,11 +192,6 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#(#possible_types)*
possible_types
},
union_values: {
let mut union_values = #crate_name::indexmap::IndexMap::new();
#(#union_values)*
union_values
},
visible: #visible,
rust_typename: ::std::any::type_name::<Self>(),
}

View File

@ -860,8 +860,6 @@ pub use async_graphql_derive::Interface;
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|----------|----------|
/// | flatten | Similar to serde (flatten) | boolean | Y |
/// | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Define a union
///
@ -935,6 +933,8 @@ pub use async_graphql_derive::Union;
/// | rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE".| string | Y |
/// | rename_args | Rename all the arguments according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE".| string | Y |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
/// | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
/// | use_type_description | Specifies that the description of the type is on the type declaration. [`Description`]()(derive.Description.html) | bool | Y |
///
/// # Field parameters

View File

@ -1,3 +1,5 @@
use std::collections::HashSet;
use crate::model::__InputValue;
use crate::{registry, Enum, Object};
@ -66,6 +68,7 @@ pub enum __DirectiveLocation {
pub struct __Directive<'a> {
pub registry: &'a registry::Registry,
pub visible_types: &'a HashSet<&'a str>,
pub directive: &'a registry::MetaDirective,
}
@ -95,6 +98,7 @@ impl<'a> __Directive<'a> {
.values()
.map(|input_value| __InputValue {
registry: self.registry,
visible_types: self.visible_types,
input_value,
})
.collect()

View File

@ -1,8 +1,12 @@
use std::collections::HashSet;
use crate::model::{__InputValue, __Type};
use crate::registry::is_visible;
use crate::{registry, Context, Object};
pub struct __Field<'a> {
pub registry: &'a registry::Registry,
pub visible_types: &'a HashSet<&'a str>,
pub field: &'a registry::MetaField,
}
@ -23,12 +27,10 @@ impl<'a> __Field<'a> {
self.field
.args
.values()
.filter(|input_value| match &input_value.visible {
Some(f) => f(ctx),
None => true,
})
.filter(|input_value| is_visible(ctx, &input_value.visible))
.map(|input_value| __InputValue {
registry: self.registry,
visible_types: self.visible_types,
input_value,
})
.collect()
@ -36,7 +38,7 @@ impl<'a> __Field<'a> {
#[graphql(name = "type")]
async fn ty(&self) -> __Type<'a> {
__Type::new(self.registry, &self.field.ty)
__Type::new(self.registry, self.visible_types, &self.field.ty)
}
#[inline]

View File

@ -1,8 +1,11 @@
use std::collections::HashSet;
use crate::model::__Type;
use crate::{registry, Object};
pub struct __InputValue<'a> {
pub registry: &'a registry::Registry,
pub visible_types: &'a HashSet<&'a str>,
pub input_value: &'a registry::MetaInputValue,
}
@ -22,7 +25,7 @@ impl<'a> __InputValue<'a> {
#[graphql(name = "type")]
#[inline]
async fn ty(&self) -> __Type<'a> {
__Type::new(self.registry, &self.input_value.ty)
__Type::new(self.registry, self.visible_types, &self.input_value.ty)
}
#[inline]

View File

@ -1,22 +1,37 @@
use std::collections::HashSet;
use crate::model::{__Directive, __Type};
use crate::{registry, Context, Object};
use crate::{registry, Object};
pub struct __Schema<'a> {
pub registry: &'a registry::Registry,
registry: &'a registry::Registry,
visible_types: &'a HashSet<&'a str>,
}
impl<'a> __Schema<'a> {
pub fn new(registry: &'a registry::Registry, visible_types: &'a HashSet<&'a str>) -> Self {
Self {
registry,
visible_types,
}
}
}
/// A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.
#[Object(internal, name = "__Schema")]
impl<'a> __Schema<'a> {
/// A list of all types supported by this server.
async fn types(&self, ctx: &Context<'_>) -> Vec<__Type<'a>> {
async fn types(&self) -> Vec<__Type<'a>> {
let mut types: Vec<_> = self
.registry
.types
.values()
.filter_map(|ty| {
if ty.is_visible(ctx) {
Some((ty.name(), __Type::new_simple(self.registry, ty)))
if self.visible_types.contains(ty.name()) {
Some((
ty.name(),
__Type::new_simple(self.registry, self.visible_types, ty),
))
} else {
None
}
@ -31,6 +46,7 @@ impl<'a> __Schema<'a> {
async fn query_type(&self) -> __Type<'a> {
__Type::new_simple(
self.registry,
self.visible_types,
&self.registry.types[&self.registry.query_type],
)
}
@ -38,19 +54,33 @@ impl<'a> __Schema<'a> {
/// If this server supports mutation, the type that mutation operations will be rooted at.
#[inline]
async fn mutation_type(&self) -> Option<__Type<'a>> {
self.registry
.mutation_type
.as_ref()
.map(|ty| __Type::new_simple(self.registry, &self.registry.types[ty]))
self.registry.mutation_type.as_ref().and_then(|ty| {
if self.visible_types.contains(ty.as_str()) {
Some(__Type::new_simple(
self.registry,
self.visible_types,
&self.registry.types[ty],
))
} else {
None
}
})
}
/// If this server support subscription, the type that subscription operations will be rooted at.
#[inline]
async fn subscription_type(&self) -> Option<__Type<'a>> {
self.registry
.subscription_type
.as_ref()
.map(|ty| __Type::new_simple(self.registry, &self.registry.types[ty]))
self.registry.subscription_type.as_ref().and_then(|ty| {
if self.visible_types.contains(ty.as_str()) {
Some(__Type::new_simple(
self.registry,
self.visible_types,
&self.registry.types[ty],
))
} else {
None
}
})
}
/// A list of all directives supported by this server.
@ -60,7 +90,8 @@ impl<'a> __Schema<'a> {
.directives
.values()
.map(|directive| __Directive {
registry: &self.registry,
registry: self.registry,
visible_types: self.visible_types,
directive,
})
.collect();

View File

@ -1,4 +1,7 @@
use std::collections::HashSet;
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
use crate::registry::is_visible;
use crate::{registry, Context, Object};
enum TypeDetail<'a> {
@ -9,31 +12,44 @@ enum TypeDetail<'a> {
pub struct __Type<'a> {
registry: &'a registry::Registry,
visible_types: &'a HashSet<&'a str>,
detail: TypeDetail<'a>,
}
impl<'a> __Type<'a> {
#[inline]
pub fn new_simple(registry: &'a registry::Registry, ty: &'a registry::MetaType) -> __Type<'a> {
pub fn new_simple(
registry: &'a registry::Registry,
visible_types: &'a HashSet<&'a str>,
ty: &'a registry::MetaType,
) -> __Type<'a> {
__Type {
registry,
visible_types,
detail: TypeDetail::Named(ty),
}
}
#[inline]
pub fn new(registry: &'a registry::Registry, type_name: &str) -> __Type<'a> {
pub fn new(
registry: &'a registry::Registry,
visible_types: &'a HashSet<&'a str>,
type_name: &str,
) -> __Type<'a> {
match registry::MetaTypeName::create(type_name) {
registry::MetaTypeName::NonNull(ty) => __Type {
registry,
visible_types,
detail: TypeDetail::NonNull(ty.to_string()),
},
registry::MetaTypeName::List(ty) => __Type {
registry,
visible_types,
detail: TypeDetail::List(ty.to_string()),
},
registry::MetaTypeName::Named(ty) => __Type {
registry,
visible_types,
detail: TypeDetail::Named(match registry.types.get(ty) {
Some(t) => t,
None => panic!("Type '{}' not found!", ty),
@ -98,16 +114,14 @@ impl<'a> __Type<'a> {
ty.fields().map(|fields| {
fields
.values()
.filter(|field| match &field.visible {
Some(f) => f(ctx),
None => true,
})
.filter(|field| is_visible(ctx, &field.visible))
.filter(|field| {
(include_deprecated || !field.deprecation.is_deprecated())
&& !field.name.starts_with("__")
})
.map(|field| __Field {
registry: self.registry,
visible_types: self.visible_types,
field,
})
.collect()
@ -125,7 +139,8 @@ impl<'a> __Type<'a> {
.get(name)
.unwrap_or(&Default::default())
.iter()
.map(|ty| __Type::new(self.registry, ty))
.filter(|ty| self.visible_types.contains(ty.as_str()))
.map(|ty| __Type::new(self.registry, self.visible_types, ty))
.collect(),
)
} else {
@ -133,27 +148,15 @@ impl<'a> __Type<'a> {
}
}
async fn possible_types(&self, ctx: &Context<'_>) -> Option<Vec<__Type<'a>>> {
if let TypeDetail::Named(registry::MetaType::Interface { possible_types, .. }) =
&self.detail
async fn possible_types(&self) -> Option<Vec<__Type<'a>>> {
if let TypeDetail::Named(registry::MetaType::Interface { possible_types, .. })
| TypeDetail::Named(registry::MetaType::Union { possible_types, .. }) = &self.detail
{
Some(
possible_types
.iter()
.map(|ty| __Type::new(self.registry, ty))
.collect(),
)
} else if let TypeDetail::Named(registry::MetaType::Union { union_values, .. }) =
&self.detail
{
Some(
union_values
.values()
.filter(|value| match &value.visible {
Some(f) => f(ctx),
None => true,
})
.map(|ty| __Type::new(self.registry, &ty.name))
.filter(|ty| self.visible_types.contains(ty.as_str()))
.map(|ty| __Type::new(self.registry, self.visible_types, ty))
.collect(),
)
} else {
@ -170,10 +173,7 @@ impl<'a> __Type<'a> {
Some(
enum_values
.values()
.filter(|value| match &value.visible {
Some(f) => f(ctx),
None => true,
})
.filter(|value| is_visible(ctx, &value.visible))
.filter(|value| include_deprecated || !value.deprecation.is_deprecated())
.map(|value| __EnumValue {
registry: self.registry,
@ -193,12 +193,10 @@ impl<'a> __Type<'a> {
Some(
input_fields
.values()
.filter(|input_value| match &input_value.visible {
Some(f) => f(ctx),
None => true,
})
.filter(|input_value| is_visible(ctx, &input_value.visible))
.map(|input_value| __InputValue {
registry: self.registry,
visible_types: self.visible_types,
input_value,
})
.collect(),
@ -211,9 +209,9 @@ impl<'a> __Type<'a> {
#[inline]
async fn of_type(&self) -> Option<__Type<'a>> {
if let TypeDetail::List(ty) = &self.detail {
Some(__Type::new(self.registry, &ty))
Some(__Type::new(self.registry, self.visible_types, &ty))
} else if let TypeDetail::NonNull(ty) = &self.detail {
Some(__Type::new(self.registry, &ty))
Some(__Type::new(self.registry, self.visible_types, &ty))
} else {
None
}

View File

@ -177,12 +177,6 @@ pub struct MetaEnumValue {
pub visible: Option<MetaVisibleFn>,
}
#[derive(Clone)]
pub struct MetaUnionValue {
pub name: String,
pub visible: Option<MetaVisibleFn>,
}
type MetaVisibleFn = fn(&Context<'_>) -> bool;
#[derive(Clone)]
@ -218,7 +212,6 @@ pub enum MetaType {
Union {
name: String,
description: Option<&'static str>,
union_values: IndexMap<String, MetaUnionValue>,
possible_types: IndexSet<String>,
visible: Option<MetaVisibleFn>,
rust_typename: &'static str,
@ -264,10 +257,7 @@ impl MetaType {
MetaType::Enum { visible, .. } => visible,
MetaType::InputObject { visible, .. } => visible,
};
match visible {
Some(f) => f(ctx),
None => true,
}
is_visible(ctx, visible)
}
#[inline]
@ -546,7 +536,6 @@ impl Registry {
MetaType::Union {
name: "_Entity".to_string(),
description: None,
union_values: Default::default(),
possible_types,
visible: None,
rust_typename: "async_graphql::federation::Entity",
@ -797,18 +786,6 @@ impl Registry {
traverse_type(&self.types, &mut used_types, ty.name());
}
fn is_system_type(name: &str) -> bool {
if name.starts_with("__") {
return true;
}
name == "Boolean"
|| name == "Int"
|| name == "Float"
|| name == "String"
|| name == "ID"
}
for ty in self.types.values() {
let name = ty.name();
if !is_system_type(name) && !used_types.contains(name) {
@ -820,4 +797,143 @@ impl Registry {
self.types.remove(&type_name);
}
}
pub fn find_visible_types(&self, ctx: &Context<'_>) -> HashSet<&str> {
let mut visible_types = HashSet::new();
fn traverse_field<'a>(
ctx: &Context<'_>,
types: &'a BTreeMap<String, MetaType>,
visible_types: &mut HashSet<&'a str>,
field: &'a MetaField,
) {
if !is_visible(ctx, &field.visible) {
return;
}
traverse_type(
ctx,
types,
visible_types,
MetaTypeName::concrete_typename(&field.ty),
);
for arg in field.args.values() {
traverse_input_value(ctx, types, visible_types, arg);
}
}
fn traverse_input_value<'a>(
ctx: &Context<'_>,
types: &'a BTreeMap<String, MetaType>,
visible_types: &mut HashSet<&'a str>,
input_value: &'a MetaInputValue,
) {
if !is_visible(ctx, &input_value.visible) {
return;
}
traverse_type(
ctx,
types,
visible_types,
MetaTypeName::concrete_typename(&input_value.ty),
);
}
fn traverse_type<'a>(
ctx: &Context<'_>,
types: &'a BTreeMap<String, MetaType>,
visible_types: &mut HashSet<&'a str>,
type_name: &'a str,
) {
if visible_types.contains(type_name) {
return;
}
if let Some(ty) = types.get(type_name) {
if !ty.is_visible(ctx) {
return;
}
visible_types.insert(type_name);
match ty {
MetaType::Object { fields, .. } => {
for field in fields.values() {
traverse_field(ctx, types, visible_types, field);
}
}
MetaType::Interface {
fields,
possible_types,
..
} => {
for field in fields.values() {
traverse_field(ctx, types, visible_types, field);
}
for type_name in possible_types.iter() {
traverse_type(ctx, types, visible_types, type_name);
}
}
MetaType::Union { possible_types, .. } => {
for type_name in possible_types.iter() {
traverse_type(ctx, types, visible_types, type_name);
}
}
MetaType::InputObject { input_fields, .. } => {
for field in input_fields.values() {
traverse_input_value(ctx, types, visible_types, field);
}
}
_ => {}
}
}
}
for type_name in Some(&self.query_type)
.into_iter()
.chain(self.mutation_type.iter())
.chain(self.subscription_type.iter())
{
traverse_type(ctx, &self.types, &mut visible_types, type_name);
}
for ty in self.types.values().filter(|ty| match ty {
MetaType::Object {
keys: Some(keys), ..
}
| MetaType::Interface {
keys: Some(keys), ..
} => !keys.is_empty(),
_ => false,
}) {
traverse_type(ctx, &self.types, &mut visible_types, ty.name());
}
self.types
.values()
.filter_map(|ty| {
let name = ty.name();
if is_system_type(name) || visible_types.contains(name) {
Some(name)
} else {
None
}
})
.collect()
}
}
pub(crate) fn is_visible(ctx: &Context<'_>, visible: &Option<MetaVisibleFn>) -> bool {
match visible {
Some(f) => f(ctx),
None => true,
}
}
fn is_system_type(name: &str) -> bool {
if name.starts_with("__") {
return true;
}
name == "Boolean" || name == "Int" || name == "Float" || name == "String" || name == "ID"
}

View File

@ -27,10 +27,9 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection {
if ctx.item.node.name.node == "__schema" {
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
let visible_types = ctx.schema_env.registry.find_visible_types(ctx);
return OutputType::resolve(
&__Schema {
registry: &ctx.schema_env.registry,
},
&__Schema::new(&ctx.schema_env.registry, &visible_types),
&ctx_obj,
ctx.item,
)
@ -39,13 +38,14 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
} else if ctx.item.node.name.node == "__type" {
let type_name: String = ctx.param_value("name", None)?;
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
let visible_types = ctx.schema_env.registry.find_visible_types(ctx);
return OutputType::resolve(
&ctx.schema_env
.registry
.types
.get(&type_name)
.filter(|ty| ty.is_visible(ctx))
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
.filter(|_| visible_types.contains(type_name.as_str()))
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, &visible_types, ty)),
&ctx_obj,
ctx.item,
)

View File

@ -1,4 +1,5 @@
use async_graphql::*;
use futures_util::Stream;
use serde::Deserialize;
#[tokio::test]
@ -257,3 +258,363 @@ pub async fn test_visible_fn() {
})
);
}
#[tokio::test]
pub async fn test_indirect_hiding_type() {
#[derive(SimpleObject)]
struct MyObj1 {
a: i32,
b: MyObj2,
c: MyObj3,
}
#[derive(SimpleObject)]
struct MyObj2 {
a: i32,
}
#[derive(SimpleObject)]
struct MyObj3 {
a: i32,
#[graphql(visible = false)]
b: MyObj5,
}
#[derive(SimpleObject)]
#[graphql(visible = false)]
struct MyObj4 {
a: i32,
}
#[derive(SimpleObject)]
struct MyObj5 {
a: i32,
}
#[derive(InputObject)]
struct MyInputObj1 {
a: i32,
b: MyInputObj2,
c: MyInputObj3,
}
#[derive(InputObject)]
struct MyInputObj2 {
a: i32,
}
#[derive(InputObject)]
struct MyInputObj3 {
a: i32,
#[graphql(visible = false)]
b: MyInputObj4,
}
#[derive(InputObject)]
struct MyInputObj4 {
a: i32,
}
#[derive(InputObject)]
struct MyInputObj5 {
a: i32,
}
#[derive(Union)]
enum MyUnion {
MyObj3(MyObj3),
MyObj4(MyObj4),
}
#[derive(Interface)]
#[graphql(field(name = "a", type = "&i32"))]
enum MyInterface {
MyObj3(MyObj3),
MyObj4(MyObj4),
}
#[derive(Interface)]
#[graphql(visible = false, field(name = "a", type = "&i32"))]
enum MyInterface2 {
MyObj3(MyObj3),
MyObj4(MyObj4),
}
struct Query;
#[Object]
#[allow(unreachable_code)]
impl Query {
#[graphql(visible = false)]
async fn obj1(&self) -> MyObj1 {
todo!()
}
async fn obj3(&self) -> MyObj3 {
todo!()
}
#[graphql(visible = false)]
async fn input_obj1(&self, _obj: MyInputObj1) -> i32 {
todo!()
}
async fn input_obj3(&self, _obj: MyInputObj3) -> i32 {
todo!()
}
async fn input_obj5(&self, #[graphql(visible = false)] _obj: Option<MyInputObj5>) -> i32 {
todo!()
}
async fn union1(&self) -> MyUnion {
todo!()
}
async fn interface1(&self) -> MyInterface {
todo!()
}
async fn interface2(&self) -> MyInterface2 {
todo!()
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj1") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj2") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj3") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "name": "MyObj3" } })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInputObj1") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInputObj2") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInputObj3") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "name": "MyInputObj3" } })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyUnion") { possibleTypes { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "possibleTypes": [{ "name": "MyObj3" }] } })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInterface") { possibleTypes { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "possibleTypes": [{ "name": "MyObj3" }] } })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInterface2") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj3") { interfaces { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "interfaces": [{ "name": "MyInterface" }] } })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj3") { fields { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "fields": [
{ "name": "a" },
]}})
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj5") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInputObj3") { inputFields { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": { "inputFields": [
{ "name": "a" },
]}})
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInputObj4") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyInputObj5") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
}
#[tokio::test]
pub async fn root() {
struct Query;
#[Object]
#[allow(unreachable_code)]
impl Query {
async fn value(&self) -> i32 {
todo!()
}
}
struct Mutation;
#[Object(visible = false)]
#[allow(unreachable_code)]
impl Mutation {
async fn value(&self) -> i32 {
todo!()
}
}
struct Subscription;
#[Subscription(visible = false)]
#[allow(unreachable_code)]
impl Subscription {
async fn value(&self) -> impl Stream<Item = i32> {
futures_util::stream::iter(vec![1, 2, 3])
}
}
let schema = Schema::new(Query, Mutation, Subscription);
assert_eq!(
schema
.execute(r#"{ __type(name: "Mutation") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
let schema = Schema::new(Query, Mutation, Subscription);
assert_eq!(
schema
.execute(r#"{ __schema { mutationType { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__schema": { "mutationType": null } })
);
assert_eq!(
schema
.execute(r#"{ __type(name: "Subscription") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__type": null })
);
let schema = Schema::new(Query, Mutation, Subscription);
assert_eq!(
schema
.execute(r#"{ __schema { subscriptionType { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__schema": { "subscriptionType": null } })
);
let schema = Schema::new(Query, Mutation, Subscription);
assert_eq!(
schema
.execute(r#"{ __schema { queryType { name } } }"#)
.await
.into_result()
.unwrap()
.data,
value!({ "__schema": { "queryType": { "name": "Query" } } })
);
}