Add visible attributes on types, fields, and parameters, allowing some content to be hidden based on conditions.

This commit is contained in:
Sunli 2020-12-11 16:03:28 +08:00
parent 5c39d0197d
commit ba23761cb4
28 changed files with 489 additions and 22 deletions

View File

@ -2,7 +2,7 @@ use darling::ast::{Data, Fields};
use darling::util::Ignored;
use darling::{FromDeriveInput, FromField, FromMeta, FromVariant};
use inflector::Inflector;
use syn::{Attribute, Generics, Ident, Lit, LitStr, Meta, Type, Visibility};
use syn::{Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, Type, Visibility};
#[derive(FromMeta)]
#[darling(default)]
@ -44,6 +44,24 @@ impl FromMeta for DefaultValue {
}
}
#[derive(Debug)]
pub enum Visible {
None,
HiddenAlways,
FnName(String),
}
impl FromMeta for Visible {
fn from_value(value: &Lit) -> darling::Result<Self> {
match value {
Lit::Bool(LitBool { value: true, .. }) => Ok(Visible::None),
Lit::Bool(LitBool { value: false, .. }) => Ok(Visible::HiddenAlways),
Lit::Str(str) => Ok(Visible::FnName(str.value())),
_ => Err(darling::Error::unexpected_lit_type(value)),
}
}
}
#[derive(FromField)]
#[darling(attributes(graphql), forward_attrs(doc))]
pub struct SimpleObjectField {
@ -70,6 +88,8 @@ pub struct SimpleObjectField {
pub requires: Option<String>,
#[darling(default)]
pub guard: Option<Meta>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromDeriveInput)]
@ -94,6 +114,8 @@ pub struct SimpleObject {
pub cache_control: CacheControl,
#[darling(default)]
pub extends: bool,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]
@ -105,6 +127,7 @@ pub struct Argument {
pub default_with: Option<LitStr>,
pub validator: Option<Meta>,
pub key: bool, // for entity
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]
@ -117,6 +140,7 @@ pub struct Object {
pub cache_control: CacheControl,
pub extends: bool,
pub use_type_description: bool,
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]
@ -131,6 +155,7 @@ pub struct ObjectField {
pub provides: Option<String>,
pub requires: Option<String>,
pub guard: Option<Meta>,
pub visible: Option<Visible>,
}
#[derive(FromDeriveInput)]
@ -149,6 +174,8 @@ pub struct Enum {
pub rename_items: Option<RenameRule>,
#[darling(default)]
pub remote: Option<String>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromVariant)]
@ -162,6 +189,8 @@ pub struct EnumItem {
pub name: Option<String>,
#[darling(default)]
pub deprecation: Option<String>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromDeriveInput)]
@ -176,6 +205,8 @@ pub struct Union {
pub internal: bool,
#[darling(default)]
pub name: Option<String>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromVariant)]
@ -206,6 +237,8 @@ pub struct InputObjectField {
pub validator: Option<Meta>,
#[darling(default)]
pub flatten: bool,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromDeriveInput)]
@ -222,6 +255,8 @@ pub struct InputObject {
pub name: Option<String>,
#[darling(default)]
pub rename_fields: Option<RenameRule>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromMeta)]
@ -235,6 +270,8 @@ pub struct InterfaceFieldArgument {
pub default: Option<DefaultValue>,
#[darling(default)]
pub default_with: Option<LitStr>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromMeta)]
@ -256,6 +293,8 @@ pub struct InterfaceField {
pub provides: Option<String>,
#[darling(default)]
pub requires: Option<String>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromVariant)]
@ -284,6 +323,8 @@ pub struct Interface {
pub fields: Vec<InterfaceField>,
#[darling(default)]
pub extends: bool,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]
@ -292,6 +333,7 @@ pub struct Scalar {
pub internal: bool,
pub name: Option<String>,
pub use_type_description: bool,
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]
@ -312,6 +354,7 @@ pub struct SubscriptionFieldArgument {
pub default: Option<DefaultValue>,
pub default_with: Option<LitStr>,
pub validator: Option<Meta>,
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]
@ -321,6 +364,7 @@ pub struct SubscriptionField {
pub name: Option<String>,
pub deprecation: Option<String>,
pub guard: Option<Meta>,
pub visible: Option<Visible>,
}
#[derive(FromField)]
@ -345,6 +389,8 @@ pub struct MergedObject {
pub cache_control: CacheControl,
#[darling(default)]
pub extends: bool,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromField)]
@ -365,6 +411,8 @@ pub struct MergedSubscription {
pub internal: bool,
#[darling(default)]
pub name: Option<String>,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(Debug, Copy, Clone, FromMeta)]

