graphql 2.0.13 → 2.3.10

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  4. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  5. data/lib/generators/graphql/install_generator.rb +3 -0
  6. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  7. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  8. data/lib/generators/graphql/relay.rb +18 -1
  9. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  10. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  11. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  12. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  13. data/lib/generators/graphql/templates/base_field.erb +2 -0
  14. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  16. data/lib/generators/graphql/templates/base_object.erb +2 -0
  17. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  18. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  19. data/lib/generators/graphql/templates/base_union.erb +2 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +8 -0
  26. data/lib/graphql/analysis/analyzer.rb +89 -0
  27. data/lib/graphql/analysis/field_usage.rb +82 -0
  28. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  29. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  30. data/lib/graphql/analysis/query_complexity.rb +183 -0
  31. data/lib/graphql/analysis/query_depth.rb +58 -0
  32. data/lib/graphql/analysis/visitor.rb +283 -0
  33. data/lib/graphql/analysis.rb +92 -1
  34. data/lib/graphql/backtrace/inspect_result.rb +0 -12
  35. data/lib/graphql/backtrace/table.rb +2 -2
  36. data/lib/graphql/backtrace/trace.rb +93 -0
  37. data/lib/graphql/backtrace/tracer.rb +1 -1
  38. data/lib/graphql/backtrace.rb +2 -1
  39. data/lib/graphql/coercion_error.rb +1 -9
  40. data/lib/graphql/dataloader/async_dataloader.rb +88 -0
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/request.rb +5 -0
  43. data/lib/graphql/dataloader/source.rb +89 -45
  44. data/lib/graphql/dataloader.rb +115 -142
  45. data/lib/graphql/duration_encoding_error.rb +16 -0
  46. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  47. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  48. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  49. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  50. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +331 -455
  52. data/lib/graphql/execution/interpreter.rb +125 -61
  53. data/lib/graphql/execution/lazy.rb +6 -12
  54. data/lib/graphql/execution/lookahead.rb +124 -46
  55. data/lib/graphql/execution/multiplex.rb +3 -117
  56. data/lib/graphql/execution.rb +0 -1
  57. data/lib/graphql/introspection/directive_type.rb +3 -3
  58. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  59. data/lib/graphql/introspection/entry_points.rb +11 -5
  60. data/lib/graphql/introspection/field_type.rb +2 -2
  61. data/lib/graphql/introspection/schema_type.rb +10 -13
  62. data/lib/graphql/introspection/type_type.rb +17 -10
  63. data/lib/graphql/introspection.rb +3 -2
  64. data/lib/graphql/language/block_string.rb +34 -18
  65. data/lib/graphql/language/definition_slice.rb +1 -1
  66. data/lib/graphql/language/document_from_schema_definition.rb +75 -59
  67. data/lib/graphql/language/lexer.rb +358 -1506
  68. data/lib/graphql/language/nodes.rb +166 -93
  69. data/lib/graphql/language/parser.rb +795 -1953
  70. data/lib/graphql/language/printer.rb +340 -160
  71. data/lib/graphql/language/sanitized_printer.rb +21 -23
  72. data/lib/graphql/language/static_visitor.rb +167 -0
  73. data/lib/graphql/language/visitor.rb +188 -141
  74. data/lib/graphql/language.rb +61 -1
  75. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  76. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  77. data/lib/graphql/pagination/array_connection.rb +6 -6
  78. data/lib/graphql/pagination/connection.rb +33 -6
  79. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  80. data/lib/graphql/query/context/scoped_context.rb +101 -0
  81. data/lib/graphql/query/context.rb +117 -112
  82. data/lib/graphql/query/null_context.rb +12 -25
  83. data/lib/graphql/query/validation_pipeline.rb +6 -5
  84. data/lib/graphql/query/variables.rb +3 -3
  85. data/lib/graphql/query.rb +86 -30
  86. data/lib/graphql/railtie.rb +9 -6
  87. data/lib/graphql/rake_task.rb +29 -11
  88. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +59 -23
  90. data/lib/graphql/schema/always_visible.rb +11 -0
  91. data/lib/graphql/schema/argument.rb +55 -26
  92. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  93. data/lib/graphql/schema/build_from_definition.rb +56 -32
  94. data/lib/graphql/schema/directive/one_of.rb +24 -0
  95. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  96. data/lib/graphql/schema/directive/transform.rb +1 -1
  97. data/lib/graphql/schema/directive.rb +15 -3
  98. data/lib/graphql/schema/enum.rb +35 -24
  99. data/lib/graphql/schema/enum_value.rb +2 -3
  100. data/lib/graphql/schema/field/connection_extension.rb +2 -16
  101. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  102. data/lib/graphql/schema/field.rb +147 -107
  103. data/lib/graphql/schema/field_extension.rb +1 -4
  104. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  105. data/lib/graphql/schema/has_single_input_argument.rb +158 -0
  106. data/lib/graphql/schema/input_object.rb +47 -11
  107. data/lib/graphql/schema/interface.rb +15 -21
  108. data/lib/graphql/schema/introspection_system.rb +7 -17
  109. data/lib/graphql/schema/late_bound_type.rb +10 -0
  110. data/lib/graphql/schema/list.rb +2 -2
  111. data/lib/graphql/schema/loader.rb +2 -3
  112. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -14
  113. data/lib/graphql/schema/member/build_type.rb +11 -3
  114. data/lib/graphql/schema/member/has_arguments.rb +170 -130
  115. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  116. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  117. data/lib/graphql/schema/member/has_directives.rb +81 -61
  118. data/lib/graphql/schema/member/has_fields.rb +100 -38
  119. data/lib/graphql/schema/member/has_interfaces.rb +65 -10
  120. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  121. data/lib/graphql/schema/member/has_validators.rb +32 -6
  122. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  123. data/lib/graphql/schema/member/scoped.rb +19 -0
  124. data/lib/graphql/schema/member/type_system_helpers.rb +16 -0
  125. data/lib/graphql/schema/member/validates_input.rb +3 -3
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +16 -5
  128. data/lib/graphql/schema/printer.rb +11 -8
  129. data/lib/graphql/schema/relay_classic_mutation.rb +7 -129
  130. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  131. data/lib/graphql/schema/resolver.rb +47 -32
  132. data/lib/graphql/schema/scalar.rb +3 -3
  133. data/lib/graphql/schema/subscription.rb +11 -4
  134. data/lib/graphql/schema/subset.rb +397 -0
  135. data/lib/graphql/schema/timeout.rb +25 -29
  136. data/lib/graphql/schema/type_expression.rb +2 -2
  137. data/lib/graphql/schema/type_membership.rb +3 -0
  138. data/lib/graphql/schema/union.rb +11 -2
  139. data/lib/graphql/schema/unique_within_type.rb +1 -1
  140. data/lib/graphql/schema/validator/all_validator.rb +60 -0
  141. data/lib/graphql/schema/validator.rb +4 -2
  142. data/lib/graphql/schema/warden.rb +238 -93
  143. data/lib/graphql/schema.rb +498 -103
  144. data/lib/graphql/static_validation/all_rules.rb +2 -1
  145. data/lib/graphql/static_validation/base_visitor.rb +7 -6
  146. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  147. data/lib/graphql/static_validation/literal_validator.rb +24 -7
  148. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  149. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  150. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
  151. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  152. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  153. data/lib/graphql/static_validation/rules/fields_will_merge.rb +10 -10
  154. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  155. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  156. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  157. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  158. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb +66 -0
  159. data/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb +29 -0
  160. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  161. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  162. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  163. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
  164. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  165. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  166. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  167. data/lib/graphql/static_validation/validation_context.rb +5 -5
  168. data/lib/graphql/static_validation/validator.rb +4 -1
  169. data/lib/graphql/static_validation.rb +0 -1
  170. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +11 -4
  171. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  172. data/lib/graphql/subscriptions/event.rb +11 -10
  173. data/lib/graphql/subscriptions/serialize.rb +2 -0
  174. data/lib/graphql/subscriptions.rb +20 -13
  175. data/lib/graphql/testing/helpers.rb +151 -0
  176. data/lib/graphql/testing.rb +2 -0
  177. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  178. data/lib/graphql/tracing/appoptics_trace.rb +251 -0
  179. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  180. data/lib/graphql/tracing/appsignal_trace.rb +77 -0
  181. data/lib/graphql/tracing/data_dog_trace.rb +183 -0
  182. data/lib/graphql/tracing/data_dog_tracing.rb +9 -21
  183. data/lib/graphql/{execution/instrumentation.rb → tracing/legacy_hooks_trace.rb} +10 -28
  184. data/lib/graphql/tracing/legacy_trace.rb +69 -0
  185. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  186. data/lib/graphql/tracing/notifications_trace.rb +45 -0
  187. data/lib/graphql/tracing/platform_trace.rb +118 -0
  188. data/lib/graphql/tracing/platform_tracing.rb +17 -3
  189. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +4 -2
  190. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  191. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  192. data/lib/graphql/tracing/scout_trace.rb +72 -0
  193. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  194. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  195. data/lib/graphql/tracing/trace.rb +76 -0
  196. data/lib/graphql/tracing.rb +20 -40
  197. data/lib/graphql/type_kinds.rb +7 -4
  198. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  199. data/lib/graphql/types/relay/base_connection.rb +1 -1
  200. data/lib/graphql/types/relay/connection_behaviors.rb +68 -6
  201. data/lib/graphql/types/relay/edge_behaviors.rb +33 -5
  202. data/lib/graphql/types/relay/node_behaviors.rb +8 -2
  203. data/lib/graphql/types/relay/page_info_behaviors.rb +11 -2
  204. data/lib/graphql/types/relay.rb +0 -1
  205. data/lib/graphql/types/string.rb +1 -1
  206. data/lib/graphql/types.rb +1 -0
  207. data/lib/graphql/version.rb +1 -1
  208. data/lib/graphql.rb +27 -20
  209. data/readme.md +13 -3
  210. metadata +96 -47
  211. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  212. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  213. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  214. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  215. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  216. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  217. data/lib/graphql/analysis/ast/visitor.rb +0 -269
  218. data/lib/graphql/analysis/ast.rb +0 -81
  219. data/lib/graphql/deprecation.rb +0 -9
  220. data/lib/graphql/filter.rb +0 -53
  221. data/lib/graphql/language/lexer.rl +0 -280
  222. data/lib/graphql/language/parser.y +0 -554
  223. data/lib/graphql/language/token.rb +0 -34
  224. data/lib/graphql/schema/base_64_bp.rb +0 -26
  225. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  226. data/lib/graphql/static_validation/type_stack.rb +0 -216
  227. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
  228. data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -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,230 +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
