Query complexity and depth

⚠️GraphQL provides a powerful way to query your data, but putting great power in the hands of your API clients also exposes you to a risk of denial of service attacks. You can mitigate that risk with Async-graphql by limiting the complexity and depth of the queries you allow.

Expensive Queries

Consider a schema that allows listing blog posts. Each blog post is also related to other posts.

type Query {
	posts(count: Int = 10): [Post!]!
}

type Post {
	title: String!
	text: String!
	related(count: Int = 10): [Post!]!
}

It's not too hard to craft a query that will cause a very large response:

{
    posts(count: 100) {
        related(count: 100) {
            related(count: 100) {
                related(count: 100) {
                    title
                }
            }
        }
    }
}

The size of the response increases exponentially with every other level of the related field. Fortunately, Async-graphql provides a way to prevent such queries.

Limiting Query depth

The depth is the number of nesting levels of the field, and the following is a query with a depth of 3.

{
    a {
        b {
            c
        }
    }
}

You can limit the depth when creating Schema. If the query exceeds this limit, an error will occur and the message Query is nested too deep will be returned.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
struct Query;
#[Object]
impl Query { async fn version(&self) -> &str { "1.0" } }
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
    .limit_depth(5) // Limit the maximum depth to 5
    .finish();
}

Limiting Query complexity

The complexity is the number of fields in the query. The default complexity of each field is 1. Below is a query with a complexity of 6.

{
    a b c {
        d {
            e f
        }
    }
}

You can limit the complexity when creating the Schema. If the query exceeds this limit, an error will occur and Query is too complex will be returned.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
struct Query;
#[Object]
impl Query { async fn version(&self) -> &str { "1.0" } }
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
    .limit_complexity(5) // Limit the maximum complexity to 5
    .finish();
}

Custom Complexity Calculation

There are two ways to customize the complexity for non-list type and list type fields.

In the following code, the complexity of the value field is 5. The complexity of the values field is count * child_complexity, child_complexity is a special variable that represents the complexity of the subquery, and count is the parameter of the field, used to calculate the complexity of the values field, and the type of the return value must be usize.

#![allow(unused)]
fn main() {
extern crate async_graphql;
use async_graphql::*;
struct Query;

#[Object]
impl Query {
    #[graphql(complexity = 5)]
    async fn value(&self) -> i32 {
        todo!()
    }

    #[graphql(complexity = "count * child_complexity")]
    async fn values(&self, count: usize) -> i32 {
        todo!()
    }
}
}

Note: The complexity calculation is done in the validation phase and not the execution phase, so you don't have to worry about partial execution of over-limit queries.