View File

@ -5,7 +5,7 @@ use syn::ext::IdentExt;
use syn::Error;
use crate::args::{self, RenameRuleExt, RenameTarget};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(enum_args.internal);
@ -62,11 +62,14 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
value: #ident::#item_ident,
}
});
let visible = visible_fn(&variant.visible);
schema_enum_items.push(quote! {
enum_items.insert(#gql_item_name, #crate_name::registry::MetaEnumValue {
name: #gql_item_name,
description: #item_desc,
deprecation: #item_deprecation,
visible: #visible,
});
});
}
@ -119,6 +122,7 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
.into());
}
let visible = visible_fn(&enum_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::resolver_utils::EnumType for #ident {
@ -143,6 +147,7 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
#(#schema_enum_items)*
enum_items
},
visible: #visible,
}
})
}

View File

@ -6,7 +6,7 @@ use syn::Error;
use crate::args::{self, RenameRuleExt, RenameTarget};
use crate::utils::{
generate_default, generate_validator, get_crate_name, get_rustdoc, GeneratorResult,
generate_default, generate_validator, get_crate_name, get_rustdoc, visible_fn, GeneratorResult,
};
pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream> {
@ -140,6 +140,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
});
fields.push(ident);
let visible = visible_fn(&field.visible);
schema_fields.push(quote! {
fields.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue {
name: #name,
@ -147,6 +148,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
ty: <#ty as #crate_name::Type>::create_type_info(registry),
default_value: #schema_default,
validator: #validator,
visible: #visible,
});
})
}
@ -159,6 +161,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
.into());
}
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #ident {
@ -174,7 +177,8 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
let mut fields = #crate_name::indexmap::IndexMap::new();
#(#schema_fields)*
fields
}
},
visible: #visible,
})
}
}

View File

@ -8,7 +8,7 @@ use syn::{visit_mut, Error, Lifetime, Type};
use crate::args::{self, InterfaceField, InterfaceFieldArgument, RenameRuleExt, RenameTarget};
use crate::output_type::OutputType;
use crate::utils::{generate_default, get_crate_name, get_rustdoc, GeneratorResult};
use crate::utils::{generate_default, get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(interface_args.internal);
@ -139,6 +139,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
external,
provides,
requires,
visible,
} in &interface_args.fields
{
let (name, method_name) = if let Some(method) = method {
@ -179,6 +180,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
ty,
default,
default_with,
visible,
} in args
{
let ident = Ident::new(name, Span::call_site());
@ -215,6 +217,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
}
})
.unwrap_or_else(|| quote! {::std::option::Option::None});
let visible = visible_fn(&visible);
schema_args.push(quote! {
args.insert(#name, #crate_name::registry::MetaInputValue {
name: #name,
@ -222,6 +225,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
ty: <#ty as #crate_name::Type>::create_type_info(registry),
default_value: #schema_default,
validator: ::std::option::Option::None,
visible: #visible,
});
});
}
@ -257,6 +261,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
}
});
let visible = visible_fn(&visible);
schema_fields.push(quote! {
fields.insert(::std::string::ToString::to_string(#name), #crate_name::registry::MetaField {
name: ::std::string::ToString::to_string(#name),
@ -272,6 +277,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
external: #external,
provides: #provides,
requires: #requires,
visible: #visible,
});
});
@ -300,6 +306,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
}
};
let visible = visible_fn(&interface_args.visible);
let expanded = quote! {
#(#type_into_impls)*
@ -337,6 +344,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
},
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
}
})
}

View File

