graphql 2.3.5 → 2.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +46 -0
  3. data/lib/graphql/analysis/analyzer.rb +89 -0
  4. data/lib/graphql/analysis/field_usage.rb +82 -0
  5. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  6. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  7. data/lib/graphql/analysis/query_complexity.rb +183 -0
  8. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  9. data/lib/graphql/analysis/visitor.rb +283 -0
  10. data/lib/graphql/analysis.rb +92 -1
  11. data/lib/graphql/current.rb +52 -0
  12. data/lib/graphql/dataloader/async_dataloader.rb +2 -0
  13. data/lib/graphql/dataloader/source.rb +5 -2
  14. data/lib/graphql/dataloader.rb +4 -1
  15. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  16. data/lib/graphql/execution/interpreter/runtime.rb +29 -25
  17. data/lib/graphql/execution/interpreter.rb +3 -1
  18. data/lib/graphql/execution/lookahead.rb +10 -10
  19. data/lib/graphql/introspection/directive_type.rb +1 -1
  20. data/lib/graphql/introspection/entry_points.rb +2 -2
  21. data/lib/graphql/introspection/field_type.rb +1 -1
  22. data/lib/graphql/introspection/schema_type.rb +6 -11
  23. data/lib/graphql/introspection/type_type.rb +5 -5
  24. data/lib/graphql/language/document_from_schema_definition.rb +19 -26
  25. data/lib/graphql/language/lexer.rb +0 -3
  26. data/lib/graphql/language/nodes.rb +2 -2
  27. data/lib/graphql/language/parser.rb +9 -1
  28. data/lib/graphql/language/sanitized_printer.rb +1 -1
  29. data/lib/graphql/language.rb +0 -1
  30. data/lib/graphql/query/context.rb +7 -1
  31. data/lib/graphql/query/null_context.rb +2 -2
  32. data/lib/graphql/query/validation_pipeline.rb +2 -2
  33. data/lib/graphql/query.rb +26 -7
  34. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +129 -0
  35. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  36. data/lib/graphql/rubocop.rb +2 -0
  37. data/lib/graphql/schema/addition.rb +1 -0
  38. data/lib/graphql/schema/always_visible.rb +1 -0
  39. data/lib/graphql/schema/argument.rb +19 -5
  40. data/lib/graphql/schema/build_from_definition.rb +8 -1
  41. data/lib/graphql/schema/directive/flagged.rb +1 -1
  42. data/lib/graphql/schema/directive.rb +2 -0
  43. data/lib/graphql/schema/enum.rb +51 -20
  44. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  45. data/lib/graphql/schema/field.rb +85 -39
  46. data/lib/graphql/schema/has_single_input_argument.rb +2 -1
  47. data/lib/graphql/schema/input_object.rb +8 -7
  48. data/lib/graphql/schema/interface.rb +20 -4
  49. data/lib/graphql/schema/introspection_system.rb +5 -16
  50. data/lib/graphql/schema/member/has_arguments.rb +14 -9
  51. data/lib/graphql/schema/member/has_fields.rb +8 -6
  52. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  53. data/lib/graphql/schema/resolver.rb +5 -5
  54. data/lib/graphql/schema/subset.rb +509 -0
  55. data/lib/graphql/schema/type_expression.rb +2 -2
  56. data/lib/graphql/schema/types_migration.rb +187 -0
  57. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  58. data/lib/graphql/schema/validator.rb +2 -0
  59. data/lib/graphql/schema/warden.rb +89 -5
  60. data/lib/graphql/schema.rb +109 -53
  61. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  62. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  63. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  64. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  65. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  66. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  67. data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
  68. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  69. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  70. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  71. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  72. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  73. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
  74. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  75. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  76. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  77. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  78. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  79. data/lib/graphql/static_validation/validation_context.rb +2 -2
  80. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  81. data/lib/graphql/subscriptions/event.rb +1 -1
  82. data/lib/graphql/subscriptions.rb +3 -3
  83. data/lib/graphql/testing/helpers.rb +8 -5
  84. data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
  85. data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
  86. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  87. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  88. data/lib/graphql/version.rb +1 -1
  89. data/lib/graphql.rb +3 -0
  90. metadata +31 -13
  91. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  92. data/lib/graphql/analysis/ast/field_usage.rb +0 -84
  93. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  94. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  95. data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
  96. data/lib/graphql/analysis/ast/visitor.rb +0 -284
  97. data/lib/graphql/analysis/ast.rb +0 -94
  98. data/lib/graphql/language/token.rb +0 -34
  99. data/lib/graphql/schema/invalid_type_error.rb +0 -7
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.5
4
+ version: 2.3.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: fiber-storage
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: benchmark-ips
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -306,14 +320,13 @@ files:
306
320
  - lib/generators/graphql/union_generator.rb
