Custom directive
This commit is contained in:
parent
3f458dcb31
commit
1b50738af7
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.0.6] 2021-11-19
|
||||
|
||||
- Custom directives. [Book](https://async-graphql.github.io/async-graphql/en/custom_directive.html)
|
||||
|
||||
## [3.0.5] 2021-11-19
|
||||
|
||||
- Remove skipped fields from the document before executing the query.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use darling::ast::{Data, Fields};
|
||||
use darling::util::{Ignored, SpannedValue};
|
||||
use darling::{FromDeriveInput, FromField, FromMeta, FromVariant};
|
||||
|
@ -697,3 +699,29 @@ pub struct ComplexObjectField {
|
|||
pub visible: Option<Visible>,
|
||||
pub complexity: Option<ComplexityType>,
|
||||
}
|
||||
|
||||
#[derive(FromMeta, Default)]
|
||||
#[darling(default)]
|
||||
pub struct Directive {
|
||||
pub internal: bool,
|
||||
pub name: Option<String>,
|
||||
pub visible: Option<Visible>,
|
||||
pub repeatable: bool,
|
||||
pub rename_args: Option<RenameRule>,
|
||||
#[darling(multiple, rename = "location")]
|
||||
pub locations: Vec<DirectiveLocation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromMeta)]
|
||||
#[darling(rename_all = "lowercase")]
|
||||
pub enum DirectiveLocation {
|
||||
Field,
|
||||
}
|
||||
|
||||
impl Display for DirectiveLocation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DirectiveLocation::Field => write!(f, "FIELD"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::{Error, FnArg, ItemFn, Pat};
|
||||
|
||||
use crate::args;
|
||||
use crate::args::{Argument, RenameRuleExt, RenameTarget};
|
||||
use crate::utils::{
|
||||
generate_default, get_crate_name, get_rustdoc, parse_graphql_attrs, remove_graphql_attrs,
|
||||
visible_fn, GeneratorResult,
|
||||
};
|
||||
|
||||
pub fn generate(
|
||||
directive_args: &args::Directive,
|
||||
item_fn: &mut ItemFn,
|
||||
) -> GeneratorResult<TokenStream> {
|
||||
let crate_name = get_crate_name(directive_args.internal);
|
||||
let ident = &item_fn.sig.ident;
|
||||
let vis = &item_fn.vis;
|
||||
let directive_name = directive_args
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| item_fn.sig.ident.to_string());
|
||||
let desc = get_rustdoc(&item_fn.attrs)?
|
||||
.map(|s| quote!(::std::option::Option::Some(#s)))
|
||||
.unwrap_or_else(|| quote!(::std::option::Option::None));
|
||||
let visible = visible_fn(&directive_args.visible);
|
||||
let repeatable = directive_args.repeatable;
|
||||
|
||||
let mut get_params = Vec::new();
|
||||
let mut use_params = Vec::new();
|
||||
let mut schema_args = Vec::new();
|
||||
|
||||
for arg in item_fn.sig.inputs.iter_mut() {
|
||||
let mut arg_info = None;
|
||||
|
||||
if let FnArg::Typed(pat) = arg {
|
||||
if let Pat::Ident(ident) = &*pat.pat {
|
||||
arg_info = Some((ident.clone(), pat.ty.clone(), pat.attrs.clone()));
|
||||
remove_graphql_attrs(&mut pat.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
let (arg_ident, arg_ty, arg_attrs) = match arg_info {
|
||||
Some(info) => info,
|
||||
None => {
|
||||
return Err(Error::new_spanned(arg, "Invalid argument type.").into());
|
||||
}
|
||||
};
|
||||
|
||||
let Argument {
|
||||
name,
|
||||
desc,
|
||||
default,
|
||||
default_with,
|
||||
validator,
|
||||
visible,
|
||||
secret,
|
||||
..
|
||||
} = parse_graphql_attrs::<args::Argument>(&arg_attrs)?.unwrap_or_default();
|
||||
|
||||
let name = name.clone().unwrap_or_else(|| {
|
||||
directive_args
|
||||
.rename_args
|
||||
.rename(arg_ident.ident.unraw().to_string(), RenameTarget::Argument)
|
||||
});
|
||||
let desc = desc
|
||||
.as_ref()
|
||||
.map(|s| quote! {::std::option::Option::Some(#s)})
|
||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||
let default = generate_default(&default, &default_with)?;
|
||||
let schema_default = default
|
||||
.as_ref()
|
||||
.map(|value| {
|
||||
quote! {
|
||||
::std::option::Option::Some(::std::string::ToString::to_string(
|
||||
&<#arg_ty as #crate_name::InputType>::to_value(&#value)
|
||||
))
|
||||
}
|
||||
})
|
||||
.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,
|
||||
description: #desc,
|
||||
ty: <#arg_ty as #crate_name::InputType>::create_type_info(registry),
|
||||
default_value: #schema_default,
|
||||
visible: #visible,
|
||||
is_secret: #secret,
|
||||
});
|
||||
});
|
||||
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#arg_ident),
|
||||
quote!(ty),
|
||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
|
||||
let default = match default {
|
||||
Some(default) => {
|
||||
quote! { ::std::option::Option::Some(|| -> #arg_ty { #default }) }
|
||||
}
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
get_params.push(quote! {
|
||||
let (__pos, #arg_ident) = ctx.param_value::<#arg_ty>(#name, #default)?;
|
||||
#validators
|
||||
});
|
||||
|
||||
use_params.push(quote! { #arg_ident });
|
||||
}
|
||||
|
||||
let locations = directive_args
|
||||
.locations
|
||||
.iter()
|
||||
.map(|loc| {
|
||||
let loc = quote::format_ident!("{}", loc.to_string());
|
||||
quote!(#crate_name::registry::__DirectiveLocation::#loc)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if locations.is_empty() {
|
||||
return Err(Error::new(
|
||||
ident.span(),
|
||||
"At least one location is required for the directive.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#[allow(non_camel_case_types)]
|
||||
#vis struct #ident;
|
||||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #crate_name::CustomDirectiveFactory for #ident {
|
||||
fn name(&self) -> &'static str {
|
||||
#directive_name
|
||||
}
|
||||
|
||||
fn register(&self, registry: &mut #crate_name::registry::Registry) {
|
||||
let meta = #crate_name::registry::MetaDirective {
|
||||
name: #directive_name,
|
||||
description: #desc,
|
||||
locations: vec![#(#locations),*],
|
||||
args: {
|
||||
#[allow(unused_mut)]
|
||||
let mut args = #crate_name::indexmap::IndexMap::new();
|
||||
#(#schema_args)*
|
||||
args
|
||||
},
|
||||
is_repeatable: #repeatable,
|
||||
visible: #visible,
|
||||
};
|
||||
registry.add_directive(meta);
|
||||
}
|
||||
|
||||
fn create(
|
||||
&self,
|
||||
ctx: &#crate_name::ContextDirective<'_>,
|
||||
directive: &#crate_name::parser::types::Directive,
|
||||
) -> #crate_name::ServerResult<::std::boxed::Box<dyn #crate_name::CustomDirective>> {
|
||||
#item_fn
|
||||
|
||||
#(#get_params)*
|
||||
Ok(::std::boxed::Box::new(#ident(#(#use_params),*)))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expanded.into())
|
||||
}
|
|
@ -7,6 +7,7 @@ extern crate proc_macro;
|
|||
mod args;
|
||||
mod complex_object;
|
||||
mod description;
|
||||
mod directive;
|
||||
mod r#enum;
|
||||
mod input_object;
|
||||
mod interface;
|
||||
|
@ -25,7 +26,7 @@ mod validators;
|
|||
use darling::{FromDeriveInput, FromMeta};
|
||||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
use syn::{AttributeArgs, DeriveInput, ItemImpl};
|
||||
use syn::{AttributeArgs, DeriveInput, ItemFn, ItemImpl};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -201,3 +202,18 @@ pub fn derive_newtype(input: TokenStream) -> TokenStream {
|
|||
Err(err) => err.write_errors().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Directive(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let directive_args =
|
||||
match args::Directive::from_list(&parse_macro_input!(args as AttributeArgs)) {
|
||||
Ok(directive_args) => directive_args,
|
||||
Err(err) => return TokenStream::from(err.write_errors()),
|
||||
};
|
||||
let mut item_fn = parse_macro_input!(input as ItemFn);
|
||||
match directive::generate(&directive_args, &mut item_fn) {
|
||||
Ok(expanded) => expanded,
|
||||
Err(err) => err.write_errors().into(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,6 +160,10 @@ impl Validators {
|
|||
});
|
||||
}
|
||||
|
||||
if codes.is_empty() {
|
||||
return Ok(quote!());
|
||||
}
|
||||
|
||||
let codes = codes.into_iter().map(|s| quote!(#s #map_err ?));
|
||||
|
||||
if self.list {
|
||||
|
|
|
@ -37,4 +37,5 @@
|
|||
- [Advanced topics](advanced_topics.md)
|
||||
- [Custom scalars](custom_scalars.md)
|
||||
- [Optimizing N+1 queries](dataloader.md)
|
||||
- [Custom directive](custom_directive.md)
|
||||
- [Apollo Federation](apollo_federation.md)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# Custom directive
|
||||
|
||||
`Async-graphql` can easily customize directives, which can extend the behavior of GraphQL.
|
||||
|
||||
To create a custom directive, you need to implement the `CustomDirective` trait, and then use the `Directive` macro to
|
||||
generate a factory function that receives the parameters of the directive and returns an instance of the directive.
|
||||
|
||||
Currently `Async-graphql` only supports directive located at `FIELD`.
|
||||
|
||||
```rust
|
||||
struct ConcatDirective {
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CustomDirective for ConcatDirective {
|
||||
async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
|
||||
resolve.await.map(|value| {
|
||||
value.map(|value| match value {
|
||||
Value::String(str) => Value::String(str + &self.value),
|
||||
_ => value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[Directive(location = "field")]
|
||||
fn concat(value: String) -> impl CustomDirective {
|
||||
ConcatDirective { value }
|
||||
}
|
||||
```
|
||||
|
||||
Register the directive when building the schema:
|
||||
|
||||
```rust
|
||||
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||
.directive(concat)
|
||||
.finish();
|
||||
```
|
|
@ -36,5 +36,5 @@
|
|||
- [高级主题](advanced_topics.md)
|
||||
- [自定义标量](custom_scalars.md)
|
||||
- [优化查询(解决N+1问题)](dataloader.md)
|
||||
- [自定义扩展](custom_extensions.md)
|
||||
- [自定义指令](custom_directive.md)
|
||||
- [Apollo Federation集成](apollo_federation.md)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 自定义指令
|
||||
|
||||
`Async-graphql`可以很方便的自定义指令,这可以扩展GraphQL的行为。
|
||||
|
||||
创建一个自定义指令,需要实现 `CustomDirective` trait,然后用`Directive`宏生成一个工厂函数,该函数接收指令的参数并返回指令的实例。
|
||||
|
||||
目前`Async-graphql`仅支持添加`FIELD`位置的指令。
|
||||
|
||||
```rust
|
||||
struct ConcatDirective {
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CustomDirective for ConcatDirective {
|
||||
async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
|
||||
resolve.await.map(|value| {
|
||||
value.map(|value| match value {
|
||||
Value::String(str) => Value::String(str + &self.value),
|
||||
_ => value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[Directive(location = "field")]
|
||||
fn concat(value: String) -> impl CustomDirective {
|
||||
ConcatDirective { value }
|
||||
}
|
||||
```
|
||||
|
||||
创建模式时注册指令:
|
||||
|
||||
```rust
|
||||
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||
.directive(concat)
|
||||
.finish();
|
||||
```
|
|
@ -1,7 +0,0 @@
|
|||
# 自定义扩展
|
||||
|
||||
一个GraphQL扩展对象能够接收一个查询执行各个阶段的事件,你可以收集想要的数据,这些数据能够在查询结果中返回。
|
||||
|
||||
只需要实现`async_graphql::Extension`就能够定义一个扩展对象,然后在创建`Schema`的时候调用`Schema::extension`应用扩展。
|
||||
|
||||
你可以参考[Apollo tracing](https://github.com/async-graphql/async-graphql/blob/master/src/extensions/tracing.rs)来实现自己的扩展类型。
|
|
@ -17,6 +17,7 @@ Comparing Features of Other Rust GraphQL Implementations
|
|||
| Dataloading | 👍 | 👍 |
|
||||
| Custom Scalar | 👍 | 👍 |
|
||||
| Custom Error | 👍 | 👍 |
|
||||
| Custom Directive | 👍 | ⛔ |
|
||||
| Extensions | 👍 | ⛔️ |
|
||||
| Cursor Connections | 👍 | ⛔️ |
|
||||
| Query complexity/depth | 👍 | ⛔️ |
|
||||
|
|
|
@ -14,7 +14,7 @@ use serde::Serialize;
|
|||
|
||||
use crate::extensions::Extensions;
|
||||
use crate::parser::types::{
|
||||
Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
|
||||
Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
|
||||
};
|
||||
use crate::schema::SchemaEnv;
|
||||
use crate::{
|
||||
|
@ -59,6 +59,9 @@ pub type ContextSelectionSet<'a> = ContextBase<'a, &'a Positioned<SelectionSet>>
|
|||
/// Context object for resolve field
|
||||
pub type Context<'a> = ContextBase<'a, &'a Positioned<Field>>;
|
||||
|
||||
/// Context object for execute directive.
|
||||
pub type ContextDirective<'a> = ContextBase<'a, &'a Positioned<Directive>>;
|
||||
|
||||
/// A segment in the path to the current query.
|
||||
///
|
||||
/// This is a borrowed form of [`PathSegment`](enum.PathSegment.html) used during execution instead
|
||||
|
@ -497,6 +500,32 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
.node
|
||||
.into_const_with(|name| self.var_value(&name, pos))
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn get_param_value<Q: InputType>(
|
||||
&self,
|
||||
arguments: &[(Positioned<Name>, Positioned<InputValue>)],
|
||||
name: &str,
|
||||
default: Option<fn() -> Q>,
|
||||
) -> ServerResult<(Pos, Q)> {
|
||||
let value = arguments
|
||||
.iter()
|
||||
.find(|(n, _)| n.node.as_str() == name)
|
||||
.map(|(_, value)| value)
|
||||
.cloned();
|
||||
if value.is_none() {
|
||||
if let Some(default) = default {
|
||||
return Ok((Pos::default(), default()));
|
||||
}
|
||||
}
|
||||
let (pos, value) = match value {
|
||||
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
|
||||
None => (Pos::default(), None),
|
||||
};
|
||||
InputType::parse(value)
|
||||
.map(|value| (pos, value))
|
||||
.map_err(|e| e.into_server_error(pos))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ContextBase<'a, &'a Positioned<SelectionSet>> {
|
||||
|
@ -521,19 +550,7 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
|||
name: &str,
|
||||
default: Option<fn() -> T>,
|
||||
) -> ServerResult<(Pos, T)> {
|
||||
let value = self.item.node.get_argument(name).cloned();
|
||||
if value.is_none() {
|
||||
if let Some(default) = default {
|
||||
return Ok((Pos::default(), default()));
|
||||
}
|
||||
}
|
||||
let (pos, value) = match value {
|
||||
Some(value) => (value.pos, Some(self.resolve_input_value(value)?)),
|
||||
None => (Pos::default(), None),
|
||||
};
|
||||
InputType::parse(value)
|
||||
.map(|value| (pos, value))
|
||||
.map_err(|e| e.into_server_error(pos))
|
||||
self.get_param_value(&self.item.node.arguments, name, default)
|
||||
}
|
||||
|
||||
/// Creates a uniform interface to inspect the forthcoming selections.
|
||||
|
@ -618,6 +635,17 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ContextBase<'a, &'a Positioned<Directive>> {
|
||||
#[doc(hidden)]
|
||||
pub fn param_value<T: InputType>(
|
||||
&self,
|
||||
name: &str,
|
||||
default: Option<fn() -> T>,
|
||||
) -> ServerResult<(Pos, T)> {
|
||||
self.get_param_value(&self.item.node.arguments, name, default)
|
||||
}
|
||||
}
|
||||
|
||||
/// Selection field.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SelectionField<'a> {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
use crate::extensions::ResolveFut;
|
||||
use crate::parser::types::Directive;
|
||||
use crate::registry::Registry;
|
||||
use crate::{Context, ContextDirective, ServerResult, Value};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait CustomDirectiveFactory: Send + Sync + 'static {
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
fn register(&self, registry: &mut Registry);
|
||||
|
||||
fn create(
|
||||
&self,
|
||||
ctx: &ContextDirective<'_>,
|
||||
directive: &Directive,
|
||||
) -> ServerResult<Box<dyn CustomDirective>>;
|
||||
}
|
||||
|
||||
/// Represents a custom directive.
|
||||
#[async_trait::async_trait]
|
||||
#[allow(unused_variables)]
|
||||
pub trait CustomDirective: Sync + Send + 'static {
|
||||
/// Called at resolve field.
|
||||
async fn resolve_field(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
resolve: ResolveFut<'_>,
|
||||
) -> ServerResult<Option<Value>> {
|
||||
resolve.await
|
||||
}
|
||||
}
|
|
@ -120,7 +120,8 @@ type ValidationFut<'a> =
|
|||
|
||||
type ExecuteFut<'a> = &'a mut (dyn Future<Output = Response> + Send + Unpin);
|
||||
|
||||
type ResolveFut<'a> = &'a mut (dyn Future<Output = ServerResult<Option<Value>>> + Send + Unpin);
|
||||
/// A future type used to resolve the field
|
||||
pub type ResolveFut<'a> = &'a mut (dyn Future<Output = ServerResult<Option<Value>>> + Send + Unpin);
|
||||
|
||||
/// The remainder of a extension chain for request.
|
||||
pub struct NextRequest<'a> {
|
||||
|
|
225
src/lib.rs
225
src/lib.rs
|
@ -164,6 +164,7 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
mod base;
|
||||
mod custom_directive;
|
||||
mod error;
|
||||
mod guard;
|
||||
mod look_ahead;
|
||||
|
@ -212,10 +213,12 @@ pub use base::{
|
|||
ComplexObject, Description, InputObjectType, InputType, InterfaceType, ObjectType, OutputType,
|
||||
UnionType,
|
||||
};
|
||||
pub use custom_directive::{CustomDirective, CustomDirectiveFactory};
|
||||
pub use error::{
|
||||
Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult,
|
||||
ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult,
|
||||
};
|
||||
pub use extensions::ResolveFut;
|
||||
pub use guard::{Guard, GuardExt};
|
||||
pub use look_ahead::Lookahead;
|
||||
pub use registry::CacheControl;
|
||||
|
@ -246,7 +249,7 @@ pub type FieldResult<T> = Result<T>;
|
|||
///
|
||||
/// All methods are converted to camelCase.
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -258,8 +261,9 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// | 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. *[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 |
|
||||
/// | serial | Resolve each field sequentially. | bool | Y |
|
||||
///
|
||||
/// # Field parameters
|
||||
/// # Field attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -275,8 +279,11 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// | guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | 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 |
|
||||
/// | complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
|
||||
/// | complexity | Custom field complexity. | string | Y |
|
||||
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||
///
|
||||
/// # Field argument parameters
|
||||
/// # Field argument attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|------------ |----------|
|
||||
|
@ -285,17 +292,13 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// | 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 |
|
||||
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||
/// | validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y |
|
||||
/// | complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
|
||||
/// | complexity | Custom field complexity. | string | 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 |
|
||||
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
/// | serial | Resolve each field sequentially. | bool | Y |
|
||||
/// | key | Is entity key(for Federation) | bool | Y |
|
||||
///
|
||||
/// # Derived argument parameters
|
||||
/// # Derived argument attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|------------ |----------|
|
||||
|
@ -322,7 +325,7 @@ pub type FieldResult<T> = Result<T>;
|
|||
///
|
||||
/// ```ignore
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn value(&self, ctx: &Context<'_>) -> { ... }
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -334,12 +337,12 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// ```rust
|
||||
/// use async_graphql::*;
|
||||
///
|
||||
/// struct QueryRoot {
|
||||
/// struct Query {
|
||||
/// value: i32,
|
||||
/// }
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// /// value
|
||||
/// async fn value(&self) -> i32 {
|
||||
/// self.value
|
||||
|
@ -361,7 +364,7 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot { value: 10 }, EmptyMutation, EmptySubscription);
|
||||
/// let schema = Schema::new(Query { value: 10 }, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute(r#"{
|
||||
/// value
|
||||
/// valueRef
|
||||
|
@ -406,10 +409,10 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn objs(&self) -> Vec<Box<dyn MyTrait>> {
|
||||
/// vec![
|
||||
/// Box::new(MyObj("a".to_string())),
|
||||
|
@ -419,7 +422,7 @@ pub type FieldResult<T> = Result<T>;
|
|||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
|
||||
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute("{ objs { name } }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
/// "objs": [
|
||||
|
@ -437,7 +440,7 @@ pub use async_graphql_derive::Object;
|
|||
///
|
||||
/// Similar to `Object`, but defined on a structure that automatically generates getters for all fields. For a list of valid field types, see [`Object`](attr.Object.html). All fields are converted to camelCase.
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -450,7 +453,7 @@ pub use async_graphql_derive::Object;
|
|||
/// | concretes | Specify how the concrete type of the generic SimpleObject should be implemented. *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_simple_object.html#generic-simpleobjects) | ConcreteType | Y |
|
||||
/// | serial | Resolve each field sequentially. | bool | Y |
|
||||
///
|
||||
/// # Field parameters
|
||||
/// # Field attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -458,7 +461,7 @@ pub use async_graphql_derive::Object;
|
|||
/// | name | Field name | string | Y |
|
||||
/// | deprecation | Field deprecated | bool | Y |
|
||||
/// | deprecation | Field deprecation reason | string | Y |
|
||||
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||
/// | owned | Field resolver return a ownedship value | bool | Y |
|
||||
/// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y |
|
||||
/// | 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 |
|
||||
|
@ -468,7 +471,7 @@ pub use async_graphql_derive::Object;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Derived argument parameters
|
||||
/// # Derived attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|------------ |----------|
|
||||
|
@ -484,12 +487,12 @@ pub use async_graphql_derive::Object;
|
|||
/// use async_graphql::*;
|
||||
///
|
||||
/// #[derive(SimpleObject)]
|
||||
/// struct QueryRoot {
|
||||
/// struct Query {
|
||||
/// value: i32,
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot{ value: 10 }, EmptyMutation, EmptySubscription);
|
||||
/// let schema = Schema::new(Query{ value: 10 }, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute("{ value }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
/// "value": 10,
|
||||
|
@ -508,7 +511,7 @@ pub use async_graphql_derive::SimpleObject;
|
|||
/// But this can be done more beautifully with the `ComplexObject` macro. We can use the `SimpleObject` macro to define
|
||||
/// some simple fields, and use the `ComplexObject` macro to define some other fields that need to be calculated.
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -516,15 +519,15 @@ pub use async_graphql_derive::SimpleObject;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Field parameters
|
||||
/// # Field attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
/// | skip | Skip this field | bool | Y |
|
||||
/// | name | Field name | string | Y |
|
||||
/// | desc | Field description | string | Y |
|
||||
/// | deprecation | Field deprecated | bool | Y |
|
||||
/// | deprecation | Field deprecation reason | string | Y |
|
||||
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||
/// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y |
|
||||
/// | 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 |
|
||||
|
@ -532,15 +535,23 @@ pub use async_graphql_derive::SimpleObject;
|
|||
/// | guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | 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 |
|
||||
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
/// | complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
|
||||
/// | complexity | Custom field complexity. | string | Y |
|
||||
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||
///
|
||||
/// # Derived argument parameters
|
||||
/// # Field argument attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|------------ |----------|
|
||||
/// | name | Generated derived field name | string | N |
|
||||
/// | into | Type to derived an into | string | Y |
|
||||
/// | with | Function to apply to manage advanced use cases | string| Y |
|
||||
/// | name | Argument name | string | Y |
|
||||
/// | desc | Argument description | string | Y |
|
||||
/// | 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 |
|
||||
/// | validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | 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 |
|
||||
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -561,17 +572,17 @@ pub use async_graphql_derive::SimpleObject;
|
|||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn obj(&self) -> MyObj {
|
||||
/// MyObj { a: 10, b: 20 }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
|
||||
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute("{ obj { a b c } }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
/// "obj": {
|
||||
|
@ -588,7 +599,7 @@ pub use async_graphql_derive::ComplexObject;
|
|||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_enum.html).*
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|---------------------------|----------|----------|
|
||||
|
@ -598,7 +609,7 @@ pub use async_graphql_derive::ComplexObject;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Item parameters
|
||||
/// # Item attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
|
@ -619,13 +630,13 @@ pub use async_graphql_derive::ComplexObject;
|
|||
/// #[graphql(name = "b")] B,
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot {
|
||||
/// struct Query {
|
||||
/// value1: MyEnum,
|
||||
/// value2: MyEnum,
|
||||
/// }
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// /// value1
|
||||
/// async fn value1(&self) -> MyEnum {
|
||||
/// self.value1
|
||||
|
@ -638,7 +649,7 @@ pub use async_graphql_derive::ComplexObject;
|
|||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot{ value1: MyEnum::A, value2: MyEnum::B }, EmptyMutation, EmptySubscription);
|
||||
/// let schema = Schema::new(Query{ value1: MyEnum::A, value2: MyEnum::B }, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute("{ value1 value2 }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({ "value1": "A", "value2": "b" }));
|
||||
/// });
|
||||
|
@ -649,7 +660,7 @@ pub use async_graphql_derive::Enum;
|
|||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_input_object.html).*
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -658,7 +669,7 @@ pub use async_graphql_derive::Enum;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Field parameters
|
||||
/// # Field attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|-------------|----------|
|
||||
|
@ -685,10 +696,10 @@ pub use async_graphql_derive::Enum;
|
|||
/// b: i32,
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// /// value
|
||||
/// async fn value(&self, input: MyInputObject) -> i32 {
|
||||
/// input.a * input.b
|
||||
|
@ -696,7 +707,7 @@ pub use async_graphql_derive::Enum;
|
|||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
|
||||
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
/// let res = schema.execute(r#"
|
||||
/// {
|
||||
/// value1: value(input:{a:9, b:3})
|
||||
|
@ -711,7 +722,7 @@ pub use async_graphql_derive::InputObject;
|
|||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_interface.html).*
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -723,7 +734,7 @@ pub use async_graphql_derive::InputObject;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Field parameters
|
||||
/// # Field attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
|
@ -740,7 +751,7 @@ pub use async_graphql_derive::InputObject;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Field argument parameters
|
||||
/// # Field argument attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|-------------|----------|
|
||||
|
@ -817,17 +828,17 @@ pub use async_graphql_derive::InputObject;
|
|||
/// TypeA(TypeA)
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn type_a(&self) -> MyInterface {
|
||||
/// TypeA { value: 10 }.into()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let res = schema.execute(r#"
|
||||
/// {
|
||||
/// typeA {
|
||||
|
@ -853,7 +864,7 @@ pub use async_graphql_derive::Interface;
|
|||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_union.html).*
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
|
@ -861,7 +872,7 @@ pub use async_graphql_derive::Interface;
|
|||
/// | 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 |
|
||||
///
|
||||
/// # Item parameters
|
||||
/// # Item attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|----------|----------|
|
||||
|
@ -890,17 +901,17 @@ pub use async_graphql_derive::Interface;
|
|||
/// TypeB(TypeB),
|
||||
/// }
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn all_data(&self) -> Vec<MyUnion> {
|
||||
/// vec![TypeA { value_a: 10 }.into(), TypeB { value_b: 20 }.into()]
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let res = schema.execute(r#"
|
||||
/// {
|
||||
/// allData {
|
||||
|
@ -931,7 +942,7 @@ pub use async_graphql_derive::Union;
|
|||
/// Starting with the third parameter is one or more filtering conditions, The filter condition is the parameter of the field.
|
||||
/// The filter function should be synchronous.
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -943,7 +954,7 @@ pub use async_graphql_derive::Union;
|
|||
/// | 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
|
||||
/// # Field attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
|
@ -953,9 +964,11 @@ pub use async_graphql_derive::Union;
|
|||
/// | guard | Field of guard *[See also the Book](https://async-graphql.github.io/async-graphql/en/field_guard.html)* | string | 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 |
|
||||
/// | complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
|
||||
/// | complexity | Custom field complexity. | string | Y |
|
||||
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
///
|
||||
/// # Field argument parameters
|
||||
/// # Field argument attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|------------ |----------|
|
||||
|
@ -988,7 +1001,7 @@ pub use async_graphql_derive::Subscription;
|
|||
|
||||
/// Define a Scalar
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
|
@ -1001,7 +1014,7 @@ pub use async_graphql_derive::Scalar;
|
|||
///
|
||||
/// It also implements `From<InnerType>` and `Into<InnerType>`.
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |-------------|---------------------------|----------|----------|
|
||||
|
@ -1021,10 +1034,10 @@ pub use async_graphql_derive::Scalar;
|
|||
/// #[derive(NewType)]
|
||||
/// struct Weight(f64);
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn value(&self) -> Weight {
|
||||
/// Weight(1.234)
|
||||
/// }
|
||||
|
@ -1035,7 +1048,7 @@ pub use async_graphql_derive::Scalar;
|
|||
/// let weight_f64: f64 = weight.into();
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
///
|
||||
/// let res = schema.execute("{ value }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
|
@ -1044,7 +1057,7 @@ pub use async_graphql_derive::Scalar;
|
|||
///
|
||||
/// let res = schema.execute(r#"
|
||||
/// {
|
||||
/// __type(name: "QueryRoot") {
|
||||
/// __type(name: "Query") {
|
||||
/// fields {
|
||||
/// name type {
|
||||
/// kind
|
||||
|
@ -1079,17 +1092,17 @@ pub use async_graphql_derive::Scalar;
|
|||
/// #[graphql(name)] // or: #[graphql(name = true)], #[graphql(name = "Weight")]
|
||||
/// struct Weight(f64);
|
||||
///
|
||||
/// struct QueryRoot;
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl QueryRoot {
|
||||
/// impl Query {
|
||||
/// async fn value(&self) -> Weight {
|
||||
/// Weight(1.234)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
/// let schema = Schema::build(Query, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
|
||||
///
|
||||
/// let res = schema.execute("{ value }").await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
|
@ -1098,7 +1111,7 @@ pub use async_graphql_derive::Scalar;
|
|||
///
|
||||
/// let res = schema.execute(r#"
|
||||
/// {
|
||||
/// __type(name: "QueryRoot") {
|
||||
/// __type(name: "Query") {
|
||||
/// fields {
|
||||
/// name type {
|
||||
/// kind
|
||||
|
@ -1135,7 +1148,7 @@ pub use async_graphql_derive::NewType;
|
|||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -1177,7 +1190,7 @@ pub use async_graphql_derive::MergedObject;
|
|||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*
|
||||
///
|
||||
/// # Macro parameters
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
|
@ -1258,3 +1271,79 @@ pub use async_graphql_derive::MergedSubscription;
|
|||
/// });
|
||||
/// ```
|
||||
pub use async_graphql_derive::Description;
|
||||
|
||||
/// Define a directive for query.
|
||||
///
|
||||
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/custom_directive.html).*
|
||||
///
|
||||
/// # Macro attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |---------------|---------------------------|----------|----------|
|
||||
/// | name | Object name | string | 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 |
|
||||
/// | repeatable | It means that the directive can be used multiple times in the same location. | bool | 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 |
|
||||
/// | locations | Specify the location where the directive is available, multiples are allowed. The possible values is "field", ... | string | N |
|
||||
///
|
||||
/// # Directive attributes
|
||||
///
|
||||
/// | Attribute | description | Type | Optional |
|
||||
/// |--------------|------------------------------------------|------------ |----------|
|
||||
/// | name | Argument name | string | Y |
|
||||
/// | desc | Argument description | string | Y |
|
||||
/// | 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 |
|
||||
/// | validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | 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 |
|
||||
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_graphql::*;
|
||||
///
|
||||
/// struct ConcatDirective {
|
||||
/// value: String,
|
||||
/// }
|
||||
///
|
||||
/// #[async_trait::async_trait]
|
||||
/// impl CustomDirective for ConcatDirective {
|
||||
/// async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> {
|
||||
/// resolve.await.map(|value| {
|
||||
/// value.map(|value| match value {
|
||||
/// Value::String(str) => Value::String(str + &self.value),
|
||||
/// _ => value,
|
||||
/// })
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[Directive(location = "field")]
|
||||
/// fn concat(value: String) -> impl CustomDirective {
|
||||
/// ConcatDirective { value }
|
||||
/// }
|
||||
///
|
||||
/// struct Query;
|
||||
///
|
||||
/// #[Object]
|
||||
/// impl Query {
|
||||
/// async fn value(&self) -> &'static str {
|
||||
/// "abc"
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||
/// let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||
/// .directive(concat)
|
||||
/// .finish();
|
||||
/// let res = schema.execute(r#"{ value @concat(value: "def") }"#).await.into_result().unwrap().data;
|
||||
/// assert_eq!(res, value!({
|
||||
/// "value": "abcdef",
|
||||
/// }));
|
||||
/// });
|
||||
/// ```
|
||||
pub use async_graphql_derive::Directive;
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
|||
use indexmap::map::IndexMap;
|
||||
use indexmap::set::IndexSet;
|
||||
|
||||
pub use crate::model::__DirectiveLocation;
|
||||
use crate::parser::types::{
|
||||
BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition,
|
||||
};
|
||||
|
@ -350,6 +351,7 @@ pub struct MetaDirective {
|
|||
pub locations: Vec<model::__DirectiveLocation>,
|
||||
pub args: IndexMap<&'static str, MetaInputValue>,
|
||||
pub is_repeatable: bool,
|
||||
pub visible: Option<MetaVisibleFn>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -763,6 +765,12 @@ impl Registry {
|
|||
}
|
||||
}
|
||||
|
||||
for directive in self.directives.values() {
|
||||
for arg in directive.args.values() {
|
||||
traverse_input_value(&self.types, &mut used_types, arg);
|
||||
}
|
||||
}
|
||||
|
||||
for type_name in Some(&self.query_type)
|
||||
.into_iter()
|
||||
.chain(self.mutation_type.iter())
|
||||
|
@ -886,6 +894,14 @@ impl Registry {
|
|||
}
|
||||
}
|
||||
|
||||
for directive in self.directives.values() {
|
||||
if is_visible(ctx, &directive.visible) {
|
||||
for arg in directive.args.values() {
|
||||
traverse_input_value(ctx, &self.types, &mut visible_types, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for type_name in Some(&self.query_type)
|
||||
.into_iter()
|
||||
.chain(self.mutation_type.iter())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use futures_util::FutureExt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
|
@ -5,7 +6,9 @@ use indexmap::IndexMap;
|
|||
|
||||
use crate::extensions::ResolveInfo;
|
||||
use crate::parser::types::Selection;
|
||||
use crate::{Context, ContextSelectionSet, Name, OutputType, ServerError, ServerResult, Value};
|
||||
use crate::{
|
||||
Context, ContextBase, ContextSelectionSet, Name, OutputType, ServerError, ServerResult, Value,
|
||||
};
|
||||
|
||||
/// Represents a GraphQL container object.
|
||||
///
|
||||
|
@ -152,14 +155,14 @@ impl<'a> Fields<'a> {
|
|||
continue;
|
||||
}
|
||||
|
||||
self.0.push(Box::pin({
|
||||
let resolve_fut = Box::pin({
|
||||
let ctx = ctx.clone();
|
||||
async move {
|
||||
let ctx_field = ctx.with_field(field);
|
||||
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||
let extensions = &ctx.query_env.extensions;
|
||||
|
||||
if extensions.is_empty() {
|
||||
if extensions.is_empty() && field.node.directives.is_empty() {
|
||||
Ok((
|
||||
field_name,
|
||||
root.resolve_field(&ctx_field).await?.unwrap_or_default(),
|
||||
|
@ -199,17 +202,57 @@ impl<'a> Fields<'a> {
|
|||
};
|
||||
|
||||
let resolve_fut = root.resolve_field(&ctx_field);
|
||||
futures_util::pin_mut!(resolve_fut);
|
||||
Ok((
|
||||
field_name,
|
||||
extensions
|
||||
.resolve(resolve_info, &mut resolve_fut)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
|
||||
if field.node.directives.is_empty() {
|
||||
futures_util::pin_mut!(resolve_fut);
|
||||
Ok((
|
||||
field_name,
|
||||
extensions
|
||||
.resolve(resolve_info, &mut resolve_fut)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
} else {
|
||||
let mut resolve_fut = resolve_fut.boxed();
|
||||
|
||||
for directive in &field.node.directives {
|
||||
if let Some(directive_factory) = ctx
|
||||
.schema_env
|
||||
.custom_directives
|
||||
.get(directive.node.name.node.as_str())
|
||||
{
|
||||
let ctx_directive = ContextBase {
|
||||
path_node: ctx_field.path_node,
|
||||
item: directive,
|
||||
schema_env: ctx_field.schema_env,
|
||||
query_env: ctx_field.query_env,
|
||||
};
|
||||
let directive_instance = directive_factory
|
||||
.create(&ctx_directive, &directive.node)?;
|
||||
resolve_fut = Box::pin({
|
||||
let ctx_field = ctx_field.clone();
|
||||
async move {
|
||||
directive_instance
|
||||
.resolve_field(&ctx_field, &mut resolve_fut)
|
||||
.await
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
field_name,
|
||||
extensions
|
||||
.resolve(resolve_info, &mut resolve_fut)
|
||||
.await?
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
self.0.push(resolve_fut);
|
||||
}
|
||||
selection => {
|
||||
let (type_condition, selection_set) = match selection {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -6,6 +7,7 @@ use futures_util::stream::{self, Stream, StreamExt};
|
|||
use indexmap::map::IndexMap;
|
||||
|
||||
use crate::context::{Data, QueryEnvInner};
|
||||
use crate::custom_directive::CustomDirectiveFactory;
|
||||
use crate::extensions::{ExtensionFactory, Extensions};
|
||||
use crate::model::__DirectiveLocation;
|
||||
use crate::parser::types::{Directive, DocumentOperations, OperationType, Selection, SelectionSet};
|
||||
|
@ -31,6 +33,7 @@ pub struct SchemaBuilder<Query, Mutation, Subscription> {
|
|||
complexity: Option<usize>,
|
||||
depth: Option<usize>,
|
||||
extensions: Vec<Box<dyn ExtensionFactory>>,
|
||||
custom_directives: HashMap<&'static str, Box<dyn CustomDirectiveFactory>>,
|
||||
}
|
||||
|
||||
impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription> {
|
||||
|
@ -131,6 +134,27 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
|
|||
self
|
||||
}
|
||||
|
||||
/// Register a custom directive.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the directive with the same name is already registered.
|
||||
pub fn directive<T: CustomDirectiveFactory>(mut self, directive: T) -> Self {
|
||||
let name = directive.name();
|
||||
let instance = Box::new(directive);
|
||||
|
||||
instance.register(&mut self.registry);
|
||||
|
||||
if name == "skip"
|
||||
|| name == "include"
|
||||
|| self.custom_directives.insert(name, instance).is_some()
|
||||
{
|
||||
panic!("Directive `{}` already exists", name);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Build schema.
|
||||
pub fn finish(mut self) -> Schema<Query, Mutation, Subscription> {
|
||||
// federation
|
||||
|
@ -149,6 +173,7 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
|
|||
env: SchemaEnv(Arc::new(SchemaEnvInner {
|
||||
registry: self.registry,
|
||||
data: self.data,
|
||||
custom_directives: self.custom_directives,
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
@ -158,6 +183,7 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
|
|||
pub struct SchemaEnvInner {
|
||||
pub registry: Registry,
|
||||
pub data: Data,
|
||||
pub custom_directives: HashMap<&'static str, Box<dyn CustomDirectiveFactory>>,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -244,6 +270,7 @@ where
|
|||
complexity: None,
|
||||
depth: None,
|
||||
extensions: Default::default(),
|
||||
custom_directives: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,6 +316,7 @@ where
|
|||
args
|
||||
},
|
||||
is_repeatable: false,
|
||||
visible: None,
|
||||
});
|
||||
|
||||
registry.add_directive(MetaDirective {
|
||||
|
@ -312,6 +340,7 @@ where
|
|||
args
|
||||
},
|
||||
is_repeatable: false,
|
||||
visible: None,
|
||||
});
|
||||
|
||||
// register scalars
|
||||
|
|
|
@ -76,3 +76,54 @@ pub async fn test_directive_include() {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_custom_directive() {
|
||||
struct Concat {
|
||||
prefix: String,
|
||||
suffix: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CustomDirective for Concat {
|
||||
async fn resolve_field(
|
||||
&self,
|
||||
_ctx: &Context<'_>,
|
||||
resolve: ResolveFut<'_>,
|
||||
) -> ServerResult<Option<Value>> {
|
||||
resolve.await.map(|value| {
|
||||
value.map(|value| match value {
|
||||
Value::String(str) => Value::String(self.prefix.clone() + &str + &self.suffix),
|
||||
_ => value,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[Directive(location = "field")]
|
||||
fn concat(prefix: String, suffix: String) -> impl CustomDirective {
|
||||
Concat { prefix, suffix }
|
||||
}
|
||||
|
||||
struct QueryRoot;
|
||||
|
||||
#[Object]
|
||||
impl QueryRoot {
|
||||
pub async fn value(&self) -> &'static str {
|
||||
"abc"
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
|
||||
.directive(concat)
|
||||
.finish();
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute(r#"{ value @concat(prefix: "&", suffix: "*") }"#)
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap()
|
||||
.data,
|
||||
value!({ "value": "&abc*" })
|
||||
);
|
||||
}
|
||||
|
|
|
@ -261,6 +261,30 @@ pub async fn test_visible_fn() {
|
|||
|
||||
#[tokio::test]
|
||||
pub async fn test_indirect_hiding_type() {
|
||||
#[derive(Enum, Eq, PartialEq, Copy, Clone)]
|
||||
enum MyEnum1 {
|
||||
A,
|
||||
}
|
||||
|
||||
#[derive(Enum, Eq, PartialEq, Copy, Clone)]
|
||||
enum MyEnum2 {
|
||||
A,
|
||||
}
|
||||
|
||||
struct MyDirective;
|
||||
|
||||
impl CustomDirective for MyDirective {}
|
||||
|
||||
#[Directive(location = "field")]
|
||||
fn my_directive1(_a: MyEnum1) -> impl CustomDirective {
|
||||
MyDirective
|
||||
}
|
||||
|
||||
#[Directive(location = "field", visible = false)]
|
||||
fn my_directive2(_a: MyEnum2) -> impl CustomDirective {
|
||||
MyDirective
|
||||
}
|
||||
|
||||
#[derive(SimpleObject)]
|
||||
struct MyObj1 {
|
||||
a: i32,
|
||||
|
@ -380,7 +404,10 @@ pub async fn test_indirect_hiding_type() {
|
|||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||
.directive(my_directive1)
|
||||
.directive(my_directive2)
|
||||
.finish();
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute(r#"{ __type(name: "MyObj1") { name } }"#)
|
||||
|
@ -530,6 +557,26 @@ pub async fn test_indirect_hiding_type() {
|
|||
.data,
|
||||
value!({ "__type": null })
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute(r#"{ __type(name: "MyEnum1") { name } }"#)
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap()
|
||||
.data,
|
||||
value!({ "__type": { "name": "MyEnum1" } })
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute(r#"{ __type(name: "MyEnum2") { name } }"#)
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap()
|
||||
.data,
|
||||
value!({ "__type": null })
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Reference in New Issue