Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions execution/graphql/complexity.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (
var DefaultComplexityCalculator = defaultComplexityCalculator{}

type ComplexityCalculator interface {
Calculate(operation, definition *ast.Document) (ComplexityResult, error)
Calculate(operation, definition *ast.Document, skipIntrospection bool) (ComplexityResult, error)
}

type defaultComplexityCalculator struct {
}

func (d defaultComplexityCalculator) Calculate(operation, definition *ast.Document) (ComplexityResult, error) {
func (d defaultComplexityCalculator) Calculate(operation, definition *ast.Document, skipIntrospection bool) (ComplexityResult, error) {
report := operationreport.Report{}
globalComplexityResult, fieldsComplexityResult := operation_complexity.CalculateOperationComplexity(operation, definition, &report)
globalComplexityResult, fieldsComplexityResult := operation_complexity.CalculateOperationComplexity(operation, definition, skipIntrospection, &report)

return complexityResult(globalComplexityResult, fieldsComplexityResult, report)
}
Expand Down
4 changes: 2 additions & 2 deletions execution/graphql/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (r *Request) SetHeader(header http.Header) {
r.request.Header = header
}

func (r *Request) CalculateComplexity(complexityCalculator ComplexityCalculator, schema *Schema) (ComplexityResult, error) {
func (r *Request) CalculateComplexity(complexityCalculator ComplexityCalculator, schema *Schema, skipIntrospection bool) (ComplexityResult, error) {
if schema == nil {
return ComplexityResult{}, ErrNilSchema
}
Expand All @@ -81,7 +81,7 @@ func (r *Request) CalculateComplexity(complexityCalculator ComplexityCalculator,
)
}

return complexityCalculator.Calculate(&r.document, &schema.document)
return complexityCalculator.Calculate(&r.document, &schema.document, skipIntrospection)
}

func (r *Request) Document() *ast.Document {
Expand Down
6 changes: 3 additions & 3 deletions execution/graphql/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestRequest_parseQueryOnce(t *testing.T) {
func TestRequest_CalculateComplexity(t *testing.T) {
t.Run("should return error when schema is nil", func(t *testing.T) {
request := Request{}
result, err := request.CalculateComplexity(DefaultComplexityCalculator, nil)
result, err := request.CalculateComplexity(DefaultComplexityCalculator, nil, false)
Comment thread
ysmolski marked this conversation as resolved.
Outdated
assert.Error(t, err)
assert.Equal(t, ErrNilSchema, err)
assert.Equal(t, 0, result.NodeCount, "unexpected node count")
Expand All @@ -103,7 +103,7 @@ func TestRequest_CalculateComplexity(t *testing.T) {
schema := StarwarsSchema(t)

request := StarwarsRequestForQuery(t, starwars.FileSimpleHeroQuery)
result, err := request.CalculateComplexity(DefaultComplexityCalculator, schema)
result, err := request.CalculateComplexity(DefaultComplexityCalculator, schema, false)
assert.NoError(t, err)
assert.Equal(t, 1, result.NodeCount, "unexpected node count")
assert.Equal(t, 1, result.Complexity, "unexpected complexity")
Expand All @@ -124,7 +124,7 @@ func TestRequest_CalculateComplexity(t *testing.T) {
schema := StarwarsSchema(t)

request := StarwarsRequestForQuery(t, starwars.FileHeroWithAliasesQuery)
result, err := request.CalculateComplexity(DefaultComplexityCalculator, schema)
result, err := request.CalculateComplexity(DefaultComplexityCalculator, schema, false)
assert.NoError(t, err)
assert.Equal(t, 2, result.NodeCount, "unexpected node count")
assert.Equal(t, 2, result.Complexity, "unexpected complexity")
Expand Down
22 changes: 12 additions & 10 deletions v2/pkg/middleware/operation_complexity/operation_complexity.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,21 @@ var (
)

const (
skipIntrospection = true
__schemaLiteral = "__schema"
__typeLiteral = "__type"
__schemaLiteral = "__schema"
__typeLiteral = "__type"
)

type OperationComplexityEstimator struct {
walker *astvisitor.Walker
visitor *complexityVisitor
}

func NewOperationComplexityEstimator() *OperationComplexityEstimator {

func NewOperationComplexityEstimator(skipIntrospection bool) *OperationComplexityEstimator {
walker := astvisitor.NewWalker(48)
visitor := &complexityVisitor{
Walker: &walker,
multipliers: make([]multiplier, 0, 16),
Walker: &walker,
multipliers: make([]multiplier, 0, 16),
skipIntrospection: skipIntrospection,
}

walker.RegisterEnterDocumentVisitor(visitor)
Expand Down Expand Up @@ -116,8 +115,8 @@ func (n *OperationComplexityEstimator) Do(operation, definition *ast.Document, r
return globalResult, n.visitor.calculatedRootFieldStats
}

func CalculateOperationComplexity(operation, definition *ast.Document, report *operationreport.Report) (OperationStats, []RootFieldStats) {
estimator := NewOperationComplexityEstimator()
func CalculateOperationComplexity(operation, definition *ast.Document, skipIntrospection bool, report *operationreport.Report) (OperationStats, []RootFieldStats) {
estimator := NewOperationComplexityEstimator(skipIntrospection)
return estimator.Do(operation, definition, report)
}

Expand All @@ -141,6 +140,9 @@ type complexityVisitor struct {
currentRootFieldSelectionSetDepth int

calculatedRootFieldStats []RootFieldStats

// Enforces to ignore introspection queries in calculations.
skipIntrospection bool
}

type multiplier struct {
Expand Down Expand Up @@ -202,7 +204,7 @@ func (c *complexityVisitor) EnterField(ref int) {
}

typeName, fieldName, alias := c.extractFieldRelatedNames(ref, definition)
Comment thread
ysmolski marked this conversation as resolved.
if skipIntrospection && (fieldName == __schemaLiteral || fieldName == __typeLiteral) {
if c.skipIntrospection && (fieldName == __schemaLiteral || fieldName == __typeLiteral) {
c.SkipNode()
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,26 @@ func TestCalculateOperationComplexity(t *testing.T) {
})
t.Run("introspection query", func(t *testing.T) {
run(t, testDefinition, introspectionQuery,
OperationStats{
NodeCount: 59,
Complexity: 59,
Depth: 13,
},
[]RootFieldStats{
{
TypeName: "Query",
FieldName: "__schema",
Stats: OperationStats{
NodeCount: 59,
Complexity: 59,
Depth: 12,
},
},
},
)
})
t.Run("introspection query with skip", func(t *testing.T) {
runSkipIntrospection(t, testDefinition, introspectionQuery,
OperationStats{
NodeCount: 0,
Complexity: 0,
Expand All @@ -477,14 +497,14 @@ func TestCalculateOperationComplexity(t *testing.T) {
})
}

var run = func(t *testing.T, definition, operation string, expectedGlobalComplexityResult OperationStats, expectedFieldsComplexityResult []RootFieldStats) {
func runConfig(t *testing.T, definition, operation string, expectedGlobalComplexityResult OperationStats, expectedFieldsComplexityResult []RootFieldStats, skipIntrospection bool) {
def := unsafeparser.ParseGraphqlDocumentString(definition)
op := unsafeparser.ParseGraphqlDocumentString(operation)
report := operationreport.Report{}

astnormalization.NormalizeOperation(&op, &def, &report)

actualGlobalComplexityResult, actualFieldsComplexityResult := CalculateOperationComplexity(&op, &def, &report)
actualGlobalComplexityResult, actualFieldsComplexityResult := CalculateOperationComplexity(&op, &def, skipIntrospection, &report)
if report.HasErrors() {
require.NoError(t, report)
Comment thread
ysmolski marked this conversation as resolved.
Outdated
}
Expand All @@ -495,11 +515,22 @@ var run = func(t *testing.T, definition, operation string, expectedGlobalComplex
assert.Equal(t, expectedFieldsComplexityResult, actualFieldsComplexityResult, "unexpected fields complexity result")
}

func run(t *testing.T, definition, operation string, expectedGlobalComplexityResult OperationStats, expectedFieldsComplexityResult []RootFieldStats) {
runConfig(t, definition, operation, expectedGlobalComplexityResult, expectedFieldsComplexityResult, false)
}

func runSkipIntrospection(t *testing.T, definition, operation string, expectedGlobalComplexityResult OperationStats, expectedFieldsComplexityResult []RootFieldStats) {
runConfig(t, definition, operation, expectedGlobalComplexityResult, expectedFieldsComplexityResult, true)
}

func BenchmarkEstimateComplexity(b *testing.B) {
def := unsafeparser.ParseGraphqlDocumentString(testDefinition)
op := unsafeparser.ParseGraphqlDocumentString(complexQuery)

estimator := NewOperationComplexityEstimator()
// We use CalculateOperationComplexity in production. We might want to move
// NewOperationComplexityEstimator into the benchmarking loop or just benchmark what we do
// in production.
estimator := NewOperationComplexityEstimator(false)
report := operationreport.Report{}

b.ResetTimer()
Expand Down