graphql 2.0.14 → 2.0.32

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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/mutation_delete_generator.rb +1 -1
  3. data/lib/generators/graphql/mutation_update_generator.rb +1 -1
  4. data/lib/generators/graphql/relay.rb +18 -1
  5. data/lib/graphql/analysis/ast/visitor.rb +42 -35
  6. data/lib/graphql/analysis/ast.rb +2 -2
  7. data/lib/graphql/backtrace/table.rb +2 -2
  8. data/lib/graphql/backtrace/trace.rb +96 -0
  9. data/lib/graphql/backtrace/tracer.rb +1 -1
  10. data/lib/graphql/backtrace.rb +2 -1
  11. data/lib/graphql/dataloader/source.rb +69 -45
  12. data/lib/graphql/dataloader.rb +8 -5
  13. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  14. data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
  15. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  16. data/lib/graphql/execution/interpreter/runtime.rb +355 -268
  17. data/lib/graphql/execution/interpreter.rb +19 -15
  18. data/lib/graphql/execution/lazy.rb +6 -12
  19. data/lib/graphql/execution/lookahead.rb +16 -5
  20. data/lib/graphql/execution/multiplex.rb +2 -1
  21. data/lib/graphql/filter.rb +8 -2
  22. data/lib/graphql/introspection/directive_type.rb +2 -2
  23. data/lib/graphql/introspection/entry_points.rb +1 -1
  24. data/lib/graphql/introspection/field_type.rb +1 -1
  25. data/lib/graphql/introspection/schema_type.rb +2 -2
  26. data/lib/graphql/introspection/type_type.rb +5 -5
  27. data/lib/graphql/introspection.rb +1 -1
  28. data/lib/graphql/language/document_from_schema_definition.rb +58 -35
  29. data/lib/graphql/language/lexer.rb +248 -1505
  30. data/lib/graphql/language/nodes.rb +69 -40
  31. data/lib/graphql/language/parser.rb +775 -742
  32. data/lib/graphql/language/parser.y +44 -38
  33. data/lib/graphql/language/printer.rb +48 -25
  34. data/lib/graphql/language/visitor.rb +192 -81
  35. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  36. data/lib/graphql/pagination/connection.rb +5 -5
  37. data/lib/graphql/query/context.rb +93 -27
  38. data/lib/graphql/query/null_context.rb +8 -18
  39. data/lib/graphql/query/validation_pipeline.rb +2 -1
  40. data/lib/graphql/query.rb +55 -13
  41. data/lib/graphql/rake_task.rb +28 -1
  42. data/lib/graphql/schema/addition.rb +38 -12
  43. data/lib/graphql/schema/always_visible.rb +10 -0
  44. data/lib/graphql/schema/argument.rb +15 -23
  45. data/lib/graphql/schema/build_from_definition.rb +54 -25
  46. data/lib/graphql/schema/directive/transform.rb +1 -1
  47. data/lib/graphql/schema/directive.rb +12 -2
  48. data/lib/graphql/schema/enum.rb +24 -17
  49. data/lib/graphql/schema/enum_value.rb +3 -4
  50. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  51. data/lib/graphql/schema/field.rb +95 -73
  52. data/lib/graphql/schema/field_extension.rb +1 -4
  53. data/lib/graphql/schema/find_inherited_value.rb +2 -7
  54. data/lib/graphql/schema/input_object.rb +9 -7
  55. data/lib/graphql/schema/interface.rb +5 -11
  56. data/lib/graphql/schema/introspection_system.rb +1 -1
  57. data/lib/graphql/schema/late_bound_type.rb +2 -0
  58. data/lib/graphql/schema/member/base_dsl_methods.rb +17 -14
  59. data/lib/graphql/schema/member/build_type.rb +11 -3
  60. data/lib/graphql/schema/member/has_arguments.rb +114 -65
  61. data/lib/graphql/schema/member/has_ast_node.rb +12 -0
  62. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  63. data/lib/graphql/schema/member/has_directives.rb +81 -61
  64. data/lib/graphql/schema/member/has_fields.rb +95 -38
  65. data/lib/graphql/schema/member/has_interfaces.rb +49 -8
  66. data/lib/graphql/schema/member/has_validators.rb +32 -6
  67. data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
  68. data/lib/graphql/schema/member/type_system_helpers.rb +17 -0
  69. data/lib/graphql/schema/object.rb +8 -5
  70. data/lib/graphql/schema/printer.rb +3 -1
  71. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  72. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  73. data/lib/graphql/schema/resolver.rb +16 -14
  74. data/lib/graphql/schema/timeout.rb +25 -29
  75. data/lib/graphql/schema/type_membership.rb +3 -0
  76. data/lib/graphql/schema/union.rb +10 -1
  77. data/lib/graphql/schema/validator.rb +2 -2
  78. data/lib/graphql/schema/warden.rb +64 -7
  79. data/lib/graphql/schema.rb +171 -28
  80. data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
  81. data/lib/graphql/static_validation/literal_validator.rb +15 -1
  82. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
  83. data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
  84. data/lib/graphql/static_validation/validator.rb +1 -1
  85. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
  86. data/lib/graphql/subscriptions/event.rb +2 -7
  87. data/lib/graphql/subscriptions.rb +5 -0
  88. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  89. data/lib/graphql/tracing/appoptics_trace.rb +255 -0
  90. data/lib/graphql/tracing/appsignal_trace.rb +81 -0
  91. data/lib/graphql/tracing/data_dog_trace.rb +187 -0
  92. data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
  93. data/lib/graphql/tracing/legacy_trace.rb +69 -0
  94. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  95. data/lib/graphql/tracing/notifications_trace.rb +49 -0
  96. data/lib/graphql/tracing/platform_trace.rb +123 -0
  97. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  98. data/lib/graphql/tracing/prometheus_trace.rb +93 -0
  99. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
  100. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  101. data/lib/graphql/tracing/scout_trace.rb +75 -0
  102. data/lib/graphql/tracing/statsd_trace.rb +60 -0
  103. data/lib/graphql/tracing/trace.rb +75 -0
  104. data/lib/graphql/tracing.rb +17 -39
  105. data/lib/graphql/type_kinds.rb +6 -3
  106. data/lib/graphql/types/relay/base_connection.rb +1 -1
  107. data/lib/graphql/types/relay/connection_behaviors.rb +28 -6
  108. data/lib/graphql/types/relay/edge_behaviors.rb +16 -5
  109. data/lib/graphql/types/relay/node_behaviors.rb +8 -2
  110. data/lib/graphql/types/relay/page_info_behaviors.rb +7 -2
  111. data/lib/graphql/types/relay.rb +0 -1
  112. data/lib/graphql/types/string.rb +1 -1
  113. data/lib/graphql/version.rb +1 -1
  114. data/lib/graphql.rb +16 -9
  115. data/readme.md +1 -1
  116. metadata +66 -29
  117. data/lib/graphql/language/lexer.rl +0 -280
  118. data/lib/graphql/types/relay/default_relay.rb +0 -21