- @complexities_on_type_by_query = {}
12
- end
13
-
14
- # Overide this method to use the complexity result
15
- def result
16
- max_possible_complexity
17
- end
18
-
19
- class ScopedTypeComplexity
20
- # A single proc for {#scoped_children} hashes. Use this to avoid repeated allocations,
21
- # since the lexical binding isn't important.
22
- HASH_CHILDREN = ->(h, k) { h[k] = {} }
23
-
24
- attr_reader :field_definition, :response_path, :query
25
-
26
- # @param parent_type [Class] The owner of `field_definition`
27
- # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
28
- # @param query [GraphQL::Query] Used for `query.possible_types`
29
- # @param response_path [Array<String>] The path to the response key for the field
30
- def initialize(parent_type, field_definition, query, response_path)
31
- @parent_type = parent_type
32
- @field_definition = field_definition
33
- @query = query
34
- @response_path = response_path
35
- @scoped_children = nil
36
- @nodes = []
37
- end
38
-
39
- # @return [Array<GraphQL::Language::Nodes::Field>]
40
- attr_reader :nodes
41
-
42
- # Returns true if this field has no selections, ie, it's a scalar.
43
- # We need a quick way to check whether we should continue traversing.
44
- def terminal?
45
- @scoped_children.nil?
46
- end
47
-
48
- # This value is only calculated when asked for to avoid needless hash allocations.
49
- # Also, if it's never asked for, we determine that this scope complexity
50
- # is a scalar field ({#terminal?}).
51
- # @return [Hash<Hash<Class => ScopedTypeComplexity>]
52
- def scoped_children
53
- @scoped_children ||= Hash.new(&HASH_CHILDREN)
54
- end
55
-
56
- def own_complexity(child_complexity)
57
- @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
58
- end
59
- end
60
-
61
- def on_enter_field(node, parent, visitor)
62
- # We don't want to visit fragment definitions,
63
- # we'll visit them when we hit the spreads instead
64
- return if visitor.visiting_fragment_definition?
65
- return if visitor.skipping?
66
- parent_type = visitor.parent_type_definition
67
- field_key = node.alias || node.name
68
- # Find the complexity calculation for this field --
69
- # if we're re-entering a selection, we'll already have one.
70
- # Otherwise, make a new one and store it.
71
- #
72
- # `node` and `visitor.field_definition` may appear from a cache,
73
- # but I think that's ok. If the arguments _didn't_ match,
74
- # then the query would have been rejected as invalid.
75
- complexities_on_type = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
76
-
77
- complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
78
- complexity.nodes.push(node)
79
- # Push it on the stack.
80
- complexities_on_type.push(complexity)
81
- end
82
-
83
- def on_leave_field(node, parent, visitor)
84
- # We don't want to visit fragment definitions,
85
- # we'll visit them when we hit the spreads instead
86
- return if visitor.visiting_fragment_definition?
87
- return if visitor.skipping?
88
- complexities_on_type = @complexities_on_type_by_query[visitor.query]
89
- complexities_on_type.pop
90
- end
91
-
92
- private
93
-
94
- # @return [Integer]
95
- def max_possible_complexity
96
- @complexities_on_type_by_query.reduce(0) do |total, (query, complexities_on_type)|
97
- root_complexity = complexities_on_type.last
98
- # Use this entry point to calculate the total complexity
99
- total_complexity_for_query = merged_max_complexity_for_scopes(query, [root_complexity.scoped_children])
100
- total + total_complexity_for_query
101
- end
102
- end
103
-
104
- # @param query [GraphQL::Query] Used for `query.possible_types`
105
- # @param scoped_children_hashes [Array<Hash>] Array of scoped children hashes
106
- # @return [Integer]
107
- def merged_max_complexity_for_scopes(query, scoped_children_hashes)
108
- # Figure out what scopes are possible here.
109
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
110
- all_scopes = {}
111
- scoped_children_hashes.each do |h|
112
- all_scopes.merge!(h)
113
- end
114
-
115
- # If an abstract scope is present, but _all_ of its concrete types
116
- # are also in the list, remove it from the list of scopes to check,
117
- # because every possible type is covered by a concrete type.
118
- # (That is, there are no remainder types to check.)
119
- prev_keys = all_scopes.keys
120
- prev_keys.each do |scope|
121
- next unless scope.kind.abstract?
122
-
123
- missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) }
124
- # This concrete type is possible _only_ as a member of the abstract type.
125
- # So, attribute to it the complexity which belongs to the abstract type.
126
- missing_concrete_types.each do |concrete_scope|
127
- all_scopes[concrete_scope] = all_scopes[scope]
128
- end
129
- all_scopes.delete(scope)
130
- end
131
-
132
- # This will hold `{ type => int }` pairs, one for each possible branch
133
- complexity_by_scope = {}
134
-
135
- # For each scope,
136
- # find the lexical selections that might apply to it,
137
- # and gather them together into an array.
138
- # Then, treat the set of selection hashes
139
- # as a set and calculate the complexity for them as a unit
140
- all_scopes.each do |scope, _|
141
- # These will be the selections on `scope`
142
- children_for_scope = []
143
- scoped_children_hashes.each do |sc_h|
144
- sc_h.each do |inner_scope, children_hash|
145
- if applies_to?(query, scope, inner_scope)
146
- children_for_scope << children_hash
147
- end
148
- end
149
- end
150
-
151
- # Calculate the complexity for `scope`, merging all
152
- # possible lexical branches.
153
- complexity_value = merged_max_complexity(query, children_for_scope)
154
- complexity_by_scope[scope] = complexity_value
155
- end
156
-
157
- # Return the max complexity among all scopes
158
- complexity_by_scope.each_value.max
159
- end
160
-
161
- def applies_to?(query, left_scope, right_scope)
162
- if left_scope == right_scope
163
- # This can happen when several branches are being analyzed together
164
- true
165
- else
166
- # Check if these two scopes have _any_ types in common.
167
- possible_right_types = query.possible_types(right_scope)
168
- possible_left_types = query.possible_types(left_scope)
169
- !(possible_right_types & possible_left_types).empty?
170
- end
171
- end
172
-
173
- # A hook which is called whenever a field's max complexity is calculated.
174
- # Override this method to capture individual field complexity details.
175
- #
176
- # @param scoped_type_complexity [ScopedTypeComplexity]
177
- # @param max_complexity [Numeric] Field's maximum complexity including child complexity
178
- # @param child_complexity [Numeric, nil] Field's child complexity
179
- def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
180
- end
181
-
182
- # @param children_for_scope [Array<Hash>] An array of `scoped_children[scope]` hashes
183
- # (`{field_key => complexity}`)
184
- # @return [Integer] Complexity value for all these selections in the current scope
185
- def merged_max_complexity(query, children_for_scope)
186
- all_keys = []
187
- children_for_scope.each do |c|
188
- all_keys.concat(c.keys)
189
- end
190
- all_keys.uniq!
191
- complexity_for_keys = {}
192
-
193
- all_keys.each do |child_key|
194
- scoped_children_for_key = nil
195
- complexity_for_key = nil
196
- children_for_scope.each do |children_hash|
197
- next unless children_hash.key?(child_key)
198
-
199
- complexity_for_key = children_hash[child_key]
200
- if complexity_for_key.terminal?
201
- # Assume that all terminals would return the same complexity
202
- # Since it's a terminal, its child complexity is zero.
203
- complexity = complexity_for_key.own_complexity(0)
204
- complexity_for_keys[child_key] = complexity
205
-
206
- field_complexity(complexity_for_key, max_complexity: complexity, child_complexity: nil)
207
- else
208
- scoped_children_for_key ||= []
209
- scoped_children_for_key << complexity_for_key.scoped_children
210
- end
211
- end
212
-
213
- next unless scoped_children_for_key
214
-
215
- child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key)
216
- # This is the _last_ one we visited; assume it's representative.
217
- max_complexity = complexity_for_key.own_complexity(child_complexity)
218
-
219
- field_complexity(complexity_for_key, max_complexity: max_complexity, child_complexity: child_complexity)
220
-
221
- complexity_for_keys[child_key] = max_complexity
222
- end
223
-
224
- # Calculate the child complexity by summing the complexity of all selections
225
- complexity_for_keys.each_value.inject(0, &:+)
226
- end
227
- end
228
- end
229
- end
230
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- # A query reducer for measuring the depth of a given query.
5
- #
6
- # See https://graphql-ruby.org/queries/ast_analysis.html for more examples.
7
- #
8
- # @example Logging the depth of a query
9
- # class LogQueryDepth < GraphQL::Analysis::QueryDepth
10
- # def result
11
- # log("GraphQL query depth: #{@max_depth}")
12
- # end
13
- # end
14
- #
15
- # # In your Schema file:
16
- #
17
- # class MySchema < GraphQL::Schema
18
- # query_analyzer LogQueryDepth
19
- # end
20
- #
21
- # # When you run the query, the depth will get logged:
22
- #
23
- # Schema.execute(query_str)
24
- # # GraphQL query depth: 8
25
- #
26
- module AST
27
- class QueryDepth < Analyzer
28
- def initialize(query)
29
- @max_depth = 0
30
- @current_depth = 0
31
- super
32
- end
33
-
34
- def on_enter_field(node, parent, visitor)
35
- return if visitor.skipping? || visitor.visiting_fragment_definition?
36
-
37
- @current_depth += 1
38
- end
39
-
40
- def on_leave_field(node, parent, visitor)
41
- return if visitor.skipping? || visitor.visiting_fragment_definition?
42
-
43
- if @max_depth < @current_depth
44
- @max_depth = @current_depth
45
- end
46
- @current_depth -= 1
47
- end
48
-
49
- def result
50
- @max_depth
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,269 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Depth first traversal through a query AST, calling AST analyzers
6
- # along the way.
7
- #
8
- # The visitor is a special case of GraphQL::Language::Visitor, visiting
9
- # only the selected operation, providing helpers for common use cases such
10
- # as skipped fields and visiting fragment spreads.
11
- #
12
- # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries
13
- class Visitor < GraphQL::Language::Visitor
14
- def initialize(query:, analyzers:)
15
- @analyzers = analyzers
16
- @path = []
17
- @object_types = []
18
- @directives = []
19
- @field_definitions = []
20
- @argument_definitions = []
21
- @directive_definitions = []
22
- @rescued_errors = []
23
- @query = query
24
- @schema = query.schema
25
- @response_path = []
26
- @skip_stack = [false]
27
- super(query.selected_operation)
28
- end
29
-
30
- # @return [GraphQL::Query] the query being visited
31
- attr_reader :query
32
-
33
- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
34
- attr_reader :object_types
35
-
36
- # @return [Array<GraphQL::AnalysisError]
37
- attr_reader :rescued_errors
38
-
39
- def visit
40
- return unless @document
41
- super
42
- end
43
-
44
- # Visit Helpers
45
-
46
- # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
47
- # @see {GraphQL::Query#arguments_for}
48
- def arguments_for(ast_node, field_definition)
49
- @query.arguments_for(ast_node, field_definition)
50
- end
51
-
52
- # @return [Boolean] If the visitor is currently inside a fragment definition
53
- def visiting_fragment_definition?
54
- @in_fragment_def
55
- end
56
-
57
- # @return [Boolean] If the current node should be skipped because of a skip or include directive
58
- def skipping?
59
- @skipping
60
- end
61
-
62
- # @return [Array<String>] The path to the response key for the current field
63
- def response_path
64
- @response_path.dup
65
- end
66
-
67
- # Visitor Hooks
68
-
69
- def on_operation_definition(node, parent)
70
- object_type = @schema.root_type_for_operation(node.operation_type)
71
- @object_types.push(object_type)
72
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
73
- call_analyzers(:on_enter_operation_definition, node, parent)
74
- super
75
- call_analyzers(:on_leave_operation_definition, node, parent)
76
- @object_types.pop
77
- @path.pop
78
- end
79
-
80
- def on_fragment_definition(node, parent)
81
- on_fragment_with_type(node) do
82
- @path.push("fragment #{node.name}")
83
- @in_fragment_def = false
84
- call_analyzers(:on_enter_fragment_definition, node, parent)
85
- super
86
- @in_fragment_def = false
87
- call_analyzers(:on_leave_fragment_definition, node, parent)
88
- end
89
- end
90
-
91
- def on_inline_fragment(node, parent)
92
- on_fragment_with_type(node) do
93
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
94
- call_analyzers(:on_enter_inline_fragment, node, parent)
95
- super
96
- call_analyzers(:on_leave_inline_fragment, node, parent)
97
- end
98
- end
99
-
100
- def on_field(node, parent)
101
- @response_path.push(node.alias || node.name)
102
- parent_type = @object_types.last
103
- # This could be nil if the previous field wasn't found:
104
- field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
105
- @field_definitions.push(field_definition)
106
- if !field_definition.nil?
107
- next_object_type = field_definition.type.unwrap
108
- @object_types.push(next_object_type)
109
- else
110
- @object_types.push(nil)
111
- end
112
- @path.push(node.alias || node.name)
113
-
114
- @skipping = @skip_stack.last || skip?(node)
115
- @skip_stack << @skipping
116
-
117
- call_analyzers(:on_enter_field, node, parent)
118
- super
119
-
120
- @skipping = @skip_stack.pop
121
-
122
- call_analyzers(:on_leave_field, node, parent)
123
- @response_path.pop
124
- @field_definitions.pop
125
- @object_types.pop
126
- @path.pop
127
- end
128
-
129
- def on_directive(node, parent)
130
- directive_defn = @schema.directives[node.name]
131
- @directive_definitions.push(directive_defn)
132
- call_analyzers(:on_enter_directive, node, parent)
133
- super
134
- call_analyzers(:on_leave_directive, node, parent)
135
- @directive_definitions.pop
136
- end
137
-
138
- def on_argument(node, parent)
139
- argument_defn = if (arg = @argument_definitions.last)
140
- arg_type = arg.type.unwrap
141
- if arg_type.kind.input_object?
142
- arg_type.get_argument(node.name, @query.context)
143
- else
144
- nil
145
- end
146
- elsif (directive_defn = @directive_definitions.last)
147
- directive_defn.get_argument(node.name, @query.context)
148
- elsif (field_defn = @field_definitions.last)
149
- field_defn.get_argument(node.name, @query.context)
150
- else
151
- nil
152
- end
153
-
154
- @argument_definitions.push(argument_defn)
155
- @path.push(node.name)
156
- call_analyzers(:on_enter_argument, node, parent)
157
- super
158
- call_analyzers(:on_leave_argument, node, parent)
159
- @argument_definitions.pop
160
- @path.pop
161
- end
162
-
163
- def on_fragment_spread(node, parent)
164
- @path.push("... #{node.name}")
165
- call_analyzers(:on_enter_fragment_spread, node, parent)
166
- enter_fragment_spread_inline(node)
167
- super
168
- leave_fragment_spread_inline(node)
169
- call_analyzers(:on_leave_fragment_spread, node, parent)
170
- @path.pop
171
- end
172
-
173
- def on_abstract_node(node, parent)
174
- call_analyzers(:on_enter_abstract_node, node, parent)
175
- super
176
- call_analyzers(:on_leave_abstract_node, node, parent)
177
- end
178
-
179
- # @return [GraphQL::BaseType] The current object type
180
- def type_definition
181
- @object_types.last
182
- end
183
-
184
- # @return [GraphQL::BaseType] The type which the current type came from
185
- def parent_type_definition
186
- @object_types[-2]
187
- end
188
-
189
- # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
190
- def field_definition
191
- @field_definitions.last
192
- end
193
-
194
- # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
195
- def previous_field_definition
196
- @field_definitions[-2]
197
- end
198
-
199
- # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
200
- def directive_definition
201
- @directive_definitions.last
202
- end
203
-
204
- # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
205
- def argument_definition
206
- @argument_definitions.last
207
- end
208
-
209
- # @return [GraphQL::Argument, nil] The previous GraphQL argument
210
- def previous_argument_definition
211
- @argument_definitions[-2]
212
- end
213
-
214
- private
215
-
216
- # Visit a fragment spread inline instead of visiting the definition
217
- # by itself.
218
- def enter_fragment_spread_inline(fragment_spread)
219
- fragment_def = query.fragments[fragment_spread.name]
220
-
221
- object_type = if fragment_def.type
222
- @query.warden.get_type(fragment_def.type.name)
223
- else
224
- object_types.last
225
- end
226
-
227
- object_types << object_type
228
-
229
- fragment_def.selections.each do |selection|
230
- visit_node(selection, fragment_def)
231
- end
232
- end
233
-
234
- # Visit a fragment spread inline instead of visiting the definition
235
- # by itself.
236
- def leave_fragment_spread_inline(_fragment_spread)
237
- object_types.pop
238
- end
239
-
240
- def skip?(ast_node)
241
- dir = ast_node.directives
242
- dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
243
- end
244
-
245
- def call_analyzers(method, node, parent)
246
- @analyzers.each do |analyzer|
247
- begin
248
- analyzer.public_send(method, node, parent, self)
249
- rescue AnalysisError => err
250
- @rescued_errors << err
251
- end
252
- end
253
- end
254
-
255
- def on_fragment_with_type(node)
256
- object_type = if node.type
257
- @query.warden.get_type(node.type.name)
258
- else
259
- @object_types.last
260
- end
261
- @object_types.push(object_type)
262
- yield(node)
263
- @object_types.pop
264
- @path.pop
265
- end
266
- end
267
- end
268
- end
269
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
- require "graphql/analysis/ast/visitor"
3
- require "graphql/analysis/ast/analyzer"
4
- require "graphql/analysis/ast/field_usage"
5
- require "graphql/analysis/ast/query_complexity"
6
- require "graphql/analysis/ast/max_query_complexity"
7
- require "graphql/analysis/ast/query_depth"
8
- require "graphql/analysis/ast/max_query_depth"
9
-
10
- module GraphQL
11
- module Analysis
12
- module AST
13
- module_function
14
- # Analyze a multiplex, and all queries within.
15
- # Multiplex analyzers are ran for all queries, keeping state.
16
- # Query analyzers are ran per query, without carrying state between queries.
17
- #
18
- # @param multiplex [GraphQL::Execution::Multiplex]
19
- # @param analyzers [Array<GraphQL::Analysis::AST::Analyzer>]
20
- # @return [Array<Any>] Results from multiplex analyzers
21
- def analyze_multiplex(multiplex, analyzers)
22
- multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
23
-
24
- multiplex.trace("analyze_multiplex", { multiplex: multiplex }) do
25
- query_results = multiplex.queries.map do |query|
26
- if query.valid?
27
- analyze_query(
28
- query,
29
- query.analyzers,
30
- multiplex_analyzers: multiplex_analyzers
31
- )
32
- else
33
- []
34
- end
35
- end
36
-
37
- multiplex_results = multiplex_analyzers.map(&:result)
38
- multiplex_errors = analysis_errors(multiplex_results)
39
-
40
- multiplex.queries.each_with_index do |query, idx|
41
- query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
42
- end
43
- multiplex_results
44
- end
45
- end
46
-
47
- # @param query [GraphQL::Query]
48
- # @param analyzers [Array<GraphQL::Analysis::AST::Analyzer>]
49
- # @return [Array<Any>] Results from those analyzers
50
- def analyze_query(query, analyzers, multiplex_analyzers: [])
51
- query.trace("analyze_query", { query: query }) do
52
- query_analyzers = analyzers
53
- .map { |analyzer| analyzer.new(query) }
54
- .select { |analyzer| analyzer.analyze? }
55
-
56
- analyzers_to_run = query_analyzers + multiplex_analyzers
57
- if analyzers_to_run.any?
58
- visitor = GraphQL::Analysis::AST::Visitor.new(
59
- query: query,
60
- analyzers: analyzers_to_run
61
- )
62
-
63
- visitor.visit
64
-
65
- if visitor.rescued_errors.any?
66
- visitor.rescued_errors
67
- else
68
- query_analyzers.map(&:result)
69
- end
70
- else
71
- []
72
- end
73
- end
74
- end
75
-
76
- def analysis_errors(results)
77
- results.flatten.select { |r| r.is_a?(GraphQL::AnalysisError) }
78
- end
79
- end
80
- end
81
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module Deprecation
5
- def self.warn(message)
6
- Kernel.warn(message)
7
- end
8
- end
9
- end