@ -5,7 +5,7 @@ use quote::quote;
use syn::{Error, LitInt};
use crate::args::{self, RenameTarget};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
@ -55,6 +55,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
obj
};
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #generics #crate_name::Type for #ident #generics #where_clause {
@ -83,6 +84,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
cache_control,
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
}
})
}

View File

@ -5,7 +5,7 @@ use quote::quote;
use syn::{Error, LitInt};
use crate::args::{self, RenameTarget};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
@ -44,6 +44,7 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
|obj, ty| quote!(#crate_name::MergedObject::<#ty, #obj>),
);
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #ident {
@ -69,6 +70,7 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
cache_control: ::std::default::Default::default(),
extends: false,
keys: ::std::option::Option::None,
visible: #visible,
}
})
}

View File

@ -8,7 +8,7 @@ use crate::output_type::OutputType;
use crate::utils::{
generate_default, generate_guards, generate_validator, get_cfg_attrs, get_crate_name,
get_param_getter_ident, get_rustdoc, get_type_path_and_name, parse_graphql_attrs,
remove_graphql_attrs, GeneratorResult,
remove_graphql_attrs, visible_fn, GeneratorResult,
};
pub fn generate(
@ -319,6 +319,7 @@ pub fn generate(
default,
default_with,
validator,
visible,
..
},
) in args
@ -352,6 +353,7 @@ pub fn generate(
None => quote!(::std::option::Option::None),
};
let visible = visible_fn(&visible);
schema_args.push(quote! {
args.insert(#name, #crate_name::registry::MetaInputValue {
name: #name,
@ -359,6 +361,7 @@ pub fn generate(
ty: <#ty as #crate_name::Type>::create_type_info(registry),
default_value: #schema_default,
validator: #validator,
visible: #visible,
});
});
@ -381,6 +384,7 @@ pub fn generate(
}
let schema_ty = ty.value_type();
let visible = visible_fn(&method_args.visible);
schema_fields.push(quote! {
#(#cfg_attrs)*
@ -398,6 +402,7 @@ pub fn generate(
external: #external,
provides: #provides,
requires: #requires,
visible: #visible,
});
});
@ -473,6 +478,7 @@ pub fn generate(
.into());
}
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#item_impl
@ -494,6 +500,7 @@ pub fn generate(
cache_control: #cache_control,
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
});
#(#create_entity_types)*
#(#add_keys)*

View File

@ -3,7 +3,9 @@ use quote::quote;
use syn::ItemImpl;
use crate::args::{self, RenameTarget};
use crate::utils::{get_crate_name, get_rustdoc, get_type_path_and_name, GeneratorResult};
use crate::utils::{
get_crate_name, get_rustdoc, get_type_path_and_name, visible_fn, GeneratorResult,
};
pub fn generate(
scalar_args: &args::Scalar,
@ -27,6 +29,7 @@ pub fn generate(
let self_ty = &item_impl.self_ty;
let generic = &item_impl.generics;
let where_clause = &item_impl.generics.where_clause;
let visible = visible_fn(&scalar_args.visible);
let expanded = quote! {
#item_impl
@ -41,6 +44,7 @@ pub fn generate(
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
description: #desc,
is_valid: |value| <#self_ty as #crate_name::ScalarType>::is_valid(value),
visible: #visible,
})
}
}

View File

@ -5,7 +5,7 @@ use syn::ext::IdentExt;
use syn::Error;
use crate::args::{self, RenameRuleExt, RenameTarget};
use crate::utils::{generate_guards, get_crate_name, get_rustdoc, GeneratorResult};
use crate::utils::{generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
@ -81,6 +81,8 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
}
};
let visible = visible_fn(&field.visible);
schema_fields.push(quote! {
fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField {
name: ::std::borrow::ToOwned::to_owned(#field_name),
@ -92,6 +94,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
external: #external,
provides: #provides,
requires: #requires,
visible: #visible,
});
});
@ -148,6 +151,8 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
}
};
let visible = visible_fn(&object_args.visible);
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #generics #ident #generics #where_clause {
@ -172,6 +177,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
cache_control: #cache_control,
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
})
}
}