@@ -65,7 +65,9 @@ module GraphQL
65
65
  # Visit `document` and all children, applying hooks as you go
66
66
  # @return [void]
67
67
  def visit
68
- result = on_node_with_modifications(@document, nil)
68
+ # `@document` may be any kind of node:
69
+ visit_method = :"#{@document.visit_method}_with_modifications"
70
+ result = public_send(visit_method, @document, nil)
69
71
  @result = if result.is_a?(Array)
70
72
  result.first
71
73
  else
@@ -74,104 +76,213 @@ module GraphQL
74
76
  end
75
77
  end
76
78
 
77
- # Call the user-defined handler for `node`.
78
- def visit_node(node, parent)
79
- public_send(node.visit_method, node, parent)
79
+ def on_document_children(document_node)
80
+ new_node = document_node
81
+ document_node.children.each do |child_node|
82
+ visit_method = :"#{child_node.visit_method}_with_modifications"
83
+ new_child_and_node = public_send(visit_method, child_node, new_node)
84
+ # Reassign `node` in case the child hook makes a modification
85
+ if new_child_and_node.is_a?(Array)
86
+ new_node = new_child_and_node[1]
87
+ end
88
+ end
89
+ new_node
80
90
  end
81
91
 
82
- # The default implementation for visiting an AST node.
83
- # It doesn't _do_ anything, but it continues to visiting the node's children.
84
- # To customize this hook, override one of its make_visit_methodes (or the base method?)
85
- # in your subclasses.
86
- #
87
- # For compatibility, it calls hook procs, too.
88
- # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
89
- # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
90
- # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
91
- def on_abstract_node(node, parent)
92
- if node.equal?(DELETE_NODE)
93
- # This might be passed to `super(DELETE_NODE, ...)`
94
- # by a user hook, don't want to keep visiting in that case.
95
- nil
96
- else
97
- # Run hooks if there are any
98
- new_node = node
99
- no_hooks = !@visitors.key?(node.class)
100
- if no_hooks || begin_visit(new_node, parent)
101
- node.children.each do |child_node|
102
- new_child_and_node = on_node_with_modifications(child_node, new_node)
103
- # Reassign `node` in case the child hook makes a modification
104
- if new_child_and_node.is_a?(Array)
105
- new_node = new_child_and_node[1]
106
- end
107
- end
92
+ def on_field_children(new_node)
93
+ new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop
94
+ new_child_and_node = on_argument_with_modifications(arg_node, new_node)
95
+ # Reassign `node` in case the child hook makes a modification
96
+ if new_child_and_node.is_a?(Array)
97
+ new_node = new_child_and_node[1]
108
98
  end
