graphql 2.4.5 → 2.5.21

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis/analyzer.rb +2 -1
  5. data/lib/graphql/analysis/query_complexity.rb +87 -7
  6. data/lib/graphql/analysis/visitor.rb +37 -40
  7. data/lib/graphql/analysis.rb +12 -9
  8. data/lib/graphql/autoload.rb +1 -0
  9. data/lib/graphql/backtrace/table.rb +118 -55
  10. data/lib/graphql/backtrace.rb +1 -19
  11. data/lib/graphql/current.rb +6 -1
  12. data/lib/graphql/dashboard/application_controller.rb +41 -0
  13. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  14. data/lib/graphql/dashboard/installable.rb +22 -0
  15. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  16. data/lib/graphql/dashboard/limiters.rb +93 -0
  17. data/lib/graphql/dashboard/operation_store.rb +199 -0
  18. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  19. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  20. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  21. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  22. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  23. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  24. data/lib/graphql/dashboard/statics/icon.png +0 -0
  25. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  26. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  42. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  43. data/lib/graphql/dashboard.rb +96 -0
  44. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  45. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  46. data/lib/graphql/dataloader/async_dataloader.rb +38 -15
  47. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  48. data/lib/graphql/dataloader/source.rb +18 -6
  49. data/lib/graphql/dataloader.rb +110 -26
  50. data/lib/graphql/date_encoding_error.rb +1 -1
  51. data/lib/graphql/dig.rb +2 -1
  52. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  53. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
  54. data/lib/graphql/execution/interpreter/runtime.rb +229 -93
  55. data/lib/graphql/execution/interpreter.rb +15 -24
  56. data/lib/graphql/execution/multiplex.rb +7 -6
  57. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  58. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  59. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  60. data/lib/graphql/execution/next/runner.rb +389 -0
  61. data/lib/graphql/execution/next/selections_step.rb +37 -0
  62. data/lib/graphql/execution/next.rb +69 -0
  63. data/lib/graphql/execution.rb +1 -0
  64. data/lib/graphql/execution_error.rb +13 -10
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +7 -3
  67. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  68. data/lib/graphql/introspection/entry_points.rb +11 -3
  69. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  70. data/lib/graphql/introspection/field_type.rb +13 -5
  71. data/lib/graphql/introspection/input_value_type.rb +21 -13
  72. data/lib/graphql/introspection/type_type.rb +64 -28
  73. data/lib/graphql/invalid_name_error.rb +1 -1
  74. data/lib/graphql/invalid_null_error.rb +25 -16
  75. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  76. data/lib/graphql/language/lexer.rb +16 -5
  77. data/lib/graphql/language/nodes.rb +8 -1
  78. data/lib/graphql/language/parser.rb +16 -8
  79. data/lib/graphql/language/static_visitor.rb +37 -33
  80. data/lib/graphql/language/visitor.rb +59 -55
  81. data/lib/graphql/language.rb +21 -12
  82. data/lib/graphql/pagination/connection.rb +2 -0
  83. data/lib/graphql/pagination/connections.rb +32 -0
  84. data/lib/graphql/query/context.rb +6 -10
  85. data/lib/graphql/query/null_context.rb +9 -3
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query.rb +64 -64
  88. data/lib/graphql/railtie.rb +1 -1
  89. data/lib/graphql/schema/addition.rb +3 -1
  90. data/lib/graphql/schema/always_visible.rb +1 -0
  91. data/lib/graphql/schema/argument.rb +24 -8
  92. data/lib/graphql/schema/build_from_definition.rb +113 -54
  93. data/lib/graphql/schema/directive/flagged.rb +2 -0
  94. data/lib/graphql/schema/directive.rb +52 -2
  95. data/lib/graphql/schema/enum.rb +36 -1
  96. data/lib/graphql/schema/enum_value.rb +1 -1
  97. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  98. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  99. data/lib/graphql/schema/field.rb +101 -51
  100. data/lib/graphql/schema/field_extension.rb +33 -0
  101. data/lib/graphql/schema/input_object.rb +45 -38
  102. data/lib/graphql/schema/interface.rb +2 -1
  103. data/lib/graphql/schema/list.rb +1 -1
  104. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  105. data/lib/graphql/schema/member/has_arguments.rb +56 -19
  106. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  107. data/lib/graphql/schema/member/has_dataloader.rb +79 -0
  108. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  109. data/lib/graphql/schema/member/has_directives.rb +1 -1
  110. data/lib/graphql/schema/member/has_fields.rb +81 -5
  111. data/lib/graphql/schema/member/has_interfaces.rb +3 -3
  112. data/lib/graphql/schema/member/scoped.rb +1 -1
  113. data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
  114. data/lib/graphql/schema/member.rb +6 -0
  115. data/lib/graphql/schema/object.rb +18 -8
  116. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  117. data/lib/graphql/schema/resolver.rb +52 -6
  118. data/lib/graphql/schema/scalar.rb +1 -6
  119. data/lib/graphql/schema/subscription.rb +50 -4
  120. data/lib/graphql/schema/timeout.rb +19 -2
  121. data/lib/graphql/schema/validator/required_validator.rb +71 -14
  122. data/lib/graphql/schema/visibility/migration.rb +3 -2
  123. data/lib/graphql/schema/visibility/profile.rb +115 -23
  124. data/lib/graphql/schema/visibility.rb +49 -32
  125. data/lib/graphql/schema/warden.rb +23 -2
  126. data/lib/graphql/schema.rb +333 -68
  127. data/lib/graphql/static_validation/all_rules.rb +2 -2
  128. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  129. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  130. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  131. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  132. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  133. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  134. data/lib/graphql/static_validation/validator.rb +6 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  136. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  137. data/lib/graphql/subscriptions/event.rb +12 -1
  138. data/lib/graphql/subscriptions/serialize.rb +1 -1
  139. data/lib/graphql/subscriptions.rb +1 -1
  140. data/lib/graphql/testing/helpers.rb +17 -11
  141. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  142. data/lib/graphql/testing.rb +1 -0
  143. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  144. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  145. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  146. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  147. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  148. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  149. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  150. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  151. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  152. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  153. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  154. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  155. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  156. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  157. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  158. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  159. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  160. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  161. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  162. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  163. data/lib/graphql/tracing/null_trace.rb +9 -0
  164. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  165. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  166. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  167. data/lib/graphql/tracing/platform_trace.rb +5 -0
  168. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  169. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  170. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  171. data/lib/graphql/tracing/scout_trace.rb +32 -55
  172. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  173. data/lib/graphql/tracing/sentry_trace.rb +64 -94
  174. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  175. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  176. data/lib/graphql/tracing/trace.rb +111 -1
  177. data/lib/graphql/tracing.rb +31 -30
  178. data/lib/graphql/type_kinds.rb +1 -0
  179. data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
  180. data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
  181. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  182. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  183. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  184. data/lib/graphql/unauthorized_error.rb +5 -1
  185. data/lib/graphql/version.rb +1 -1
  186. data/lib/graphql.rb +12 -31
  187. metadata +174 -11
  188. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  189. data/lib/graphql/backtrace/trace.rb +0 -93
  190. data/lib/graphql/backtrace/tracer.rb +0 -80
  191. data/lib/graphql/schema/null_mask.rb +0 -11
  192. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -22,39 +22,6 @@ module GraphQL
