Module: GraphQL::StaticValidation::FieldsWillMerge

Defined in:
lib/graphql/static_validation/rules/fields_will_merge.rb

Defined Under Namespace

Classes: Field

Constant Summary collapse

NO_ARGS =

Validates that a selection set is valid if all fields (including spreading any fragments) either correspond to distinct response names or can be merged without ambiguity.

Optimized algorithm based on: https://tech.new-work.se/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01

Instead of comparing fields, fields-vs-fragments, and fragments-vs-fragments separately (which leads to exponential recursion through nested fragments), we flatten all fragment spreads into a single field map and compare within it.

GraphQL::EmptyObjects::EMPTY_HASH

Instance Method Summary collapse

Instance Method Details

#initializeObject



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/graphql/static_validation/rules/fields_will_merge.rb', line 38

def initialize(*)
  super
  @conflict_count = 0
  @max_errors = context.max_errors
  @fragments = context.fragments
  # Track which sub-selection node pairs have been compared to prevent
  # infinite recursion with cyclic fragments
  @compared_sub_selections = {}.compare_by_identity
  # Cache mutually_exclusive? results for type pairs
  @mutually_exclusive_cache = {}.compare_by_identity
  # Cache collect_fields results for sub-selection comparison
  @sub_fields_cache = {}.compare_by_identity
end

#on_field(node, _parent) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/graphql/static_validation/rules/fields_will_merge.rb', line 59

def on_field(node, _parent)
  if !node.selections.empty? && selections_may_conflict?(node.selections)
    @conflicts = nil
    conflicts_within_selection_set(node, type_definition)
    @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
  end
  super
end

#on_operation_definition(node, _parent) ⇒ Object



52
53
54
55
56
57
# File 'lib/graphql/static_validation/rules/fields_will_merge.rb', line 52

def on_operation_definition(node, _parent)
  @conflicts = nil
  conflicts_within_selection_set(node, type_definition)
  @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } }
  super
end