View File

@ -11,7 +11,7 @@ use crate::output_type::OutputType;
use crate::utils::{
generate_default, generate_guards, generate_validator, get_cfg_attrs, get_crate_name,
get_param_getter_ident, get_rustdoc, get_type_path_and_name, parse_graphql_attrs,
remove_graphql_attrs, GeneratorResult,
remove_graphql_attrs, visible_fn, GeneratorResult,
};
pub fn generate(
@ -150,6 +150,7 @@ pub fn generate(
default,
default_with,
validator,
visible: arg_visible,
},
) in args
{
@ -183,6 +184,7 @@ pub fn generate(
})
.unwrap_or_else(|| quote! {::std::option::Option::None});
let visible = visible_fn(&arg_visible);
schema_args.push(quote! {
args.insert(#name, #crate_name::registry::MetaInputValue {
name: #name,
@ -190,6 +192,7 @@ pub fn generate(
ty: <#ty as #crate_name::Type>::create_type_info(registry),
default_value: #schema_default,
validator: #validator,
visible: #visible,
});
});
@ -235,6 +238,7 @@ pub fn generate(
.expect("invalid result type");
}
let visible = visible_fn(&field.visible);
schema_fields.push(quote! {
#(#cfg_attrs)*
fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField {
@ -251,6 +255,7 @@ pub fn generate(
external: false,
requires: ::std::option::Option::None,
provides: ::std::option::Option::None,
visible: #visible,
});
});
@ -388,6 +393,7 @@ pub fn generate(
cache_control: ::std::default::Default::default(),
extends: false,
keys: ::std::option::Option::None,
visible: ::std::option::Option::None,
})
}
}

View File

@ -7,7 +7,7 @@ use syn::visit_mut::VisitMut;
use syn::{visit_mut, Error, Lifetime, Type};
use crate::args::{self, RenameTarget};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(union_args.internal);
@ -148,6 +148,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
.into());
}
let visible = visible_fn(&union_args.visible);
let expanded = quote! {
#(#type_into_impls)*
@ -174,7 +175,8 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
let mut possible_types = #crate_name::indexmap::IndexSet::new();
#(#possible_types)*
possible_types
}
},
visible: #visible,
}
})
}

View File