22
22
  end
23
23
  end
24
24
 
25
- # We don't use `alias` here because it breaks `super`
26
- def self.make_visit_methods(ast_node_class)
27
- node_method = ast_node_class.visit_method
28
- children_of_type = ast_node_class.children_of_type
29
- child_visit_method = :"#{node_method}_children"
30
-
31
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
32
- # The default implementation for visiting an AST node.
33
- # It doesn't _do_ anything, but it continues to visiting the node's children.
34
- # To customize this hook, override one of its make_visit_methods (or the base method?)
35
- # in your subclasses.
36
- #
37
- # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
38
- # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
39
- # @return [void]
40
- def #{node_method}(node, parent)
41
- #{
42
- if method_defined?(child_visit_method)
43
- "#{child_visit_method}(node)"
44
- elsif children_of_type
45
- children_of_type.map do |child_accessor, child_class|
46
- "node.#{child_accessor}.each do |child_node|
47
- #{child_class.visit_method}(child_node, node)
48
- end"
49
- end.join("\n")
50
- else
51
- ""
52
- end
53
- }
54
- end
55
- RUBY
56
- end
57
-
58
25
  def on_document_children(document_node)
59
26
  document_node.children.each do |child_node|
60
27
  visit_method = child_node.visit_method
@@ -123,6 +90,41 @@ module GraphQL
123
90
  end
