Class: GraphQL::Analysis::QueryComplexity

Inherits:
Analyzer
  • Object
show all
Defined in:
lib/graphql/analysis/query_complexity.rb

Overview

Calculate the complexity of a query, using Field#complexity values.

Direct Known Subclasses

MaxQueryComplexity

Defined Under Namespace

Classes: ScopedTypeComplexity

Instance Method Summary collapse

Methods inherited from Analyzer

#analyze?, #visit?

Constructor Details

#initialize(query) ⇒ QueryComplexity

State for the query complexity calculation:

  • complexities_on_type holds complexity scores for each type
[View source]

8
9
10
11
12
# File 'lib/graphql/analysis/query_complexity.rb', line 8

def initialize(query)
  super
  @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
  @complexities_on_type_by_query = {}
end

Instance Method Details

#on_enter_field(node, parent, visitor) ⇒ Object

[View source]

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/graphql/analysis/query_complexity.rb', line 78

def on_enter_field(node, parent, visitor)
  # We don't want to visit fragment definitions,
  # we'll visit them when we hit the spreads instead
  return if visitor.visiting_fragment_definition?
  return if visitor.skipping?
  return if @skip_introspection_fields && visitor.field_definition.introspection?
  parent_type = visitor.parent_type_definition
  field_key = node.alias || node.name

  # Find or create a complexity scope stack for this query.
  scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]

  # Find or create the complexity costing node for this field.
  scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
  scope.nodes.push(node)
  scopes_stack.push(scope)
end

#on_leave_field(node, parent, visitor) ⇒ Object

[View source]

96
97
98
99
100
101
102
103
104
# File 'lib/graphql/analysis/query_complexity.rb', line 96

def on_leave_field(node, parent, visitor)
  # We don't want to visit fragment definitions,
  # we'll visit them when we hit the spreads instead
  return if visitor.visiting_fragment_definition?
  return if visitor.skipping?
  return if @skip_introspection_fields && visitor.field_definition.introspection?
  scopes_stack = @complexities_on_type_by_query[visitor.query]
  scopes_stack.pop
end

#resultObject

Override this method to use the complexity result

[View source]

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/graphql/analysis/query_complexity.rb', line 15

def result
  case subject.schema.complexity_cost_calculation_mode_for(subject.context)
  when :future
    max_possible_complexity
  when :legacy
    max_possible_complexity(mode: :legacy)
  when :compare
    future_complexity = max_possible_complexity
    legacy_complexity = max_possible_complexity(mode: :legacy)
    if future_complexity != legacy_complexity
      subject.schema.legacy_complexity_cost_calculation_mismatch(subject, future_complexity, legacy_complexity)
    else
      future_complexity
    end
  when nil
    subject.logger.warn <<~GRAPHQL
      GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-docs/#{GraphQL::VERSION}/Schema.html#complexity_cost_cacluation_mode-class_method

      To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:

        complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`

    GRAPHQL
    max_possible_complexity
  else
    raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
  end
end