109
- end_visit(new_node, parent) unless no_hooks
99
+ end
100
+ new_node = visit_directives(new_node)
101
+ new_node = visit_selections(new_node)
102
+ new_node
103
+ end
110
104
 
111
- if new_node.equal?(node)
112
- nil
105
+ def visit_directives(new_node)
106
+ new_node.directives.each do |dir_node|
107
+ new_child_and_node = on_directive_with_modifications(dir_node, new_node)
108
+ # Reassign `node` in case the child hook makes a modification
109
+ if new_child_and_node.is_a?(Array)
110
+ new_node = new_child_and_node[1]
111
+ end
112
+ end
113
+ new_node
114
+ end
115
+
116
+ def visit_selections(new_node)
117
+ new_node.selections.each do |selection|
118
+ new_child_and_node = case selection
119
+ when GraphQL::Language::Nodes::Field
120
+ on_field_with_modifications(selection, new_node)
121
+ when GraphQL::Language::Nodes::InlineFragment
122
+ on_inline_fragment_with_modifications(selection, new_node)
123
+ when GraphQL::Language::Nodes::FragmentSpread
124
+ on_fragment_spread_with_modifications(selection, new_node)
113
125
  else
114
- [new_node, parent]
126
+ raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})"
127
+ end
128
+ # Reassign `node` in case the child hook makes a modification
129
+ if new_child_and_node.is_a?(Array)
130
+ new_node = new_child_and_node[1]
115
131
  end
116
132
  end
133
+ new_node
117
134
  end
118
135
 
119
- # We don't use `alias` here because it breaks `super`
120
- def self.make_visit_method(node_method)
136
+ def on_fragment_definition_children(new_node)
137
+ new_node = visit_directives(new_node)
138
+ new_node = visit_selections(new_node)
139
+ new_node
140
+ end
141
+
142
+ alias :on_inline_fragment_children :on_fragment_definition_children
143
+
144
+ def on_operation_definition_children(new_node)
145
+ new_node.variables.each do |arg_node|
146
+ new_child_and_node = on_variable_definition_with_modifications(arg_node, new_node)
147
+ # Reassign `node` in case the child hook makes a modification
148
+ if new_child_and_node.is_a?(Array)
149
+ new_node = new_child_and_node[1]
150
+ end
151
+ end
152
+ new_node = visit_directives(new_node)
153
+ new_node = visit_selections(new_node)
154
+ new_node
155
+ end
156
+
157
+ def on_argument_children(new_node)
158
+ new_node.children.each do |value_node|
159
+ new_child_and_node = case value_node
160
+ when Language::Nodes::VariableIdentifier
161
+ on_variable_identifier_with_modifications(value_node, new_node)
162
+ when Language::Nodes::InputObject
163
+ on_input_object_with_modifications(value_node, new_node)
164
+ when Language::Nodes::Enum
165
+ on_enum_with_modifications(value_node, new_node)
166
+ when Language::Nodes::NullValue
167
+ on_null_value_with_modifications(value_node, new_node)
168
+ else
169
+ raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})"
170
+ end
171
+ # Reassign `node` in case the child hook makes a modification
172
+ if new_child_and_node.is_a?(Array)
173
+ new_node = new_child_and_node[1]
174
+ end
175
+ end
176
+ new_node
177
+ end
178
+
179
+ # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
180
+
181
+ def self.make_visit_methods(ast_node_class)
182
+ node_method = ast_node_class.visit_method
183
+ children_of_type = ast_node_class.children_of_type
184
+ child_visit_method = :"#{node_method}_children"
185
+
121
186
  class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