124
91
  end
125
92
 
93
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
94
+
95
+ # We don't use `alias` here because it breaks `super`
96
+ def self.make_visit_methods(ast_node_class)
97
+ node_method = ast_node_class.visit_method
98
+ children_of_type = ast_node_class.children_of_type
99
+ child_visit_method = :"#{node_method}_children"
100
+
101
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
102
+ # The default implementation for visiting an AST node.
103
+ # It doesn't _do_ anything, but it continues to visiting the node's children.
104
+ # To customize this hook, override one of its make_visit_methods (or the base method?)
105
+ # in your subclasses.
106
+ #
107
+ # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
108
+ # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
109
+ # @return [void]
110
+ def #{node_method}(node, parent)
111
+ #{
112
+ if method_defined?(child_visit_method)
113
+ "#{child_visit_method}(node)"
114
+ elsif children_of_type
115
+ children_of_type.map do |child_accessor, child_class|
116
+ "node.#{child_accessor}.each do |child_node|
117
+ #{child_class.visit_method}(child_node, node)
118
+ end"
119
+ end.join("\n")
120
+ else
121
+ ""
122
+ end
123
+ }
124
+ end
125
+ RUBY
126
+ end
127
+
126
128
  [
127
129
  Language::Nodes::Argument,
128
130
  Language::Nodes::Directive,
@@ -162,6 +164,8 @@ module GraphQL
162
164
  ].each do |ast_node_class|
163
165
  make_visit_methods(ast_node_class)
164
166
  end
167
+
168
+ # rubocop:disable Development/NoEvalCop
165
169
  end
166
170
  end
167
171
  end
@@ -61,61 +61,6 @@ module GraphQL
61
61
  end
62
62
  end
63
63
 
64
- # We don't use `alias` here because it breaks `super`
65
- def self.make_visit_methods(ast_node_class)
66
- node_method = ast_node_class.visit_method
67
- children_of_type = ast_node_class.children_of_type
68
- child_visit_method = :"#{node_method}_children"
69
-
70
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
71
- # The default implementation for visiting an AST node.
72
- # It doesn't _do_ anything, but it continues to visiting the node's children.
73
- # To customize this hook, override one of its make_visit_methods (or the base method?)
74
- # in your subclasses.
75
- #
76
- # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
77
- # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
78
- # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
79
- def #{node_method}(node, parent)
80
- if node.equal?(DELETE_NODE)
81
- # This might be passed to `super(DELETE_NODE, ...)`
82
- # by a user hook, don't want to keep visiting in that case.
83
- [node, parent]
84
- else
85
- new_node = node
86
- #{
87
- if method_defined?(child_visit_method)
88
- "new_node = #{child_visit_method}(new_node)"
89
- elsif children_of_type
90
- children_of_type.map do |child_accessor, child_class|
91
- "node.#{child_accessor}.each do |child_node|
92
- new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
93
- # Reassign `node` in case the child hook makes a modification
94
- if new_child_and_node.is_a?(Array)
95
- new_node = new_child_and_node[1]
96
- end
97
- end"
98
- end.join("\n")
99
- else
100
- ""
101
- end
102
- }
103
-
104
- if new_node.equal?(node)
105
- [node, parent]
106
- else
107
- [new_node, parent]
108
- end
109
- end
110
- end
111
-
112
- def #{node_method}_with_modifications(node, parent)
113
- new_node_and_new_parent = #{node_method}(node, parent)
114
- apply_modifications(node, parent, new_node_and_new_parent)
115
- end
116
- RUBY
117
- end
118
-
119
64
  def on_document_children(document_node)
