Hide types that are not referenced by visible fields. #698
This commit is contained in:
parent
a86ca953c3
commit
acb44bdd05
|
@ -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)]
|
||||
|
|
|
@ -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>(),
|
||||
})
|
||||
|
|
|
@ -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>(),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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" } } })
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user