@ -8,6 +8,7 @@ use syn::{
use thiserror::Error;
use crate::args;
use crate::args::Visible;
#[derive(Error, Debug)]
pub enum GeneratorError {
@ -389,3 +390,14 @@ pub fn get_type_path_and_name(ty: &Type) -> GeneratorResult<(&TypePath, String)>
_ => Err(Error::new_spanned(ty, "Invalid type").into()),
}
}
pub fn visible_fn(visible: &Option<Visible>) -> TokenStream {
match visible {
None | Some(Visible::None) => quote! { ::std::option::Option::None },
Some(Visible::HiddenAlways) => quote! { ::std::option::Option::Some(|_| false) },
Some(Visible::FnName(name)) => {
let ident = Ident::new(name, Span::call_site());
quote! { ::std::option::Option::Some(#ident) }
}
}
}

View File

@ -244,6 +244,8 @@ pub type FieldResult<T> = Result<T>;
/// | cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
/// | use_type_description | Specifies that the description of the type is on the type declaration. [`Description`]()(derive.Description.html) | bool | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field parameters
///
@ -258,6 +260,8 @@ pub type FieldResult<T> = Result<T>;
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field argument parameters
///
@ -270,6 +274,8 @@ pub type FieldResult<T> = Result<T>;
/// | default_with | Expression to generate default value | code string | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// | key | Is entity key | bool | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Valid field return types
///
@ -360,6 +366,8 @@ pub use async_graphql_derive::Object;
/// | 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 |
/// | cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | 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. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field parameters
///
@ -374,6 +382,8 @@ pub use async_graphql_derive::Object;
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Examples
///
@ -406,6 +416,8 @@ pub use async_graphql_derive::SimpleObject;
/// | name | Enum name | string | Y |
/// | rename_items | 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 |
/// | remote | Derive a remote enum | string | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Item parameters
///
@ -413,6 +425,8 @@ pub use async_graphql_derive::SimpleObject;
/// |-------------|---------------------------|----------|----------|
/// | name | Item name | string | Y |
/// | deprecation | Item deprecation reason | string | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Examples
///
@ -461,6 +475,8 @@ pub use async_graphql_derive::Enum;
/// |---------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | 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 |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field parameters
///
@ -472,6 +488,8 @@ pub use async_graphql_derive::Enum;
/// | default_with | Expression to generate default value | code string | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// | flatten | Similar to serde (flatten) | boolean | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Examples
///
@ -520,6 +538,8 @@ pub use async_graphql_derive::InputObject;
/// | 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 |
/// | field | Fields of this Interface | [InterfaceField] | N |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field parameters
///
@ -534,6 +554,8 @@ pub use async_graphql_derive::InputObject;
/// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y |
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field argument parameters
///
@ -545,6 +567,8 @@ pub use async_graphql_derive::InputObject;
/// | default | Use `Default::default` for default value | none | Y |
/// | default | Argument default value | literal | Y |
/// | default_with | Expression to generate default value | code string | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Define an interface
///
@ -650,6 +674,8 @@ pub use async_graphql_derive::Interface;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Item parameters
///
@ -737,6 +763,8 @@ pub use async_graphql_derive::Union;
/// | name | Field name | string | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Field argument parameters
///
@ -748,6 +776,8 @@ pub use async_graphql_derive::Union;
/// | default | Argument default value | literal | Y |
/// | default_with | Expression to generate default value | code string | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Examples
///
@ -789,6 +819,8 @@ pub use async_graphql_derive::Scalar;
/// | cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
/// | use_type_description | Specifies that the description of the type is on the type declaration. [`Description`]()(derive.Description.html) | bool | Y |
/// | visible | If `false`, it will not be displayed in introspection. | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Examples
///

View File

@ -1,5 +1,5 @@
use crate::model::{__InputValue, __Type};
use crate::{registry, Object};
use crate::{registry, Context, Object};
pub struct __Field<'a> {
pub registry: &'a registry::Registry,
@ -17,10 +17,14 @@ impl<'a> __Field<'a> {
self.field.description.map(ToString::to_string)
}
async fn args(&self) -> Vec<__InputValue<'a>> {
async fn args(&self, ctx: &Context<'_>) -> Vec<__InputValue<'a>> {
self.field
.args
.values()
.filter(|input_value| match &input_value.visible {
Some(f) => f(ctx),
None => true,
})
.map(|input_value| __InputValue {
registry: self.registry,
input_value,

View File

@ -1,5 +1,5 @@
use crate::model::{__Directive, __Type};
use crate::{registry, Object};
use crate::{registry, Context, Object};
pub struct __Schema<'a> {
pub registry: &'a registry::Registry,
@ -9,12 +9,18 @@ pub struct __Schema<'a> {
#[Object(internal, name = "__Schema")]
impl<'a> __Schema<'a> {
/// A list of all types supported by this server.
async fn types(&self) -> Vec<__Type<'a>> {
async fn types(&self, ctx: &Context<'_>) -> Vec<__Type<'a>> {
let mut types: Vec<_> = self
.registry
.types
.values()
.map(|ty| (ty.name(), __Type::new_simple(self.registry, ty)))
.filter_map(|ty| {
if ty.is_visible(ctx) {
Some((ty.name(), __Type::new_simple(self.registry, ty)))
} else {
None
}
})
.collect();
types.sort_by(|a, b| a.0.cmp(b.0));
types.into_iter().map(|(_, ty)| ty).collect()

View File

@ -1,5 +1,5 @@
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
use crate::{registry, Object};
use crate::{registry, Context, Object};
enum TypeDetail<'a> {
Named(&'a registry::MetaType),
@ -95,12 +95,17 @@ impl<'a> __Type<'a> {
async fn fields(
&self,
ctx: &Context<'_>,
#[graphql(default = false)] include_deprecated: bool,
) -> Option<Vec<__Field<'a>>> {
if let TypeDetail::Named(ty) = &self.detail {
ty.fields().map(|fields| {
fields
.values()
.filter(|field| match &field.visible {
Some(f) => f(ctx),
None => true,
})
.filter(|field| {
(include_deprecated || field.deprecation.is_none())
&& !field.name.starts_with("__")
@ -158,13 +163,18 @@ impl<'a> __Type<'a> {
async fn enum_values(
&self,
ctx: &Context<'_>,
#[graphql(default = false)] include_deprecated: bool,
) -> Option<Vec<__EnumValue<'a>>> {
if let TypeDetail::Named(registry::MetaType::Enum { enum_values, .. }) = &self.detail {
Some(
enum_values
.values()
.filter(|field| include_deprecated || field.deprecation.is_none())
.filter(|value| match &value.visible {
Some(f) => f(ctx),
None => true,
})
.filter(|value| include_deprecated || value.deprecation.is_none())
.map(|value| __EnumValue {
registry: self.registry,
value,
@ -176,13 +186,17 @@ impl<'a> __Type<'a> {
}
}
async fn input_fields(&self) -> Option<Vec<__InputValue<'a>>> {
async fn input_fields(&self, ctx: &Context<'_>) -> Option<Vec<__InputValue<'a>>> {
if let TypeDetail::Named(registry::MetaType::InputObject { input_fields, .. }) =
&self.detail
{
Some(
input_fields
.values()
.filter(|input_value| match &input_value.visible {
Some(f) => f(ctx),
None => true,
})
.map(|input_value| __InputValue {
registry: self.registry,
input_value,

View File

@ -9,7 +9,7 @@ use indexmap::set::IndexSet;
use crate::parser::types::{BaseType as ParsedBaseType, Type as ParsedType};
use crate::validators::InputValueValidator;
use crate::{model, Any, Type, Value};
use crate::{model, Any, Context, Type, Value};
pub use cache_control::CacheControl;
@ -92,6 +92,7 @@ pub struct MetaInputValue {
pub ty: String,
pub default_value: Option<String>,
pub validator: Option<Arc<dyn InputValueValidator>>,
pub visible: Option<MetaVisibleFn>,
}
#[derive(Clone)]
@ -105,6 +106,7 @@ pub struct MetaField {
pub external: bool,
pub requires: Option<&'static str>,
pub provides: Option<&'static str>,
pub visible: Option<MetaVisibleFn>,
}
#[derive(Clone)]
@ -112,13 +114,17 @@ pub struct MetaEnumValue {
pub name: &'static str,
pub description: Option<&'static str>,
pub deprecation: Option<&'static str>,
pub visible: Option<MetaVisibleFn>,
}
type MetaVisibleFn = fn(&Context<'_>) -> bool;
pub enum MetaType {
Scalar {
name: String,
description: Option<&'static str>,
is_valid: fn(value: &Value) -> bool,
visible: Option<MetaVisibleFn>,
},
Object {
name: String,
@ -127,6 +133,7 @@ pub enum MetaType {
cache_control: CacheControl,
extends: bool,
keys: Option<Vec<String>>,
visible: Option<MetaVisibleFn>,
},
Interface {
name: String,
@ -135,21 +142,25 @@ pub enum MetaType {
possible_types: IndexSet<String>,
extends: bool,
keys: Option<Vec<String>>,
visible: Option<MetaVisibleFn>,
},
Union {
name: String,
description: Option<&'static str>,
possible_types: IndexSet<String>,
visible: Option<MetaVisibleFn>,
},
Enum {
name: String,
description: Option<&'static str>,
enum_values: IndexMap<&'static str, MetaEnumValue>,
visible: Option<MetaVisibleFn>,
},
InputObject {
name: String,
description: Option<&'static str>,
input_fields: IndexMap<String, MetaInputValue>,
visible: Option<MetaVisibleFn>,
},
}
@ -166,6 +177,21 @@ impl MetaType {
}
}
pub fn is_visible(&self, ctx: &Context<'_>) -> bool {
let visible = match self {
MetaType::Scalar { visible, .. } => visible,
MetaType::Object { visible, .. } => visible,
MetaType::Interface { visible, .. } => visible,
MetaType::Union { visible, .. } => visible,
MetaType::Enum { visible, .. } => visible,
MetaType::InputObject { visible, .. } => visible,
};
match visible {
Some(f) => f(ctx),
None => true,
}
}
pub fn name(&self) -> &str {
match self {
MetaType::Scalar { name, .. } => &name,
@ -281,6 +307,7 @@ impl Registry {
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
},
);
let ty = f(self);
@ -394,6 +421,7 @@ impl Registry {
name: "_Entity".to_string(),
description: None,
possible_types,
visible: None,
},
);
}
@ -420,6 +448,7 @@ impl Registry {
external: false,
requires: None,
provides: None,
visible: None,
},
);
fields
@ -427,6 +456,7 @@ impl Registry {
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
},
);
@ -446,6 +476,7 @@ impl Registry {
external: false,
requires: None,
provides: None,
visible: None,
},
);
@ -464,6 +495,7 @@ impl Registry {
ty: "[_Any!]!".to_string(),
default_value: None,
validator: None,
visible: None,
},
);
args
@ -474,6 +506,7 @@ impl Registry {
external: false,
requires: None,
provides: None,
visible: None,
},
);
}

View File

@ -121,6 +121,7 @@ macro_rules! scalar_internal {
name: ::std::borrow::ToOwned::to_owned($name),
description: $desc,
is_valid: |value| <$ty as $crate::ScalarType>::is_valid(value),
visible: ::std::option::Option::None,
})
}
}

View File

@ -266,6 +266,7 @@ where
ty: "Boolean!".to_string(),
default_value: None,
validator: None,
visible: None,
});
args
}
@ -287,6 +288,7 @@ where
ty: "Boolean!".to_string(),
default_value: None,
validator: None,
visible: None,
});
args
}

View File

@ -159,6 +159,7 @@ where
external: false,
requires: None,
provides: None,
visible: None,
},
);
@ -176,6 +177,7 @@ where
external: false,
requires: None,
provides: None,
visible: None,
},
);
@ -185,6 +187,7 @@ where
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
}
})
}

View File

@ -78,6 +78,7 @@ where
external: false,
requires: None,
provides: None,
visible: None,
},
);
@ -93,6 +94,7 @@ where
external: false,
requires: None,
provides: None,
visible: None,
},
);
@ -102,6 +104,7 @@ where
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
}
})
}