120
65
  new_node = document_node
121
66
  document_node.children.each do |child_node|
@@ -216,6 +161,63 @@ module GraphQL
216
161
  new_node
217
162
  end
218
163
 
164
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
165
+
166
+ # We don't use `alias` here because it breaks `super`
167
+ def self.make_visit_methods(ast_node_class)
168
+ node_method = ast_node_class.visit_method
169
+ children_of_type = ast_node_class.children_of_type
170
+ child_visit_method = :"#{node_method}_children"
171
+
172
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
173
+ # The default implementation for visiting an AST node.
174
+ # It doesn't _do_ anything, but it continues to visiting the node's children.
175
+ # To customize this hook, override one of its make_visit_methods (or the base method?)
176
+ # in your subclasses.
177
+ #
178
+ # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
179
+ # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
180
+ # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
181
+ def #{node_method}(node, parent)
182
+ if node.equal?(DELETE_NODE)
183
+ # This might be passed to `super(DELETE_NODE, ...)`
184
+ # by a user hook, don't want to keep visiting in that case.
185
+ [node, parent]
186
+ else
187
+ new_node = node
188
+ #{
189
+ if method_defined?(child_visit_method)
190
+ "new_node = #{child_visit_method}(new_node)"
191
+ elsif children_of_type
192
+ children_of_type.map do |child_accessor, child_class|
193
+ "node.#{child_accessor}.each do |child_node|
194
+ new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
195
+ # Reassign `node` in case the child hook makes a modification
196
+ if new_child_and_node.is_a?(Array)
197
+ new_node = new_child_and_node[1]
198
+ end
199
+ end"
200
+ end.join("\n")
201
+ else
202
+ ""
203
+ end
204
+ }
205
+
206
+ if new_node.equal?(node)
207
+ [node, parent]
208
+ else
209
+ [new_node, parent]
210
+ end
211
+ end
212
+ end
213
+
214
+ def #{node_method}_with_modifications(node, parent)
215
+ new_node_and_new_parent = #{node_method}(node, parent)
216
+ apply_modifications(node, parent, new_node_and_new_parent)
217
+ end
218
+ RUBY
219
+ end
220
+
219
221
  [
220
222
  Language::Nodes::Argument,
221
223
  Language::Nodes::Directive,
@@ -256,6 +258,8 @@ module GraphQL
256
258
  make_visit_methods(ast_node_class)
257
259
  end
258
260
 
261
+ # rubocop:enable Development/NoEvalCop
262
+
259
263
  private
260
264
 
261
265
  def apply_modifications(node, parent, new_node_and_new_parent)
@@ -77,21 +77,30 @@ module GraphQL
77
77
  new_query_str || query_str
78
78
  end
79
79
 
80
+ LEADING_REGEX = Regexp.union(" ", *Lexer::Punctuation.constants.map { |const| Lexer::Punctuation.const_get(const) })
81
+
82
+ # Optimized pattern using:
83
+ # - Possessive quantifiers (*+, ++) to prevent backtracking in number patterns
84
+ # - Atomic group (?>...) for IGNORE to prevent backtracking
85
+ # - Single unified number pattern instead of three alternatives
86
+ EFFICIENT_NUMBER_REGEXP = /-?(?:0|[1-9][0-9]*+)(?:\.[0-9]++)?(?:[eE][+-]?[0-9]++)?/
87
+ EFFICIENT_IGNORE_REGEXP = /(?>[, \r\n\t]+|\#[^\n]*$)*/
88
+
89
+ MAYBE_INVALID_NUMBER = /\d[_a-zA-Z]/
90
+
80
91
  INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
81
- (
82
- ((?<num>#{Lexer::INT_REGEXP}(#{Lexer::FLOAT_EXP_REGEXP})?)(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
83
- |
84
- ((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP}#{Lexer::FLOAT_EXP_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
85
- |
86
- ((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
87
- )}x
92
+ (?<leading>#{LEADING_REGEX})
93
+ (?<num>#{EFFICIENT_NUMBER_REGEXP})
94
+ (?<name>#{Lexer::IDENTIFIER_REGEXP})
95
+ #{EFFICIENT_IGNORE_REGEXP}
96
+ :
97
+ }x
88
98
 
89
99
  def self.add_space_between_numbers_and_names(query_str)
90
- if query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
91
- query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<num> \\k<name>:")
92
- else
93
- query_str
94
- end
100
+ # Fast check for digit followed by identifier char. If this doesn't match, skip the more expensive regexp entirely.
101
+ return query_str unless query_str.match?(MAYBE_INVALID_NUMBER)
102
+ return query_str unless query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
103
+ query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<leading>\\k<num> \\k<name>:")
95
104
  end
96
105
  end
97
106
  end
@@ -94,6 +94,8 @@ module GraphQL
94
94
  @was_authorized_by_scope_items = detect_was_authorized_by_scope_items
95
95
  end
96
96
 
97
+ attr_writer :was_authorized_by_scope_items
98
+
97
99
  def was_authorized_by_scope_items?
98
100
  @was_authorized_by_scope_items
99
101
  end
@@ -83,6 +83,38 @@ module GraphQL
83
83
  end
84
84
  end
85
85
 
86
+ def populate_connection(field, object, value, original_arguments, context)
87
+ if value.is_a? GraphQL::ExecutionError
88
+ # This isn't even going to work because context doesn't have ast_node anymore
89
+ context.add_error(value)
90
+ nil
91
+ elsif value.nil?
92
+ nil
93
+ elsif value.is_a?(GraphQL::Pagination::Connection)
94
+ # update the connection with some things that may not have been provided
95
+ value.context ||= context
96
+ value.parent ||= object
97
+ value.first_value ||= original_arguments[:first]
98
+ value.after_value ||= original_arguments[:after]
99
+ value.last_value ||= original_arguments[:last]
100
+ value.before_value ||= original_arguments[:before]
101
+ value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method
102
+ value.field ||= field
103
+ if field.has_max_page_size? && !value.has_max_page_size_override?
104
+ value.max_page_size = field.max_page_size
105
+ end
106
+ if field.has_default_page_size? && !value.has_default_page_size_override?
107
+ value.default_page_size = field.default_page_size
108
+ end
109
+ if (custom_t = context.schema.connections.edge_class_for_field(field))
110
+ value.edge_class = custom_t
111
+ end
112
+ value
113
+ else
114
+ context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
115
+ context.schema.connections.wrap(field, object, value, original_arguments, context)
116
+ end
117
+ end
86
118
  # use an override if there is one
87
119
  # @api private
88
120
  def edge_class_for_field(field)
@@ -29,6 +29,7 @@ module GraphQL
29
29
  end
30
30
 
31
31
  extend Forwardable
32
+ include Schema::Member::HasDataloader
32
33
 
33
34
  # @return [Array<GraphQL::ExecutionError>] errors returned during execution
34
35
  attr_reader :errors
@@ -39,9 +40,6 @@ module GraphQL
39
40
  # @return [GraphQL::Schema]
40
41
  attr_reader :schema
41
42
 
42
- # @return [Array<String, Integer>] The current position in the result
43
- attr_reader :path
44
-
45
43
  # Make a new context which delegates key lookup to `values`
46
44
  # @param query [GraphQL::Query] the query who owns this context
47
45
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
@@ -53,12 +51,10 @@ module GraphQL
53
51
  @storage = Hash.new { |h, k| h[k] = {} }
54
52
  @storage[nil] = @provided_values
55
53
  @errors = []
56
- @path = []
57
- @value = nil
58
- @context = self # for SharedMethods TODO delete sharedmethods
59
54
  @scoped_context = ScopedContext.new(self)
60
55
  end
61
56
 
57
+ # Modify this hash to return extensions to client.
62
58
  # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }`
63
59
  def response_extensions
64
60
  namespace(:__query_result_extensions__)
@@ -89,7 +85,7 @@ module GraphQL
89
85
 
90
86
  attr_writer :types
91
87
 
92
- RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
88
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
93
89
  # @!method []=(key, value)
94
90
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
95
91
 
@@ -123,8 +119,8 @@ module GraphQL
123
119
  # @param error [GraphQL::ExecutionError] an execution error
124
120
  # @return [void]
125
121
  def add_error(error)
126
- if !error.is_a?(ExecutionError)
127
- raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
122
+ if !error.is_a?(GraphQL::RuntimeError)
123
+ raise TypeError, "expected error to be a GraphQL::RuntimeError, but was #{error.class}"
128
124
  end
129
125
  errors << error
130
126
  nil
@@ -244,7 +240,7 @@ module GraphQL
244
240
  end
245
241
 
246
242
  def inspect
247
- "#<Query::Context ...>"
243
+ "#<#{self.class} ...>"
248
244
  end
249
245
 
250
246
  def scoped_merge!(hash)
@@ -4,7 +4,13 @@ module GraphQL
4
4
  class Query
5
5
  # This object can be `ctx` in places where there is no query
6
6
  class NullContext < Context
7
- include Singleton
7
+ def self.instance
8
+ @instance ||= self.new
9
+ end
10
+
11
+ def self.instance=(new_inst)
12
+ @instance = new_inst
13
+ end
8
14
 
9
15
  class NullQuery
10
16
  def after_lazy(value)
@@ -20,10 +26,10 @@ module GraphQL
20
26
  attr_reader :schema, :query, :warden, :dataloader
21
27
  def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
22
28
 
23
- def initialize
29
+ def initialize(schema: NullSchema)
24
30
  @query = NullQuery.new
25
31
  @dataloader = GraphQL::Dataloader::NullDataloader.new
26
- @schema = NullSchema
32
+ @schema = schema
27
33
  @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
28
34
  @types = @warden.visibility_profile
29
35
  freeze
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ # This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string.
5
+ #
6
+ # It depends on a "parent" {Query}.
7
+ #
8
+ # During execution, it calls query-related tracing hooks but passes itself as `query:`.
9
+ #
10
+ # The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for
11
+ # `object` in some cases.
12
+ #
13
+ # @see Query#run_partials Run via {Query#run_partials}
14
+ class Partial
15
+ include Query::Runnable
16
+
17
+ # @param path [Array<String, Integer>] A path in `query.query_string` to start executing from
18
+ # @param object [Object] A starting object for execution
19
+ # @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared.
20
+ # @param context [Hash] Extra context values to merge into `query.context`, if provided
21
+ # @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition]
22
+ def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil)
23
+ @path = path
24
+ @object = object
25
+ @query = query
26
+ @schema = query.schema
27
+ context_vals = @query.context.to_h
28
+ if context
29
+ context_vals = context_vals.merge(context)
30
+ end
31
+ @context = GraphQL::Query::Context.new(query: self, schema: @query.schema, values: context_vals)
32
+ @multiplex = nil
33
+ @result_values = nil
34
+ @result = nil
35
+
36
+ if fragment_node
37
+ @ast_nodes = [fragment_node]
38
+ @root_type = type || raise(ArgumentError, "Pass `type:` when using `node:`")
39
+ # This is only used when `@leaf`
40
+ @field_definition = nil
41
+ elsif path.nil?
42
+ raise ArgumentError, "`path:` is required if `node:` is not given; add `path:`"
43
+ else
44
+ set_type_info_from_path
45
+ end
46
+
47
+ @leaf = @root_type.unwrap.kind.leaf?
48
+ end
49
+
50
+ def leaf?
51
+ @leaf
52
+ end
53
+
54
+ attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
55
+
56
+ attr_accessor :multiplex, :result_values
57
+
58
+ class Result < GraphQL::Query::Result
59
+ def path
60
+ @query.path
61
+ end
62
+
63
+ # @return [GraphQL::Query::Partial]
64
+ def partial
65
+ @query
66
+ end
67
+ end
68
+
69
+ def result
70
+ @result ||= Result.new(query: self, values: result_values)
71
+ end
72
+
73
+ def current_trace
74
+ @query.current_trace
75
+ end
76
+
77
+ def types
78
+ @query.types
79
+ end
80
+
81
+ def resolve_type(...)
82
+ @query.resolve_type(...)
83
+ end
84
+
85
+ def variables
86
+ @query.variables
87
+ end
88
+
89
+ def fragments
90
+ @query.fragments
91
+ end
92
+
93
+ def valid?
94
+ @query.valid?
95
+ end
96
+
97
+ def analyzers
98
+ EmptyObjects::EMPTY_ARRAY
99
+ end
100
+
101
+ def analysis_errors=(_ignored)
102
+ # pass
103
+ end
104
+
105
+ def subscription?
106
+ @query.subscription?
107
+ end
108
+
109
+ def selected_operation
110
+ ast_nodes.first
111
+ end
112
+
113
+ def static_errors
114
+ @query.static_errors
115
+ end
116
+
117
+ def selected_operation_name
118
+ @query.selected_operation_name
119
+ end
120
+
121
+ private
122
+
123
+ def set_type_info_from_path
124
+ selections = [@query.selected_operation]
125
+ type = @query.root_type
126
+ parent_type = nil
127
+ field_defn = nil
128
+
129
+ @path.each do |name_in_doc|
130
+ if name_in_doc.is_a?(Integer)
131
+ if type.list?
132
+ type = type.unwrap
133
+ next
134
+ else
135
+ raise ArgumentError, "Received path with index `#{name_in_doc}`, but type wasn't a list. Type: #{type.to_type_signature}, path: #{@path}"
136
+ end
137
+ end
138
+
139
+ next_selections = []
140
+ selections.each do |selection|
141
+ selections_to_check = []
142
+ selections_to_check.concat(selection.selections)
143
+ while (sel = selections_to_check.shift)
144
+ case sel
145
+ when GraphQL::Language::Nodes::InlineFragment
146
+ selections_to_check.concat(sel.selections)
147
+ when GraphQL::Language::Nodes::FragmentSpread
148
+ fragment = @query.fragments[sel.name]
149
+ selections_to_check.concat(fragment.selections)
150
+ when GraphQL::Language::Nodes::Field
151
+ if sel.alias == name_in_doc || sel.name == name_in_doc
152
+ next_selections << sel
153
+ end
154
+ else
155
+ raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
156
+ end
157
+ end
158
+ end
159
+
160
+ if next_selections.empty?
161
+ raise ArgumentError, "Path `#{@path.inspect}` is not present in this query. `#{name_in_doc.inspect}` was not found. Try a different path or rewrite the query to include it."
162
+ end
163
+ field_name = next_selections.first.name
164
+ field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
165
+ parent_type = type
166
+ type = field_defn.type
167
+ if type.non_null?
168
+ type = type.of_type
169
+ end
170
+ selections = next_selections
171
+ end
172
+
173
+ @ast_nodes = selections
174
+ @root_type = type
175
+ @field_definition = field_defn
176
+ end
177
+ end
178
+ end
179
+ end