graphql 2.2.17 → 2.5.16

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.
Files changed (240) 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_generator.rb +46 -0
  4. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  5. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  6. data/lib/generators/graphql/templates/schema.erb +3 -0
  7. data/lib/generators/graphql/type_generator.rb +1 -1
  8. data/lib/graphql/analysis/analyzer.rb +90 -0
  9. data/lib/graphql/analysis/field_usage.rb +82 -0
  10. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  11. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  12. data/lib/graphql/analysis/query_complexity.rb +263 -0
  13. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  14. data/lib/graphql/analysis/visitor.rb +280 -0
  15. data/lib/graphql/analysis.rb +95 -1
  16. data/lib/graphql/autoload.rb +38 -0
  17. data/lib/graphql/backtrace/table.rb +118 -55
  18. data/lib/graphql/backtrace.rb +1 -19
  19. data/lib/graphql/current.rb +57 -0
  20. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  21. data/lib/graphql/dashboard/installable.rb +22 -0
  22. data/lib/graphql/dashboard/limiters.rb +93 -0
  23. data/lib/graphql/dashboard/operation_store.rb +199 -0
  24. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  25. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  26. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  27. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  28. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  29. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  30. data/lib/graphql/dashboard/statics/icon.png +0 -0
  31. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  42. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  43. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  44. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  45. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  46. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  47. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  48. data/lib/graphql/dashboard.rb +158 -0
  49. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  50. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  51. data/lib/graphql/dataloader/async_dataloader.rb +46 -19
  52. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  53. data/lib/graphql/dataloader/source.rb +20 -9
  54. data/lib/graphql/dataloader.rb +153 -45
  55. data/lib/graphql/date_encoding_error.rb +1 -1
  56. data/lib/graphql/dig.rb +2 -1
  57. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  58. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  59. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  60. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
  61. data/lib/graphql/execution/interpreter/runtime.rb +321 -222
  62. data/lib/graphql/execution/interpreter.rb +23 -30
  63. data/lib/graphql/execution/lookahead.rb +18 -11
  64. data/lib/graphql/execution/multiplex.rb +6 -5
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +1 -1
  67. data/lib/graphql/introspection/entry_points.rb +2 -2
  68. data/lib/graphql/introspection/field_type.rb +1 -1
  69. data/lib/graphql/introspection/schema_type.rb +6 -11
  70. data/lib/graphql/introspection/type_type.rb +5 -5
  71. data/lib/graphql/invalid_name_error.rb +1 -1
  72. data/lib/graphql/invalid_null_error.rb +20 -17
  73. data/lib/graphql/language/cache.rb +13 -0
  74. data/lib/graphql/language/comment.rb +18 -0
  75. data/lib/graphql/language/document_from_schema_definition.rb +64 -35
  76. data/lib/graphql/language/lexer.rb +72 -42
  77. data/lib/graphql/language/nodes.rb +93 -52
  78. data/lib/graphql/language/parser.rb +168 -61
  79. data/lib/graphql/language/printer.rb +31 -15
  80. data/lib/graphql/language/sanitized_printer.rb +1 -1
  81. data/lib/graphql/language.rb +61 -1
  82. data/lib/graphql/pagination/connection.rb +1 -1
  83. data/lib/graphql/query/context/scoped_context.rb +1 -1
  84. data/lib/graphql/query/context.rb +46 -47
  85. data/lib/graphql/query/null_context.rb +3 -5
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query/validation_pipeline.rb +2 -2
  88. data/lib/graphql/query/variable_validation_error.rb +1 -1
  89. data/lib/graphql/query.rb +123 -69
  90. data/lib/graphql/railtie.rb +7 -0
  91. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  92. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  93. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  94. data/lib/graphql/rubocop.rb +2 -0
  95. data/lib/graphql/schema/addition.rb +26 -13
  96. data/lib/graphql/schema/always_visible.rb +7 -2
  97. data/lib/graphql/schema/argument.rb +57 -8
  98. data/lib/graphql/schema/build_from_definition.rb +116 -49
  99. data/lib/graphql/schema/directive/flagged.rb +4 -2
  100. data/lib/graphql/schema/directive.rb +54 -2
  101. data/lib/graphql/schema/enum.rb +107 -24
  102. data/lib/graphql/schema/enum_value.rb +10 -2
  103. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  104. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  105. data/lib/graphql/schema/field.rb +134 -45
  106. data/lib/graphql/schema/field_extension.rb +1 -1
  107. data/lib/graphql/schema/has_single_input_argument.rb +6 -2
  108. data/lib/graphql/schema/input_object.rb +122 -64
  109. data/lib/graphql/schema/interface.rb +23 -5
  110. data/lib/graphql/schema/introspection_system.rb +6 -17
  111. data/lib/graphql/schema/late_bound_type.rb +4 -0
  112. data/lib/graphql/schema/list.rb +3 -3
  113. data/lib/graphql/schema/loader.rb +3 -2
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  115. data/lib/graphql/schema/member/has_arguments.rb +44 -58
  116. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  118. data/lib/graphql/schema/member/has_directives.rb +4 -4
  119. data/lib/graphql/schema/member/has_fields.rb +26 -6
  120. data/lib/graphql/schema/member/has_interfaces.rb +6 -6
  121. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  122. data/lib/graphql/schema/member/has_validators.rb +1 -1
  123. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  124. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  125. data/lib/graphql/schema/member.rb +1 -0
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +25 -8
  128. data/lib/graphql/schema/printer.rb +1 -0
  129. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  130. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  131. data/lib/graphql/schema/resolver.rb +29 -23
  132. data/lib/graphql/schema/scalar.rb +1 -6
  133. data/lib/graphql/schema/subscription.rb +52 -6
  134. data/lib/graphql/schema/timeout.rb +19 -2
  135. data/lib/graphql/schema/type_expression.rb +2 -2
  136. data/lib/graphql/schema/union.rb +1 -1
  137. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  139. data/lib/graphql/schema/validator.rb +3 -1
  140. data/lib/graphql/schema/visibility/migration.rb +188 -0
  141. data/lib/graphql/schema/visibility/profile.rb +445 -0
  142. data/lib/graphql/schema/visibility/visit.rb +190 -0
  143. data/lib/graphql/schema/visibility.rb +311 -0
  144. data/lib/graphql/schema/warden.rb +190 -20
  145. data/lib/graphql/schema.rb +695 -167
  146. data/lib/graphql/static_validation/all_rules.rb +2 -2
  147. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  148. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  149. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  150. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  151. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  152. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  153. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  154. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  155. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  156. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  157. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  158. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  159. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  160. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  161. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  162. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  163. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  164. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  165. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  166. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  167. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  168. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  169. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  170. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  171. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  172. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  173. data/lib/graphql/static_validation/validation_context.rb +18 -2
  174. data/lib/graphql/static_validation/validator.rb +6 -1
  175. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
  176. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  177. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  178. data/lib/graphql/subscriptions/event.rb +13 -2
  179. data/lib/graphql/subscriptions/serialize.rb +1 -1
  180. data/lib/graphql/subscriptions.rb +7 -5
  181. data/lib/graphql/testing/helpers.rb +48 -16
  182. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  183. data/lib/graphql/testing.rb +1 -0
  184. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  185. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  186. data/lib/graphql/tracing/appoptics_trace.rb +5 -1
  187. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  188. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  189. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  190. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  191. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  192. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  193. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  194. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  195. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  196. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  197. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  198. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  199. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  200. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  201. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  202. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  203. data/lib/graphql/tracing/null_trace.rb +9 -0
  204. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  205. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  206. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  207. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  208. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  209. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  210. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  211. data/lib/graphql/tracing/scout_trace.rb +32 -58
  212. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  213. data/lib/graphql/tracing/sentry_trace.rb +64 -98
  214. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  215. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  216. data/lib/graphql/tracing/trace.rb +111 -1
  217. data/lib/graphql/tracing.rb +31 -30
  218. data/lib/graphql/type_kinds.rb +2 -1
  219. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  220. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  221. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  222. data/lib/graphql/types.rb +18 -11
  223. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  224. data/lib/graphql/version.rb +1 -1
  225. data/lib/graphql.rb +64 -54
  226. metadata +197 -22
  227. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  228. data/lib/graphql/analysis/ast/field_usage.rb +0 -82
  229. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  230. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  231. data/lib/graphql/analysis/ast/query_complexity.rb +0 -182
  232. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  233. data/lib/graphql/analysis/ast.rb +0 -94
  234. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  235. data/lib/graphql/backtrace/trace.rb +0 -93
  236. data/lib/graphql/backtrace/tracer.rb +0 -80
  237. data/lib/graphql/language/token.rb +0 -34
  238. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  239. data/lib/graphql/schema/null_mask.rb +0 -11
  240. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -1,182 +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
- # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
20
- # Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
21
- class ScopedTypeComplexity < Hash
22
- # A proc for defaulting empty namespace requests as a new scope hash.
23
- DEFAULT_PROC = ->(h, k) { h[k] = {} }
24
-
25
- attr_reader :field_definition, :response_path, :query
26
-
27
- # @param parent_type [Class] The owner of `field_definition`
28
- # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
29
- # @param query [GraphQL::Query] Used for `query.possible_types`
30
- # @param response_path [Array<String>] The path to the response key for the field
31
- # @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
32
- def initialize(parent_type, field_definition, query, response_path)
33
- super(&DEFAULT_PROC)
34
- @parent_type = parent_type
35
- @field_definition = field_definition
36
- @query = query
37
- @response_path = response_path
38
- @nodes = []
39
- end
40
-
41
- # @return [Array<GraphQL::Language::Nodes::Field>]
42
- attr_reader :nodes
43
-
44
- def own_complexity(child_complexity)
45
- @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
46
- end
47
- end
48
-
49
- def on_enter_field(node, parent, visitor)
50
- # We don't want to visit fragment definitions,
51
- # we'll visit them when we hit the spreads instead
52
- return if visitor.visiting_fragment_definition?
53
- return if visitor.skipping?
54
- parent_type = visitor.parent_type_definition
55
- field_key = node.alias || node.name
56
-
57
- # Find or create a complexity scope stack for this query.
58
- scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
59
-
60
- # Find or create the complexity costing node for this field.
61
- scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
62
- scope.nodes.push(node)
63
- scopes_stack.push(scope)
64
- end
65
-
66
- def on_leave_field(node, parent, visitor)
67
- # We don't want to visit fragment definitions,
68
- # we'll visit them when we hit the spreads instead
69
- return if visitor.visiting_fragment_definition?
70
- return if visitor.skipping?
71
- scopes_stack = @complexities_on_type_by_query[visitor.query]
72
- scopes_stack.pop
73
- end
74
-
75
- private
76
-
77
- # @return [Integer]
78
- def max_possible_complexity
79
- @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
80
- total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
81
- end
82
- end
83
-
84
- # @param query [GraphQL::Query] Used for `query.possible_types`
85
- # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
86
- # @return [Integer]
87
- def merged_max_complexity_for_scopes(query, scopes)
88
- # Aggregate a set of all possible scope types encountered (scope keys).
89
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
90
- possible_scope_types = scopes.each_with_object({}) do |scope, memo|
91
- memo.merge!(scope)
92
- end
93
-
94
- # Expand abstract scope types into their concrete implementations;
95
- # overlapping abstracts coalesce through their intersecting types.
96
- possible_scope_types.keys.each do |possible_scope_type|
97
- next unless possible_scope_type.kind.abstract?
98
-
99
- query.possible_types(possible_scope_type).each do |impl_type|
100
- possible_scope_types[impl_type] ||= true
101
- end
102
- possible_scope_types.delete(possible_scope_type)
103
- end
104
-
105
- # Aggregate the lexical selections that may apply to each possible type,
106
- # and then return the maximum cost among possible typed selections.
107
- possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
108
- # Collect inner selections from all scopes that intersect with this possible type.
109
- all_inner_selections = scopes.each_with_object([]) do |scope, memo|
110
- scope.each do |scope_type, inner_selections|
111
- memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
112
- end
113
- end
114
-
115
- # Find the maximum complexity for the scope type among possible lexical branches.
116
- complexity = merged_max_complexity(query, all_inner_selections)
117
- complexity > max ? complexity : max
118
- end
119
- end
120
-
121
- def types_intersect?(query, a, b)
122
- return true if a == b
123
-
124
- a_types = query.possible_types(a)
125
- query.possible_types(b).any? { |t| a_types.include?(t) }
126
- end
127
-
128
- # A hook which is called whenever a field's max complexity is calculated.
129
- # Override this method to capture individual field complexity details.
130
- #
131
- # @param scoped_type_complexity [ScopedTypeComplexity]
132
- # @param max_complexity [Numeric] Field's maximum complexity including child complexity
133
- # @param child_complexity [Numeric, nil] Field's child complexity
134
- def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
135
- end
136
-
137
- # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
138
- # @return [Integer] Total complexity value for all these selections in the parent scope
139
- def merged_max_complexity(query, inner_selections)
140
- # Aggregate a set of all unique field selection keys across all scopes.
141
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
142
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
143
- memo.merge!(inner_selection)
144
- end
145
-
146
- # Add up the total cost for each unique field name's coalesced selections
147
- unique_field_keys.each_key.reduce(0) do |total, field_key|
148
- composite_scopes = nil
149
- field_cost = 0
150
-
151
- # Collect composite selection scopes for further aggregation,
152
- # leaf selections report their costs directly.
153
- inner_selections.each do |inner_selection|
154
- child_scope = inner_selection[field_key]
155
- next unless child_scope
156
-
157
- # Empty child scopes are leaf nodes with zero child complexity.
158
- if child_scope.empty?
159
- field_cost = child_scope.own_complexity(0)
160
- field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
161
- else
162
- composite_scopes ||= []
163
- composite_scopes << child_scope
164
- end
165
- end
166
-
167
- if composite_scopes
168
- child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
169
-
170
- # This is the last composite scope visited; assume it's representative (for backwards compatibility).
171
- # Note: it would be more correct to score each composite scope and use the maximum possibility.
172
- field_cost = composite_scopes.last.own_complexity(child_complexity)
173
- field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
174
- end
175
-
176
- total + field_cost
177
- end
178
- end
179
- end
180
- end
181
- end
182
- end
@@ -1,276 +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::StaticVisitor, 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::StaticVisitor
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
- :operation_definition, :fragment_definition,
70
- :inline_fragment, :field, :directive, :argument, :fragment_spread
71
- ].each do |node_type|
72
- module_eval <<-RUBY, __FILE__, __LINE__
73
- def call_on_enter_#{node_type}(node, parent)
74
- @analyzers.each do |a|
75
- begin
76
- a.on_enter_#{node_type}(node, parent, self)
77
- rescue AnalysisError => err
78
- @rescued_errors << err
79
- end
80
- end
81
- end
82
-
83
- def call_on_leave_#{node_type}(node, parent)
84
- @analyzers.each do |a|
85
- begin
86
- a.on_leave_#{node_type}(node, parent, self)
87
- rescue AnalysisError => err
88
- @rescued_errors << err
89
- end
90
- end
91
- end
92
-
93
- RUBY
94
- end
95
-
96
- def on_operation_definition(node, parent)
97
- object_type = @schema.root_type_for_operation(node.operation_type)
98
- @object_types.push(object_type)
99
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
100
- call_on_enter_operation_definition(node, parent)
101
- super
102
- call_on_leave_operation_definition(node, parent)
103
- @object_types.pop
104
- @path.pop
105
- end
106
-
107
- def on_fragment_definition(node, parent)
108
- on_fragment_with_type(node) do
109
- @path.push("fragment #{node.name}")
110
- @in_fragment_def = false
111
- call_on_enter_fragment_definition(node, parent)
112
- super
113
- @in_fragment_def = false
114
- call_on_leave_fragment_definition(node, parent)
115
- end
116
- end
117
-
118
- def on_inline_fragment(node, parent)
119
- on_fragment_with_type(node) do
120
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121
- call_on_enter_inline_fragment(node, parent)
122
- super
123
- call_on_leave_inline_fragment(node, parent)
124
- end
125
- end
126
-
127
- def on_field(node, parent)
128
- @response_path.push(node.alias || node.name)
129
- parent_type = @object_types.last
130
- # This could be nil if the previous field wasn't found:
131
- field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
132
- @field_definitions.push(field_definition)
133
- if !field_definition.nil?
134
- next_object_type = field_definition.type.unwrap
135
- @object_types.push(next_object_type)
136
- else
137
- @object_types.push(nil)
138
- end
139
- @path.push(node.alias || node.name)
140
-
141
- @skipping = @skip_stack.last || skip?(node)
142
- @skip_stack << @skipping
143
-
144
- call_on_enter_field(node, parent)
145
- super
146
- @skipping = @skip_stack.pop
147
- call_on_leave_field(node, parent)
148
- @response_path.pop
149
- @field_definitions.pop
150
- @object_types.pop
151
- @path.pop
152
- end
153
-
154
- def on_directive(node, parent)
155
- directive_defn = @schema.directives[node.name]
156
- @directive_definitions.push(directive_defn)
157
- call_on_enter_directive(node, parent)
158
- super
159
- call_on_leave_directive(node, parent)
160
- @directive_definitions.pop
161
- end
162
-
163
- def on_argument(node, parent)
164
- argument_defn = if (arg = @argument_definitions.last)
165
- arg_type = arg.type.unwrap
166
- if arg_type.kind.input_object?
167
- arg_type.get_argument(node.name, @query.context)
168
- else
169
- nil
170
- end
171
- elsif (directive_defn = @directive_definitions.last)
172
- directive_defn.get_argument(node.name, @query.context)
173
- elsif (field_defn = @field_definitions.last)
174
- field_defn.get_argument(node.name, @query.context)
175
- else
176
- nil
177
- end
178
-
179
- @argument_definitions.push(argument_defn)
180
- @path.push(node.name)
181
- call_on_enter_argument(node, parent)
182
- super
183
- call_on_leave_argument(node, parent)
184
- @argument_definitions.pop
185
- @path.pop
186
- end
187
-
188
- def on_fragment_spread(node, parent)
189
- @path.push("... #{node.name}")
190
- call_on_enter_fragment_spread(node, parent)
191
- enter_fragment_spread_inline(node)
192
- super
193
- leave_fragment_spread_inline(node)
194
- call_on_leave_fragment_spread(node, parent)
195
- @path.pop
196
- end
197
-
198
- # @return [GraphQL::BaseType] The current object type
199
- def type_definition
200
- @object_types.last
201
- end
202
-
203
- # @return [GraphQL::BaseType] The type which the current type came from
204
- def parent_type_definition
205
- @object_types[-2]
206
- end
207
-
208
- # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
209
- def field_definition
210
- @field_definitions.last
211
- end
212
-
213
- # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
214
- def previous_field_definition
215
- @field_definitions[-2]
216
- end
217
-
218
- # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
219
- def directive_definition
220
- @directive_definitions.last
221
- end
222
-
223
- # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
224
- def argument_definition
225
- @argument_definitions.last
226
- end
227
-
228
- # @return [GraphQL::Argument, nil] The previous GraphQL argument
229
- def previous_argument_definition
230
- @argument_definitions[-2]
231
- end
232
-
233
- private
234
-
235
- # Visit a fragment spread inline instead of visiting the definition
236
- # by itself.
237
- def enter_fragment_spread_inline(fragment_spread)
238
- fragment_def = query.fragments[fragment_spread.name]
239
-
240
- object_type = if fragment_def.type
241
- @query.warden.get_type(fragment_def.type.name)
242
- else
243
- object_types.last
244
- end
245
-
246
- object_types << object_type
247
-
248
- on_fragment_definition_children(fragment_def)
249
- end
250
-
251
- # Visit a fragment spread inline instead of visiting the definition
252
- # by itself.
253
- def leave_fragment_spread_inline(_fragment_spread)
254
- object_types.pop
255
- end
256
-
257
- def skip?(ast_node)
258
- dir = ast_node.directives
259
- dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
260
- end
261
-
262
- def on_fragment_with_type(node)
263
- object_type = if node.type
264
- @query.warden.get_type(node.type.name)
265
- else
266
- @object_types.last
267
- end
268
- @object_types.push(object_type)
269
- yield(node)
270
- @object_types.pop
271
- @path.pop
272
- end
273
- end
274
- end
275
- end
276
- end
@@ -1,94 +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
- require "timeout"
10
-
11
- module GraphQL
12
- module Analysis
13
- module AST
14
- module_function
15
- # Analyze a multiplex, and all queries within.
16
- # Multiplex analyzers are ran for all queries, keeping state.
17
- # Query analyzers are ran per query, without carrying state between queries.
18
- #
19
- # @param multiplex [GraphQL::Execution::Multiplex]
20
- # @param analyzers [Array<GraphQL::Analysis::AST::Analyzer>]
21
- # @return [Array<Any>] Results from multiplex analyzers
22
- def analyze_multiplex(multiplex, analyzers)
23
- multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
24
-
25
- multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
26
- query_results = multiplex.queries.map do |query|
27
- if query.valid?
28
- analyze_query(
29
- query,
30
- query.analyzers,
31
- multiplex_analyzers: multiplex_analyzers
32
- )
33
- else
34
- []
35
- end
36
- end
37
-
38
- multiplex_results = multiplex_analyzers.map(&:result)
39
- multiplex_errors = analysis_errors(multiplex_results)
40
-
41
- multiplex.queries.each_with_index do |query, idx|
42
- query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
43
- end
44
- multiplex_results
45
- end
46
- end
47
-
48
- # @param query [GraphQL::Query]
49
- # @param analyzers [Array<GraphQL::Analysis::AST::Analyzer>]
50
- # @return [Array<Any>] Results from those analyzers
51
- def analyze_query(query, analyzers, multiplex_analyzers: [])
52
- query.current_trace.analyze_query(query: query) do
53
- query_analyzers = analyzers
54
- .map { |analyzer| analyzer.new(query) }
55
- .tap { _1.select!(&:analyze?) }
56
-
57
- analyzers_to_run = query_analyzers + multiplex_analyzers
58
- if analyzers_to_run.any?
59
-
60
- analyzers_to_run.select!(&:visit?)
61
- if analyzers_to_run.any?
62
- visitor = GraphQL::Analysis::AST::Visitor.new(
63
- query: query,
64
- analyzers: analyzers_to_run
65
- )
66
-
67
- # `nil` or `0` causes no timeout
68
- Timeout::timeout(query.validate_timeout_remaining) do
69
- visitor.visit
70
- end
71
-
72
- if visitor.rescued_errors.any?
73
- return visitor.rescued_errors
74
- end
75
- end
76
-
77
- query_analyzers.map(&:result)
78
- else
79
- []
80
- end
81
- end
82
- rescue Timeout::Error
83
- [GraphQL::AnalysisError.new("Timeout on validation of query")]
84
- rescue GraphQL::UnauthorizedError
85
- # This error was raised during analysis and will be returned the client before execution
86
- []
87
- end
88
-
89
- def analysis_errors(results)
90
- results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
91
- end
92
- end
93
- end
94
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- class Backtrace
4
- module InspectResult
5
- module_function
6
-
7
- def inspect_result(obj)
8
- case obj
9
- when Hash
10
- "{" +
11
- obj.map do |key, val|
12
- "#{key}: #{inspect_truncated(val)}"
13
- end.join(", ") +
14
- "}"
15
- when Array
16
- "[" +
17
- obj.map { |v| inspect_truncated(v) }.join(", ") +
18
- "]"
19
- when Query::Context::SharedMethods
20
- if obj.invalid_null?
21
- "nil"
22
- else
23
- inspect_truncated(obj.value)
24
- end
25
- else
26
- inspect_truncated(obj)
27
- end
28
- end
29
-
30
- def inspect_truncated(obj)
31
- case obj
32
- when Hash
33
- "{...}"
34
- when Array
35
- "[...]"
36
- when Query::Context::SharedMethods
37
- if obj.invalid_null?
38
- "nil"
39
- else
40
- inspect_truncated(obj.value)
41
- end
42
- when GraphQL::Execution::Lazy
43
- "(unresolved)"
44
- else
45
- "#{obj.inspect}"
46
- end
47
- end
48
- end
49
- end
50
- end
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- class Backtrace
4
- module Trace
5
- def initialize(*args, **kwargs, &block)
6
- @__backtrace_contexts = {}
7
- @__backtrace_last_context = nil
8
- super
9
- end
10
-
11
- def validate(query:, validate:)
12
- if query.multiplex
13
- push_query_backtrace_context(query)
14
- end
15
- super
16
- end
17
-
18
- def analyze_query(query:)
19
- if query.multiplex # missing for stand-alone static validation
20
- push_query_backtrace_context(query)
21
- end
22
- super
23
- end
24
-
25
- def execute_query(query:)
26
- push_query_backtrace_context(query)
27
- super
28
- end
29
-
30
- def execute_query_lazy(query:, multiplex:)
31
- query ||= multiplex.queries.first
32
- push_query_backtrace_context(query)
33
- super
34
- end
35
-
36
- def execute_field(field:, query:, ast_node:, arguments:, object:)
37
- push_field_backtrace_context(field, query, ast_node, arguments, object)
38
- super
39
- end
40
-
41
- def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
42
- push_field_backtrace_context(field, query, ast_node, arguments, object)
43
- super
44
- end
45
-
46
- def execute_multiplex(multiplex:)
47
- super
48
- rescue StandardError => err
49
- # This is an unhandled error from execution,
50
- # Re-raise it with a GraphQL trace.
51
- potential_context = @__backtrace_last_context
52
- if potential_context.is_a?(GraphQL::Query::Context) ||
53
- potential_context.is_a?(Backtrace::Frame)
54
- raise TracedError.new(err, potential_context)
55
- else
56
- raise
57
- end
58
- end
59
-
60
- private
61
-
62
- def push_query_backtrace_context(query)
63
- push_data = query
64
- push_key = []
65
- @__backtrace_contexts[push_key] = push_data
66
- @__backtrace_last_context = push_data
67
- end
68
-
69
- def push_field_backtrace_context(field, query, ast_node, arguments, object)
70
- push_key = query.context[:current_path]
71
- push_storage = @__backtrace_contexts
72
- parent_frame = push_storage[push_key[0..-2]]
73
-
74
- if parent_frame.is_a?(GraphQL::Query)
75
- parent_frame = parent_frame.context
76
- end
77
-
78
- push_data = Frame.new(
79
- query: query,
80
- path: push_key,
81
- ast_node: ast_node,
82
- field: field,
83
- object: object,
84
- arguments: arguments,
85
- parent_frame: parent_frame,
86
- )
87
- push_storage[push_key] = push_data
88
- @__backtrace_last_context = push_data
89
- end
90
-
91
- end
92
- end
93
- end