307
321
  - lib/graphql.rb
308
322
  - lib/graphql/analysis.rb
309
- - lib/graphql/analysis/ast.rb
310
- - lib/graphql/analysis/ast/analyzer.rb
311
- - lib/graphql/analysis/ast/field_usage.rb
312
- - lib/graphql/analysis/ast/max_query_complexity.rb
313
- - lib/graphql/analysis/ast/max_query_depth.rb
314
- - lib/graphql/analysis/ast/query_complexity.rb
315
- - lib/graphql/analysis/ast/query_depth.rb
316
- - lib/graphql/analysis/ast/visitor.rb
323
+ - lib/graphql/analysis/analyzer.rb
324
+ - lib/graphql/analysis/field_usage.rb
325
+ - lib/graphql/analysis/max_query_complexity.rb
326
+ - lib/graphql/analysis/max_query_depth.rb
327
+ - lib/graphql/analysis/query_complexity.rb
328
+ - lib/graphql/analysis/query_depth.rb
329
+ - lib/graphql/analysis/visitor.rb
317
330
  - lib/graphql/analysis_error.rb
318
331
  - lib/graphql/backtrace.rb
319
332
  - lib/graphql/backtrace/inspect_result.rb
@@ -322,6 +335,7 @@ files:
322
335
  - lib/graphql/backtrace/traced_error.rb
323
336
  - lib/graphql/backtrace/tracer.rb
324
337
  - lib/graphql/coercion_error.rb
338
+ - lib/graphql/current.rb
325
339
  - lib/graphql/dataloader.rb
326
340
  - lib/graphql/dataloader/async_dataloader.rb
327
341
  - lib/graphql/dataloader/null_dataloader.rb
@@ -377,7 +391,6 @@ files:
377
391
  - lib/graphql/language/printer.rb
378
392
  - lib/graphql/language/sanitized_printer.rb
379
393
  - lib/graphql/language/static_visitor.rb
380
- - lib/graphql/language/token.rb
381
394
  - lib/graphql/language/visitor.rb
382
395
  - lib/graphql/load_application_object_failed_error.rb
383
396
  - lib/graphql/name_validator.rb
@@ -409,6 +422,8 @@ files:
409
422
  - lib/graphql/rubocop/graphql/base_cop.rb
410
423
  - lib/graphql/rubocop/graphql/default_null_true.rb
411
424
  - lib/graphql/rubocop/graphql/default_required_true.rb
425
+ - lib/graphql/rubocop/graphql/field_type_in_block.rb
426
+ - lib/graphql/rubocop/graphql/root_types_in_block.rb
412
427
  - lib/graphql/runtime_type_error.rb
413
428
  - lib/graphql/schema.rb
414
429
  - lib/graphql/schema/addition.rb
@@ -440,7 +455,6 @@ files:
440
455
  - lib/graphql/schema/input_object.rb
441
456
  - lib/graphql/schema/interface.rb
442
457
  - lib/graphql/schema/introspection_system.rb
443
- - lib/graphql/schema/invalid_type_error.rb
444
458
  - lib/graphql/schema/late_bound_type.rb
445
459
  - lib/graphql/schema/list.rb
446
460
  - lib/graphql/schema/loader.rb
@@ -471,12 +485,15 @@ files:
471
485
  - lib/graphql/schema/resolver/has_payload_type.rb
472
486
  - lib/graphql/schema/scalar.rb
473
487
  - lib/graphql/schema/subscription.rb
488
+ - lib/graphql/schema/subset.rb
474
489
  - lib/graphql/schema/timeout.rb
475
490
  - lib/graphql/schema/type_expression.rb
476
491
  - lib/graphql/schema/type_membership.rb
492
+ - lib/graphql/schema/types_migration.rb
477
493
  - lib/graphql/schema/union.rb
478
494
  - lib/graphql/schema/unique_within_type.rb
479
495
  - lib/graphql/schema/validator.rb
496
+ - lib/graphql/schema/validator/all_validator.rb
480
497
  - lib/graphql/schema/validator/allow_blank_validator.rb
