How extensions are defined

An async-graphql extension is defined by implementing the trait Extension associated. The Extension trait allows you to insert custom code to some several steps used to respond to GraphQL's queries through async-graphql. With Extensions, your application can hook into the GraphQL's requests lifecycle to add behaviors about incoming requests or outgoing response.

Extensions are a lot like middleware from other frameworks, be careful when using those: when you use an extension it'll be run for every GraphQL request.

Across every step, you'll have the ExtensionContext supplied with data about your current request execution. Feel free to check how it's constructed in the code, documentation about it will soon come.

A word about middleware

For those who don't know, let's dig deeper into what is a middleware:

async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
  // Logic to your middleware.

  /*
   * Final step to your middleware, we call the next function which will trigger
   * the execution of the next middleware. It's like a `callback` in JavaScript.
   */
  next.run(ctx).await
}

As you have seen, a Middleware is only a function calling the next function at the end, but we could also do a middleware with the next.run function at the start. This is where it's becoming tricky: depending on where you put your logic and where is the next.run call, your logic won't have the same execution order.

Depending on your logic code, you'll want to process it before or after the next.run call. If you need more information about middlewares, there are a lot of things on the web.

Processing of a query

There are several steps to go to process a query to completion, you'll be able to create extension based on these hooks.

request

First, when we receive a request, if it's not a subscription, the first function to be called will be request, it's the first step, it's the function called at the incoming request, and it's also the function which will output the response to the user.

Default implementation for request:

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
    next.run(ctx).await
}
}
}

Depending on where you put your logic code, it'll be executed at the beginning or at the end of the query being processed.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
    // The code here will be run before the prepare_request is executed.
    let result = next.run(ctx).await;
    // The code after the completion of this future will be after the processing, just before sending the result to the user.
    result
}
}
}

prepare_request

Just after the request, we will have the prepare_request lifecycle, which will be hooked.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::*;
use async_graphql::extensions::*;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
async fn prepare_request(
    &self,
    ctx: &ExtensionContext<'_>,
    request: Request,
    next: NextPrepareRequest<'_>,
) -> ServerResult<Request> {
    // The code here will be un before the prepare_request is executed, just after the request lifecycle hook.
    let result = next.run(ctx, request).await;
    // The code here will be run just after the prepare_request
    result
}
}
}

parse_query

The parse_query will create a GraphQL ExecutableDocument on your query, it'll check if the query is valid for the GraphQL Spec. Usually the implemented spec in async-graphql tends to be the last stable one (October2021).

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
use async_graphql::parser::types::ExecutableDocument;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
/// Called at parse query.
async fn parse_query(
    &self,
    ctx: &ExtensionContext<'_>,
    // The raw query
    query: &str,
    // The variables
    variables: &Variables,
    next: NextParseQuery<'_>,
) -> ServerResult<ExecutableDocument> {
    next.run(ctx, query, variables).await
}
}
}

validation

The validation step will check (depending on your validation_mode) rules the query should abide to and give the client data about why the query is not valid.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
/// Called at validation query.
async fn validation(
  &self,
  ctx: &ExtensionContext<'_>,
  next: NextValidation<'_>,
) -> Result<ValidationResult, Vec<ServerError>> {
  next.run(ctx).await
}
}
}

execute

The execution step is a huge one, it'll start the execution of the query by calling each resolver concurrently for a Query and serially for a Mutation.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
/// Called at execute query.
async fn execute(
    &self,
    ctx: &ExtensionContext<'_>,
    operation_name: Option<&str>,
    next: NextExecute<'_>,
) -> Response {
    // Before starting resolving the whole query
    let result = next.run(ctx, operation_name).await;
    // After resolving the whole query
    result
}
}
}

resolve

The resolve step is launched for each field.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware { 
/// Called at resolve field.
async fn resolve(
    &self,
    ctx: &ExtensionContext<'_>,
    info: ResolveInfo<'_>,
    next: NextResolve<'_>,
) -> ServerResult<Option<Value>> {
    // Logic before resolving the field
    let result = next.run(ctx, info).await;
    // Logic after resolving the field
    result
}
}
}

subscribe

The subscribe lifecycle has the same behavior as the request but for a Subscritpion.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
use async_graphql::extensions::*;
use futures_util::stream::BoxStream;
struct MyMiddleware;
#[async_trait::async_trait]
impl Extension for MyMiddleware {
/// Called at subscribe request.
fn subscribe<'s>(
    &self,
    ctx: &ExtensionContext<'_>,
    stream: BoxStream<'s, Response>,
    next: NextSubscribe<'_>,
) -> BoxStream<'s, Response> {
    next.run(ctx, stream)
}
}
}