187
+ # The default implementation for visiting an AST node.
188
+ # It doesn't _do_ anything, but it continues to visiting the node's children.
189
+ # To customize this hook, override one of its make_visit_methods (or the base method?)
190
+ # in your subclasses.
191
+ #
192
+ # For compatibility, it calls hook procs, too.
193
+ # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
194
+ # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
195
+ # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
122
196
  def #{node_method}(node, parent)
123
- child_mod = on_abstract_node(node, parent)
124
- # If visiting the children returned changes, continue passing those.
125
- child_mod || [node, parent]
197
+ if node.equal?(DELETE_NODE)
198
+ # This might be passed to `super(DELETE_NODE, ...)`
199
+ # by a user hook, don't want to keep visiting in that case.
200
+ [node, parent]
201
+ else
202
+ # Run hooks if there are any
203
+ new_node = node
204
+ no_hooks = !@visitors.key?(node.class)
205
+ if no_hooks || begin_visit(new_node, parent)
206
+ #{
207
+ if method_defined?(child_visit_method)
208
+ "new_node = #{child_visit_method}(new_node)"
209
+ elsif children_of_type
210
+ children_of_type.map do |child_accessor, child_class|
211
+ "node.#{child_accessor}.each do |child_node|
212
+ new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
213
+ # Reassign `node` in case the child hook makes a modification
214
+ if new_child_and_node.is_a?(Array)
215
+ new_node = new_child_and_node[1]
216
+ end
217
+ end"
218
+ end.join("\n")
219
+ else
220
+ ""
221
+ end
222
+ }
223
+ end
224
+ end_visit(new_node, parent) unless no_hooks
225
+ if new_node.equal?(node)
226
+ [node, parent]
227
+ else
228
+ [new_node, parent]
229
+ end
230
+ end
231
+ end
232
+ def #{node_method}_with_modifications(node, parent)
233
+ new_node_and_new_parent = #{node_method}(node, parent)
234
+ apply_modifications(node, parent, new_node_and_new_parent)
126
235
  end
127
236
  RUBY
128
237
  end
129
238
 
130
- make_visit_method :on_argument
131
- make_visit_method :on_directive
132
- make_visit_method :on_directive_definition
133
- make_visit_method :on_directive_location
134
- make_visit_method :on_document
135
- make_visit_method :on_enum
136
- make_visit_method :on_enum_type_definition
137
- make_visit_method :on_enum_type_extension
138
- make_visit_method :on_enum_value_definition
139
- make_visit_method :on_field
140
- make_visit_method :on_field_definition
141
- make_visit_method :on_fragment_definition
142
- make_visit_method :on_fragment_spread
143
- make_visit_method :on_inline_fragment
144
- make_visit_method :on_input_object
145
- make_visit_method :on_input_object_type_definition
146
- make_visit_method :on_input_object_type_extension
147
- make_visit_method :on_input_value_definition
148
- make_visit_method :on_interface_type_definition
149
- make_visit_method :on_interface_type_extension
150
- make_visit_method :on_list_type
151
- make_visit_method :on_non_null_type
152
- make_visit_method :on_null_value
153
- make_visit_method :on_object_type_definition
154
- make_visit_method :on_object_type_extension
155
- make_visit_method :on_operation_definition
156
- make_visit_method :on_scalar_type_definition
157
- make_visit_method :on_scalar_type_extension
158
- make_visit_method :on_schema_definition
159
- make_visit_method :on_schema_extension
160
- make_visit_method :on_type_name
161
- make_visit_method :on_union_type_definition
162
- make_visit_method :on_union_type_extension
163
- make_visit_method :on_variable_definition
164
- make_visit_method :on_variable_identifier
239
+
240
+
241
+ [
242
+ Language::Nodes::Argument,
243
+ Language::Nodes::Directive,
244
+ Language::Nodes::DirectiveDefinition,
245
+ Language::Nodes::DirectiveLocation,
246
+ Language::Nodes::Document,
247
+ Language::Nodes::Enum,
248
+ Language::Nodes::EnumTypeDefinition,
249
+ Language::Nodes::EnumTypeExtension,
250
+ Language::Nodes::EnumValueDefinition,
251
+ Language::Nodes::Field,
252
+ Language::Nodes::FieldDefinition,
253
+ Language::Nodes::FragmentDefinition,
254
+ Language::Nodes::FragmentSpread,
255
+ Language::Nodes::InlineFragment,
256
+ Language::Nodes::InputObject,
257
+ Language::Nodes::InputObjectTypeDefinition,
258
+ Language::Nodes::InputObjectTypeExtension,
259
+ Language::Nodes::InputValueDefinition,
260
+ Language::Nodes::InterfaceTypeDefinition,
261
+ Language::Nodes::InterfaceTypeExtension,
262
+ Language::Nodes::ListType,
263
+ Language::Nodes::NonNullType,
264
+ Language::Nodes::NullValue,
265
+ Language::Nodes::ObjectTypeDefinition,
266
+ Language::Nodes::ObjectTypeExtension,
267
+ Language::Nodes::OperationDefinition,
268
+ Language::Nodes::ScalarTypeDefinition,
269
+ Language::Nodes::ScalarTypeExtension,
270
+ Language::Nodes::SchemaDefinition,
271
+ Language::Nodes::SchemaExtension,
272
+ Language::Nodes::TypeName,
273
+ Language::Nodes::UnionTypeDefinition,
274
+ Language::Nodes::UnionTypeExtension,
275
+ Language::Nodes::VariableDefinition,
276
+ Language::Nodes::VariableIdentifier,
277
+ ].each do |ast_node_class|
278
+ make_visit_methods(ast_node_class)
279
+ end
280
+
281
+ # rubocop:enable Development/NoEvalCop
165
282
 