481
498
  - lib/graphql/schema/validator/allow_null_validator.rb
482
499
  - lib/graphql/schema/validator/exclusion_validator.rb
@@ -615,6 +632,7 @@ files:
615
632
  - lib/graphql/types/relay/page_info.rb
616
633
  - lib/graphql/types/relay/page_info_behaviors.rb
617
634
  - lib/graphql/types/string.rb
635
+ - lib/graphql/unauthorized_enum_value_error.rb
618
636
  - lib/graphql/unauthorized_error.rb
619
637
  - lib/graphql/unauthorized_field_error.rb
620
638
  - lib/graphql/unresolved_type_error.rb
@@ -645,7 +663,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
645
663
  - !ruby/object:Gem::Version
646
664
  version: '0'
647
665
  requirements: []
648
- rubygems_version: 3.5.3
666
+ rubygems_version: 3.3.7
649
667
  signing_key:
650
668
  specification_version: 4
651
669
  summary: A GraphQL language and runtime for Ruby
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Query analyzer for query ASTs. Query analyzers respond to visitor style methods
6
- # but are prefixed by `enter` and `leave`.
7
- #
8
- # When an analyzer is initialized with a Multiplex, you can always get the current query from
9
- # `visitor.query` in the visit methods.
10
- #
11
- # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze
12
- class Analyzer
13
- def initialize(subject)
14
- @subject = subject
15
-
16
- if subject.is_a?(GraphQL::Query)
17
- @query = subject
18
- @multiplex = nil
19
- else
20
- @multiplex = subject
21
- @query = nil
22
- end
23
- end
24
-
25
- # Analyzer hook to decide at analysis time whether a query should
26
- # be analyzed or not.
27
- # @return [Boolean] If the query should be analyzed or not
28
- def analyze?
29
- true
30
- end
31
-
32
- # Analyzer hook to decide at analysis time whether analysis
33
- # requires a visitor pass; can be disabled for precomputed results.
34
- # @return [Boolean] If analysis requires visitation or not
35
- def visit?
36
- true
37
- end
38
-
39
- # The result for this analyzer. Returning {GraphQL::AnalysisError} results
40
- # in a query error.
41
- # @return [Any] The analyzer result
42
- def result
43
- raise GraphQL::RequiredImplementationMissingError
44
- end
45
-
46
- class << self
47
- private
48
-
49
- def build_visitor_hooks(member_name)
50
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
51
- def on_enter_#{member_name}(node, parent, visitor)
52
- end
53
-
54
- def on_leave_#{member_name}(node, parent, visitor)
55
- end
56
- EOS
57
- end
58
- end
59
-
60
- build_visitor_hooks :argument
61
- build_visitor_hooks :directive
62
- build_visitor_hooks :document
63
- build_visitor_hooks :enum
64
- build_visitor_hooks :field
65
- build_visitor_hooks :fragment_spread
66
- build_visitor_hooks :inline_fragment
67
- build_visitor_hooks :input_object
68
- build_visitor_hooks :list_type
69
- build_visitor_hooks :non_null_type
70
- build_visitor_hooks :null_value
71
- build_visitor_hooks :operation_definition
72
- build_visitor_hooks :type_name
73
- build_visitor_hooks :variable_definition
74
- build_visitor_hooks :variable_identifier
75
- build_visitor_hooks :abstract_node
76
-
77
- protected
78
-
79
- # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
80
- attr_reader :subject
81
-
82
- # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex
83
- # (When this is `nil`, use `visitor.query` inside visit methods to get the current query)
84
- attr_reader :query
85
-
86
- # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query
87
- attr_reader :multiplex
88
- end
89
- end
90
- end
91
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- class FieldUsage < Analyzer
6
- def initialize(query)
7
- super
8
- @used_fields = Set.new
9
- @used_deprecated_fields = Set.new
10
- @used_deprecated_arguments = Set.new
11
- @used_deprecated_enum_values = Set.new
12
- end
13
-
14
- def on_leave_field(node, parent, visitor)
15
- field_defn = visitor.field_definition
16
- field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
17
- @used_fields << field
18
- @used_deprecated_fields << field if field_defn.deprecation_reason
19
- arguments = visitor.query.arguments_for(node, field_defn)
20
- # If there was an error when preparing this argument object,
21
- # then this might be an error or something:
22
- if arguments.respond_to?(:argument_values)
23
- extract_deprecated_arguments(arguments.argument_values)
24
- end
25
- end
26
-
27
- def result
28
- {
29
- used_fields: @used_fields.to_a,
30
- used_deprecated_fields: @used_deprecated_fields.to_a,
31
- used_deprecated_arguments: @used_deprecated_arguments.to_a,
32
- used_deprecated_enum_values: @used_deprecated_enum_values.to_a,
33
- }
34
- end
35
-
36
- private
37
-
38
- def extract_deprecated_arguments(argument_values)
39
- argument_values.each_pair do |_argument_name, argument|
40
- if argument.definition.deprecation_reason
41
- @used_deprecated_arguments << argument.definition.path
42
- end
43
-
44
- arg_val = argument.value
45
-
46
- next if arg_val.nil?
47
-
48
- argument_type = argument.definition.type
49
- if argument_type.non_null?
50
- argument_type = argument_type.of_type
51
- end
52
-
53
- if argument_type.kind.input_object?
54
- extract_deprecated_arguments(argument.original_value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
55
- elsif argument_type.kind.enum?
56
- extract_deprecated_enum_value(argument_type, arg_val)
57
- elsif argument_type.list?
58
- inner_type = argument_type.unwrap
59
- case inner_type.kind
60
- when TypeKinds::INPUT_OBJECT
61
- argument.original_value.each do |value|
62
- extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
63
- end
64
- when TypeKinds::ENUM
65
- arg_val.each do |value|
66
- extract_deprecated_enum_value(inner_type, value)
67
- end
68
- else
69
- # Not a kind of input that we track
70
- end
71
- end
72
- end
73
- end
74
-
75
- def extract_deprecated_enum_value(enum_type, value)
76
- enum_value = @query.warden.enum_values(enum_type).find { |ev| ev.value == value }
77
- if enum_value&.deprecation_reason
78
- @used_deprecated_enum_values << enum_value.path
79
- end
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Used under the hood to implement complexity validation,
6
- # see {Schema#max_complexity} and {Query#max_complexity}
7
- class MaxQueryComplexity < QueryComplexity
8
- def result
9
- return if subject.max_complexity.nil?
10
-
11
- total_complexity = max_possible_complexity
12
-
13
- if total_complexity > subject.max_complexity
14
- GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}")
15
- else
16
- nil
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- class MaxQueryDepth < QueryDepth
6
- def result
7
- configured_max_depth = if query
8
- query.max_depth
9
- else
10
- multiplex.schema.max_depth
11
- end
12
-
13
- if configured_max_depth && @max_depth > configured_max_depth
14
- GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}")
15
- else
16
- nil
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,185 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- # Calculate the complexity of a query, using {Field#complexity} values.
5
- module AST
6
- class QueryComplexity < Analyzer
7
- # State for the query complexity calculation:
8
- # - `complexities_on_type` holds complexity scores for each type
9
- def initialize(query)
10
- super
11
- @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
12
- @complexities_on_type_by_query = {}
13
- end
14
-
15
- # Override this method to use the complexity result
16
- def result
17
- max_possible_complexity
18
- end
19
-
20
- # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
21
- # Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
22
- class ScopedTypeComplexity < Hash
23
- # A proc for defaulting empty namespace requests as a new scope hash.
24
- DEFAULT_PROC = ->(h, k) { h[k] = {} }
25
-
26
- attr_reader :field_definition, :response_path, :query
27
-
28
- # @param parent_type [Class] The owner of `field_definition`
29
- # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
30
- # @param query [GraphQL::Query] Used for `query.possible_types`
31
- # @param response_path [Array<String>] The path to the response key for the field
32
- # @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
33
- def initialize(parent_type, field_definition, query, response_path)
34
- super(&DEFAULT_PROC)
35
- @parent_type = parent_type
36
- @field_definition = field_definition
37
- @query = query
38
- @response_path = response_path
39
- @nodes = []
40
- end
41
-
42
- # @return [Array<GraphQL::Language::Nodes::Field>]
43
- attr_reader :nodes
44
-
45
- def own_complexity(child_complexity)
46
- @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
47
- end
48
- end
49
-
50
- def on_enter_field(node, parent, visitor)
51
- # We don't want to visit fragment definitions,
52
- # we'll visit them when we hit the spreads instead
53
- return if visitor.visiting_fragment_definition?
54
- return if visitor.skipping?
55
- return if @skip_introspection_fields && visitor.field_definition.introspection?
56
- parent_type = visitor.parent_type_definition
57
- field_key = node.alias || node.name
58
-
59
- # Find or create a complexity scope stack for this query.
60
- scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
61
-
62
- # Find or create the complexity costing node for this field.
63
- scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
64
- scope.nodes.push(node)
65
- scopes_stack.push(scope)
66
- end
67
-
68
- def on_leave_field(node, parent, visitor)
69
- # We don't want to visit fragment definitions,
70
- # we'll visit them when we hit the spreads instead
71
- return if visitor.visiting_fragment_definition?
72
- return if visitor.skipping?
73
- return if @skip_introspection_fields && visitor.field_definition.introspection?
74
- scopes_stack = @complexities_on_type_by_query[visitor.query]
75
- scopes_stack.pop
76
- end
77
-
78
- private
79
-
80
- # @return [Integer]
81
- def max_possible_complexity
82
- @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
83
- total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
84
- end
85
- end
86
-
87
- # @param query [GraphQL::Query] Used for `query.possible_types`
88
- # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
89
- # @return [Integer]
90
- def merged_max_complexity_for_scopes(query, scopes)
91
- # Aggregate a set of all possible scope types encountered (scope keys).
92
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
93
- possible_scope_types = scopes.each_with_object({}) do |scope, memo|
94
- memo.merge!(scope)
95
- end
96
-
97
- # Expand abstract scope types into their concrete implementations;
98
- # overlapping abstracts coalesce through their intersecting types.
99
- possible_scope_types.keys.each do |possible_scope_type|
100
- next unless possible_scope_type.kind.abstract?
101
-
102
- query.possible_types(possible_scope_type).each do |impl_type|
103
- possible_scope_types[impl_type] ||= true
104
- end
105
- possible_scope_types.delete(possible_scope_type)
106
- end
107
-
108
- # Aggregate the lexical selections that may apply to each possible type,
109
- # and then return the maximum cost among possible typed selections.
110
- possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
111
- # Collect inner selections from all scopes that intersect with this possible type.
112
- all_inner_selections = scopes.each_with_object([]) do |scope, memo|
113
- scope.each do |scope_type, inner_selections|
114
- memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
115
- end
116
- end
117
-
118
- # Find the maximum complexity for the scope type among possible lexical branches.
119
- complexity = merged_max_complexity(query, all_inner_selections)
120
- complexity > max ? complexity : max
121
- end
122
- end
123
-
124
- def types_intersect?(query, a, b)
125
- return true if a == b
126
-
127
- a_types = query.possible_types(a)
128
- query.possible_types(b).any? { |t| a_types.include?(t) }
129
- end
130
-
131
- # A hook which is called whenever a field's max complexity is calculated.
132
- # Override this method to capture individual field complexity details.
133
- #
134
- # @param scoped_type_complexity [ScopedTypeComplexity]
135
- # @param max_complexity [Numeric] Field's maximum complexity including child complexity
136
- # @param child_complexity [Numeric, nil] Field's child complexity
137
- def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
138
- end
139
-
140
- # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
141
- # @return [Integer] Total complexity value for all these selections in the parent scope
142
- def merged_max_complexity(query, inner_selections)
143
- # Aggregate a set of all unique field selection keys across all scopes.
144
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
145
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
146
- memo.merge!(inner_selection)
147
- end
148
-
149
- # Add up the total cost for each unique field name's coalesced selections
150
- unique_field_keys.each_key.reduce(0) do |total, field_key|
151
- composite_scopes = nil
152
- field_cost = 0
153
-
154
- # Collect composite selection scopes for further aggregation,
155
- # leaf selections report their costs directly.
156
- inner_selections.each do |inner_selection|
157
- child_scope = inner_selection[field_key]
158
- next unless child_scope
159
-
160
- # Empty child scopes are leaf nodes with zero child complexity.
161
- if child_scope.empty?
162
- field_cost = child_scope.own_complexity(0)
163
- field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
164
- else
165
- composite_scopes ||= []
166
- composite_scopes << child_scope
167
- end
168
- end
169
-
170
- if composite_scopes
171
- child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
172
-
173
- # This is the last composite scope visited; assume it's representative (for backwards compatibility).
174
- # Note: it would be more correct to score each composite scope and use the maximum possibility.
175
- field_cost = composite_scopes.last.own_complexity(child_complexity)
176
- field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
177
- end
178
-
179
- total + field_cost
180
- end
181
- end
182
- end
183
- end
184
- end
185
- end