Custom directive
There are two types of directives in GraphQL: executable and type system. Executable directives are used by the client within an operation to modify the behavior (like the built-in @include
and @skip
directives). Type system directives provide additional information about the types, potentially modifying how the server behaves (like @deprecated
and @oneOf
). async-graphql
allows you to declare both types of custom directives, with different limitations on each.
Executable directives
To create a custom executable 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 custom executable directives located at FIELD
.
#![allow(unused)] fn main() { extern crate async_graphql; 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 } } }
Register the directive when building the schema:
#![allow(unused)] fn main() { extern crate async_graphql; use async_graphql::*; struct Query; #[Object] impl Query { async fn version(&self) -> &str { "1.0" } } struct ConcatDirective { value: String, } #[async_trait::async_trait] impl CustomDirective for ConcatDirective { async fn resolve_field(&self, _ctx: &Context<'_>, resolve: ResolveFut<'_>) -> ServerResult<Option<Value>> { todo!() } } #[Directive(location = "Field")] fn concat(value: String) -> impl CustomDirective { ConcatDirective { value } } let schema = Schema::build(Query, EmptyMutation, EmptySubscription) .directive(concat) .finish(); }
Type system directives
To create a custom type system directive, you can use the #[TypeDirective]
macro on a function:
#![allow(unused)] fn main() { extern crate async_graphql; use async_graphql::*; #[TypeDirective( location = "FieldDefinition", location = "Object", )] fn testDirective(scope: String, input: u32, opt: Option<u64>) {} }
Current only the FieldDefinition
and Object
locations are supported, you can select one or both. After declaring the directive, you can apply it to a relevant location (after importing the function) like this:
#![allow(unused)] fn main() { extern crate async_graphql; use async_graphql::*; #[TypeDirective( location = "FieldDefinition", location = "Object", )] fn testDirective(scope: String, input: u32, opt: Option<u64>) {} #[derive(SimpleObject)] #[graphql( directive = testDirective::apply("simple object type".to_string(), 1, Some(3)) )] struct SimpleValue { #[graphql( directive = testDirective::apply("field and param with \" symbol".to_string(), 2, Some(3)) )] some_data: String, } }
This example produces a schema like this:
type SimpleValue @testDirective(scope: "simple object type", input: 1, opt: 3) {
someData: String! @testDirective(scope: "field and param with \" symbol", input: 2, opt: 3)
}
directive @testDirective(scope: String!, input: Int!, opt: Int) on FIELD_DEFINITION | OBJECT
Note: To use a type-system directive with Apollo Federation's @composeDirective
, see the federation docs