Query Cost Analysis
Query cost analysis can be used to limit the complexity of the executed queries and to protect the service against various attacks (DoS etc).
Cost of the query is defined by calculating complexity value for each field and
setting a maximum allowed cost. Complexity can be set by setting a default field complexity
which will be used for all fields in a query and/or by using @cost
directive on the schema.
Example
Example schema has few fields with and without set complexity.
public CostFacts()
{
var sdl =
@"
schema {
query: Query
}
type Query {
default: Int
withCost: Int @cost(complexity: 1)
withMultiplier(count: Int!): Int @cost(complexity: 2, multipliers: [""count""])
}
";
Schema = new SchemaBuilder()
.Include(CostAnalyzer.CostDirective)
.Sdl(sdl)
.Build();
}
Default field complexity
Default field complexity will be used when @cost
directive is not present in the field.
[Fact]
public void Cost_above_max_cost_with_defaultComplexity()
{
/* Given */
var document = Parser.ParseDocument(
@"{
default
}");
/* When */
var result = Validate(
document,
CostAnalyzer.MaxCost(
maxCost: 0,
defaultFieldComplexity: 1)
);
/* Then */
Assert.False(result.IsValid);
Assert.Single(
result.Errors,
error => error.Code == "MAX_COST");
}
Cost Directive
Complexity of field is set with @cost
directive.
[Fact]
public void Cost_above_max_cost_with_costDirective()
{
/* Given */
var document = Parser.ParseDocument(
@"{
withCost
}");
/* When */
var result = Validate(
document,
CostAnalyzer.MaxCost(
maxCost: 0,
defaultFieldComplexity: 0)
);
/* Then */
Assert.False(result.IsValid);
Assert.Single(
result.Errors,
error => error.Code == "MAX_COST");
}
In some cases the complexity of the field is related to its arguments. In these cases value of the argument can be used as mulpliplier for the complexity.
[Fact]
public void Cost_above_max_cost_with_costDirective_and_multiplier()
{
/* Given */
var document = Parser.ParseDocument(
@"{
withMultiplier(count: 5)
}");
/* When */
var result = Validate(
document,
CostAnalyzer.MaxCost(
maxCost: 5,
defaultFieldComplexity: 0)
);
/* Then */
Assert.False(result.IsValid);
Assert.Single(
result.Errors,
error => error.Code == "MAX_COST");
}
Import directive
Using SDL import
[Fact]
public async Task Parse_Sdl()
{
/* Given */
var sdl =
@"
""""""
tanka_import from ""tanka://cost-analysis""
""""""
type ObjectType {
property: Int! @cost(complexity: 1)
}
type Query {
obj: ObjectType
}
";
/* When */
var builder = await new SchemaBuilder()
// BuiltIn import providers are used
.SdlAsync(sdl);
var schema = builder.Build();
/* Then */
var objectType = schema.GetNamedType<ObjectType>("ObjectType");
var property = schema.GetField(objectType.Name, "property");
Assert.Single(property.Directives, directive => directive.Name == "cost");
}
Or you can include it manually using the SchemaBuilder.Include
var builder = new SchemaBuilder()
.Include(CostAnalyzer.CostDirective);