166
283
  private
167
284
 
168
- # Run the hooks for `node`, and if the hooks return a copy of `node`,
169
- # copy `parent` so that it contains the copy of that node as a child,
170
- # then return the copies
171
- # If a non-array value is returned, consuming functions should ignore
172
- # said value
173
- def on_node_with_modifications(node, parent)
174
- new_node_and_new_parent = visit_node(node, parent)
285
+ def apply_modifications(node, parent, new_node_and_new_parent)
175
286
  if new_node_and_new_parent.is_a?(Array)
176
287
  new_node = new_node_and_new_parent[0]
177
288
  new_parent = new_node_and_new_parent[1]
@@ -7,14 +7,6 @@ module GraphQL
7
7
  class ActiveRecordRelationConnection < Pagination::RelationConnection
8
8
  private
9
9
 
10
- def relation_larger_than(relation, initial_offset, size)
11
- if already_loaded?(relation)
12
- (relation.size + initial_offset) > size
13
- else
14
- set_offset(sliced_nodes, initial_offset + size).exists?
15
- end
16
- end
17
-
18
10
  def relation_count(relation)
19
11
  int_or_hash = if already_loaded?(relation)
20
12
  relation.size
@@ -58,7 +58,7 @@ module GraphQL
58
58
  # @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection
59
59
  # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set.
60
60
  # @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given.
61
- def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, default_page_size: :not_given, last: nil, before: nil, edge_class: nil, arguments: nil)
61
+ def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, last: nil, before: nil, edge_class: nil, arguments: nil)
62
62
  @items = items
63
63
  @parent = parent
64
64
  @context = context
@@ -71,14 +71,14 @@ module GraphQL
71
71
  @edge_class = edge_class || self.class::Edge
72
72
  # This is only true if the object was _initialized_ with an override
73
73
  # or if one is assigned later.
74
- @has_max_page_size_override = max_page_size != :not_given
75
- @max_page_size = if max_page_size == :not_given
74
+ @has_max_page_size_override = max_page_size != NOT_CONFIGURED
75
+ @max_page_size = if max_page_size == NOT_CONFIGURED
76
76
  nil
77
77
  else
78
78
  max_page_size
79
79
  end
80
- @has_default_page_size_override = default_page_size != :not_given
81
- @default_page_size = if default_page_size == :not_given
80
+ @has_default_page_size_override = default_page_size != NOT_CONFIGURED
81
+ @default_page_size = if default_page_size == NOT_CONFIGURED
82
82
  nil
83
83
  else
84
84
  default_page_size
@@ -91,33 +91,44 @@ module GraphQL
91
91
  end
92
92
 
93
93
  class ScopedContext
94
+ NO_PATH = GraphQL::EmptyObjects::EMPTY_ARRAY
95
+ NO_CONTEXT = GraphQL::EmptyObjects::EMPTY_HASH
96
+
94
97
  def initialize(query_context)
95
98
  @query_context = query_context