View File

@ -44,6 +44,7 @@ impl Type for EmptyMutation {
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
})
}
}

View File

@ -24,6 +24,7 @@ impl Type for EmptySubscription {
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
})
}
}

View File

@ -84,6 +84,7 @@ impl<T> Type for OutputJson<T> {
name: Self::type_name().to_string(),
description: None,
is_valid: |_| true,
visible: None,
})
}
}

View File

@ -51,6 +51,7 @@ impl<A: Type, B: Type> Type for MergedObject<A, B> {
cache_control: cc,
extends: false,
keys: None,
visible: None,
}
})
}

View File

@ -45,6 +45,7 @@ impl<T: Type> Type for QueryRoot<T> {
external: false,
requires: None,
provides: None,
visible: None,
},
);
@ -63,6 +64,7 @@ impl<T: Type> Type for QueryRoot<T> {
ty: "String!".to_string(),
default_value: None,
validator: None,
visible: None,
},
);
args
@ -73,6 +75,7 @@ impl<T: Type> Type for QueryRoot<T> {
external: false,
requires: None,
provides: None,
visible: None,
},
);
}
@ -106,6 +109,7 @@ impl<T: ObjectType + Send + Sync> ContainerType for QueryRoot<T> {
.registry
.types
.get(&type_name)
.filter(|ty| ty.is_visible(ctx))
.map(|ty| __Type::new_simple(&ctx.schema_env.registry, ty)),
&ctx_obj,
ctx.item,

View File

@ -110,6 +110,7 @@ impl Type for Upload {
name: Self::type_name().to_string(),
description: None,
is_valid: |value| matches!(value, Value::String(_)),
visible: None,
})
}
}