96
- @path_contexts = {}
97
- @no_path = [].freeze
99
+ @scoped_contexts = nil
100
+ @all_keys = nil
98
101
  end
99
102
 
100
103
  def merged_context
101
- merged_ctx = {}
102
- each_present_path_ctx do |path_ctx|
103
- merged_ctx = path_ctx.merge(merged_ctx)
104
+ if @scoped_contexts.nil?
105
+ NO_CONTEXT
106
+ else
107
+ merged_ctx = {}
108
+ each_present_path_ctx do |path_ctx|
109
+ merged_ctx = path_ctx.merge(merged_ctx)
110
+ end
111
+ merged_ctx
104
112
  end
105
- merged_ctx
106
113
  end
107
114
 
108
115
  def merge!(hash)
109
- current_ctx = @path_contexts[current_path] ||= {}
110
- current_ctx.merge!(hash)
111
- end
112
-
113
- def current_path
114
- @query_context.namespace(:interpreter)[:current_path] || @no_path
116
+ @all_keys ||= Set.new
117
+ @all_keys.merge(hash.keys)
118
+ ctx = @scoped_contexts ||= {}
119
+ current_path.each do |path_part|
120
+ ctx = ctx[path_part] ||= { parent: ctx }
121
+ end
122
+ this_scoped_ctx = ctx[:scoped_context] ||= {}
123
+ this_scoped_ctx.merge!(hash)
115
124
  end
116
125
 
117
126
  def key?(key)
118
- each_present_path_ctx do |path_ctx|
119
- if path_ctx.key?(key)
120
- return true
127
+ if @all_keys && @all_keys.include?(key)
128
+ each_present_path_ctx do |path_ctx|
129
+ if path_ctx.key?(key)
130
+ return true
131
+ end
121
132
  end
122
133
  end
123
134
  false
@@ -132,6 +143,10 @@ module GraphQL
132
143
  nil
133
144
  end
134
145
 
146
+ def current_path
147
+ @query_context.current_path || NO_PATH
148
+ end
149
+
135
150
  def dig(key, *other_keys)
136
151
  each_present_path_ctx do |path_ctx|
137
152
  if path_ctx.key?(key)
@@ -151,15 +166,23 @@ module GraphQL
151
166
  # Start at the current location,
152
167
  # but look up the tree for previously-assigned scoped values
153
168
  def each_present_path_ctx
154
- search_path = current_path.dup
155
- if (current_path_ctx = @path_contexts[search_path])
156
- yield(current_path_ctx)
157
- end
169
+ ctx = @scoped_contexts
170
+ if ctx.nil?
171
+ # no-op
172
+ else
173
+ current_path.each do |path_part|
174
+ if ctx.key?(path_part)
175
+ ctx = ctx[path_part]
176
+ else
177
+ break
178
+ end
179
+ end
158
180
 
159
- while search_path.size > 0
160
- search_path.pop # look one level higher
161
- if (search_path_ctx = @path_contexts[search_path])
162
- yield(search_path_ctx)
181
+ while ctx
182
+ if (scoped_ctx = ctx[:scoped_context])
183
+ yield(scoped_ctx)
184
+ end
185
+ ctx = ctx[:parent]
163
186
  end
164
187
  end
165
188
  end
@@ -189,6 +212,7 @@ module GraphQL
189
212
 
190
213
  def_delegators :@query, :trace, :interpreter?
191
214
 
215
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
192
216
  # @!method []=(key, value)
193
217
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
194
218
 
@@ -196,11 +220,36 @@ module GraphQL
196
220
  def [](key)
197
221
  if @scoped_context.key?(key)
198
222
  @scoped_context[key]
199
- else
223
+ elsif @provided_values.key?(key)
200
224
  @provided_values[key]