View File

@ -0,0 +1,254 @@
use async_graphql::*;
use serde::Deserialize;
#[async_std::test]
pub async fn test_type_visible() {
#[derive(SimpleObject)]
#[graphql(visible = false)]
struct MyObj {
a: i32,
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> MyObj {
todo!()
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema
.execute(r#"{ __type(name: "MyObj") { name } }"#)
.await
.into_result()
.unwrap()
.data,
value!({
"__type": null,
})
);
#[derive(Deserialize)]
struct QueryResponse {
#[serde(rename = "__schema")]
schema: SchemaResponse,
}
#[derive(Deserialize)]
struct SchemaResponse {
types: Vec<TypeResponse>,
}
#[derive(Deserialize)]
struct TypeResponse {
name: String,
}
let resp: QueryResponse = from_value(
schema
.execute(r#"{ __schema { types { name } } }"#)
.await
.into_result()
.unwrap()
.data,
)
.unwrap();
assert!(resp
.schema
.types
.into_iter()
.find(|ty| ty.name == "MyObj")
.is_none());
}
#[async_std::test]
pub async fn test_field_visible() {
#[derive(SimpleObject)]
struct MyObj {
a: i32,
#[graphql(visible = false)]
b: i32,
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> MyObj {
todo!()
}
#[graphql(visible = false)]
async fn c(&self) -> i32 {
todo!()
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
#[derive(Debug, Deserialize)]
struct QueryResponse {
#[serde(rename = "__type")]
ty: TypeResponse,
}
#[derive(Debug, Deserialize)]
struct TypeResponse {
fields: Vec<FieldResposne>,
}
#[derive(Debug, Deserialize)]
struct FieldResposne {
name: String,
}
let resp: QueryResponse = from_value(
schema
.execute(r#"{ __type(name: "MyObj") { fields { name } } }"#)
.await
.into_result()
.unwrap()
.data,
)
.unwrap();
assert_eq!(
resp.ty
.fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>(),
vec!["a"]
);
let resp: QueryResponse = from_value(
schema
.execute(r#"{ __type(name: "Query") { fields { name } } }"#)
.await
.into_result()
.unwrap()
.data,
)
.unwrap();
assert_eq!(
resp.ty
.fields
.iter()
.map(|field| field.name.as_str())
.collect::<Vec<_>>(),
vec!["obj"]
);
}
#[async_std::test]
pub async fn test_enum_value_visible() {
#[derive(Enum, Eq, PartialEq, Copy, Clone)]
enum MyEnum {
A,
B,
#[graphql(visible = false)]
C,
}
struct Query;
#[Object]
impl Query {
async fn e(&self) -> MyEnum {
todo!()
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
#[derive(Debug, Deserialize)]
struct QueryResponse {
#[serde(rename = "__type")]
ty: TypeResponse,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TypeResponse {
enum_values: Vec<EnumValueResponse>,
}
#[derive(Debug, Deserialize)]
struct EnumValueResponse {
name: String,
}
let resp: QueryResponse = from_value(
schema
.execute(r#"{ __type(name: "MyEnum") { enumValues { name } } }"#)
.await
.into_result()
.unwrap()
.data,
)
.unwrap();
assert_eq!(
resp.ty
.enum_values
.iter()
.map(|value| value.name.as_str())
.collect::<Vec<_>>(),
vec!["A", "B"]
);
}
#[async_std::test]
pub async fn test_visible_fn() {
struct IsAdmin(bool);
#[derive(SimpleObject)]
#[graphql(visible = "is_admin")]
struct MyObj {
a: i32,
}
fn is_admin(ctx: &Context<'_>) -> bool {
ctx.data_unchecked::<IsAdmin>().0
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> MyObj {
todo!()
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema
.execute(Request::new(r#"{ __type(name: "MyObj") { name } }"#).data(IsAdmin(false)))
.await
.into_result()
.unwrap()
.data,
value!({
"__type": null,
})
);
assert_eq!(
schema
.execute(Request::new(r#"{ __type(name: "MyObj") { name } }"#).data(IsAdmin(true)))
.await
.into_result()
.unwrap()
.data,
value!({
"__type": {
"name": "MyObj",
},
})
);
}