225
+ elsif RUNTIME_METADATA_KEYS.include?(key)
226
+ if key == :current_path
227
+ current_path
228
+ else
229
+ (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
230
+ (query_runtime_state = current_runtime_state[@query]) &&
231
+ (query_runtime_state.public_send(key))
232
+ end
233
+ else
234
+ # not found
235
+ nil
201
236
  end
202
237
  end
203
238
 
239
+ def current_path
240
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
241
+ query_runtime_state = current_runtime_state && current_runtime_state[@query]
242
+
243
+ path = query_runtime_state &&
244
+ (result = query_runtime_state.current_result) &&
245
+ (result.path)
246
+ if path && (rn = query_runtime_state.current_result_name)
247
+ path = path.dup
248
+ path.push(rn)
249
+ end
250
+ path
251
+ end
252
+
204
253
  def delete(key)
205
254
  if @scoped_context.key?(key)
206
255
  @scoped_context.delete(key)
@@ -212,7 +261,11 @@ module GraphQL
212
261
  UNSPECIFIED_FETCH_DEFAULT = Object.new
213
262
 
214
263
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
215
- if @scoped_context.key?(key)
264
+ if RUNTIME_METADATA_KEYS.include?(key)
265
+ (runtime = Thread.current[:__graphql_runtime_info]) &&
266
+ (query_runtime_state = runtime[@query]) &&
267
+ (query_runtime_state.public_send(key))
268
+ elsif @scoped_context.key?(key)
216
269
  scoped_context[key]
217
270
  elsif @provided_values.key?(key)
218
271
  @provided_values[key]
@@ -226,7 +279,16 @@ module GraphQL
226
279
  end
227
280
 
228
281
  def dig(key, *other_keys)
229
- if @scoped_context.key?(key)
282
+ if RUNTIME_METADATA_KEYS.include?(key)
283
+ (current_runtime_state = Thread.current[:__graphql_runtime_info]) &&
284
+ (query_runtime_state = current_runtime_state[@query]) &&
285
+ (obj = query_runtime_state.public_send(key)) &&
286
+ if other_keys.empty?
287
+ obj
288
+ else
289
+ obj.dig(*other_keys)
290
+ end
291
+ elsif @scoped_context.key?(key)
230
292
  @scoped_context.dig(key, *other_keys)
231
293
  else
232
294
  @provided_values.dig(key, *other_keys)
@@ -259,7 +321,11 @@ module GraphQL
259
321
  # @param ns [Object] a usage-specific namespace identifier
260
322
  # @return [Hash] namespaced storage
261
323
  def namespace(ns)
262
- @storage[ns]
324
+ if ns == :interpreter
325
+ self
326
+ else
327
+ @storage[ns]
328
+ end
263
329
  end
264
330
 
265
331
  # @return [Boolean] true if this namespace was accessed before
@@ -3,35 +3,27 @@ module GraphQL
3
3
  class Query
4
4
  # This object can be `ctx` in places where there is no query
5
5
  class NullContext
6
- class NullWarden < GraphQL::Schema::Warden
7
- def visible_field?(field, ctx); true; end
8
- def visible_argument?(arg, ctx); true; end
9
- def visible_type?(type, ctx); true; end
10
- def visible_enum_value?(ev, ctx); true; end
11
- def visible_type_membership?(tm, ctx); true; end
12
- end
13
-
14
6
  class NullQuery
7
+ def after_lazy(value)
8
+ yield(value)
9
+ end
15
10
  end
16
11
 
17
12
  class NullSchema < GraphQL::Schema
18
13
  end
19
14
 
15
+ extend Forwardable
16
+
20
17
  attr_reader :schema, :query, :warden, :dataloader
18
+ def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?
21
19
 
22
20
  def initialize
23
21
  @query = NullQuery.new
24
22
  @dataloader = GraphQL::Dataloader::NullDataloader.new
25
23
  @schema = NullSchema
26
- @warden = NullWarden.new(
27
- GraphQL::Filter.new,
28
- context: self,
29
- schema: @schema,
30
- )
24
+ @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
31
25
  end
32
26
 
33
- def [](key); end
34
-
35
27
  def interpreter?
36
28
  true
37
29
  end
@@ -39,13 +31,11 @@ module GraphQL
39
31
  class << self
40
32
  extend Forwardable
41
33
 
42
- def [](key); end
43
-
44
34
  def instance
45
35
  @instance ||= self.new
46
36
  end
47
37
 
48
- def_delegators :instance, :query, :warden, :schema, :interpreter?, :dataloader
38
+ def_delegators :instance, :query, :warden, :schema, :interpreter?, :dataloader, :[], :fetch, :dig, :key?
49
39
  end
50
40
  end
51
41
  end
@@ -68,7 +68,8 @@ module GraphQL
68
68
  elsif @operation_name_error
69
69
  @validation_errors << @operation_name_error
70
70
  else
71
- validation_result = @schema.static_validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
71
+ validator = @query.static_validator || @schema.static_validator
72
+ validation_result = validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors)
72
73
  @validation_errors.concat(validation_result[:errors])
73
74
 
74
75
  if @validation_errors.empty?