graphql 2.0.27 → 2.1.3

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

Potentially problematic release.


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

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  7. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  8. data/lib/generators/graphql/templates/base_field.erb +2 -0
  9. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  15. data/lib/generators/graphql/templates/loader.erb +2 -0
  16. data/lib/generators/graphql/templates/mutation.erb +2 -0
  17. data/lib/generators/graphql/templates/node_type.erb +2 -0
  18. data/lib/generators/graphql/templates/query_type.erb +2 -0
  19. data/lib/generators/graphql/templates/schema.erb +2 -0
  20. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  21. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  22. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  23. data/lib/graphql/analysis/ast.rb +15 -11
  24. data/lib/graphql/dataloader/source.rb +7 -0
  25. data/lib/graphql/dataloader.rb +38 -10
  26. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  27. data/lib/graphql/execution/interpreter/runtime.rb +95 -254
  28. data/lib/graphql/execution/interpreter.rb +0 -6
  29. data/lib/graphql/execution/lookahead.rb +1 -1
  30. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  31. data/lib/graphql/introspection/entry_points.rb +2 -2
  32. data/lib/graphql/language/block_string.rb +28 -16
  33. data/lib/graphql/language/definition_slice.rb +1 -1
  34. data/lib/graphql/language/document_from_schema_definition.rb +36 -35
  35. data/lib/graphql/language/nodes.rb +2 -2
  36. data/lib/graphql/language/printer.rb +294 -145
  37. data/lib/graphql/language/sanitized_printer.rb +20 -22
  38. data/lib/graphql/language/static_visitor.rb +167 -0
  39. data/lib/graphql/language/visitor.rb +20 -81
  40. data/lib/graphql/language.rb +1 -0
  41. data/lib/graphql/pagination/connection.rb +23 -1
  42. data/lib/graphql/query/context/scoped_context.rb +101 -0
  43. data/lib/graphql/query/context.rb +32 -98
  44. data/lib/graphql/query.rb +2 -19
  45. data/lib/graphql/rake_task.rb +3 -12
  46. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  47. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  48. data/lib/graphql/schema/field/scope_extension.rb +7 -1
  49. data/lib/graphql/schema/field.rb +7 -4
  50. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  51. data/lib/graphql/schema/introspection_system.rb +2 -0
  52. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  53. data/lib/graphql/schema/member/has_arguments.rb +19 -4
  54. data/lib/graphql/schema/member/has_fields.rb +4 -1
  55. data/lib/graphql/schema/member/has_interfaces.rb +21 -7
  56. data/lib/graphql/schema/member/scoped.rb +19 -0
  57. data/lib/graphql/schema/object.rb +8 -0
  58. data/lib/graphql/schema/printer.rb +8 -7
  59. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  60. data/lib/graphql/schema/resolver.rb +4 -0
  61. data/lib/graphql/schema/scalar.rb +3 -3
  62. data/lib/graphql/schema/subscription.rb +11 -4
  63. data/lib/graphql/schema/warden.rb +87 -89
  64. data/lib/graphql/schema.rb +125 -55
  65. data/lib/graphql/static_validation/all_rules.rb +1 -1
  66. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  67. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  68. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  69. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  70. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  71. data/lib/graphql/static_validation/validation_context.rb +5 -5
  72. data/lib/graphql/static_validation.rb +0 -1
  73. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
  74. data/lib/graphql/subscriptions.rb +11 -6
  75. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  76. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  77. data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
  78. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  79. data/lib/graphql/version.rb +1 -1
  80. data/lib/graphql.rb +1 -2
  81. metadata +23 -20
  82. data/lib/graphql/filter.rb +0 -59
  83. data/lib/graphql/static_validation/type_stack.rb +0 -216
@@ -40,7 +40,7 @@ module GraphQL
40
40
  case node
41
41
  when FalseClass, Float, Integer, String, TrueClass
42
42
  if @current_argument && redact_argument_value?(@current_argument, node)
43
- redacted_argument_value(@current_argument)
43
+ print_string(redacted_argument_value(@current_argument))
44
44
  else
45
45
  super
46
46
  end
@@ -51,9 +51,8 @@ module GraphQL
51
51
  @current_input_type = @current_input_type.of_type if @current_input_type.non_null?
52
52
  end
53
53
 
54
- res = super
54
+ super
55
55
  @current_input_type = old_input_type
56
- res
57
56
  else
58
57
  super
59
58
  end
@@ -89,11 +88,12 @@ module GraphQL
89
88
  else
90
89
  argument.value
91
90
  end
92
- res = "#{argument.name}: #{print_node(argument_value)}".dup
91
+
92
+ print_string("#{argument.name}: ")
93
+ print_node(argument_value)
93
94
 
94
95
  @current_input_type = old_input_type
95
96
  @current_argument = old_current_argument
96
- res
97
97
  end
98
98
 
99
99
  def coerce_argument_value_to_list?(type, value)
@@ -116,9 +116,8 @@ module GraphQL
116
116
  @current_field = query.get_field(@current_type, field.name)
117
117
  old_type = @current_type
118
118
  @current_type = @current_field.type.unwrap
119
- res = super
119
+ super
120
120
  @current_type = old_type
121
- res
122
121
  end
123
122
 
124
123
  def print_inline_fragment(inline_fragment, indent: "")
@@ -128,31 +127,26 @@ module GraphQL
128
127
  @current_type = query.get_type(inline_fragment.type.name)
129
128
  end
130
129
 
131
- res = super
130
+ super
132
131
 
133
132
  @current_type = old_type
134
-
135
- res
136
133
  end
137
134
 
138
135
  def print_fragment_definition(fragment_def, indent: "")
139
136
  old_type = @current_type
140
137
  @current_type = query.get_type(fragment_def.type.name)
141
138
 
142
- res = super
139
+ super
143
140
 
144
141
  @current_type = old_type
145
-
146
- res
147
142
  end
148
143
 
149
144
  def print_directive(directive)
150
145
  @current_directive = query.schema.directives[directive.name]
151
146
 
152
- res = super
147
+ super
153
148
 
154
149
  @current_directive = nil
155
- res
156
150
  end
157
151
 
158
152
  # Print the operation definition but do not include the variable
@@ -162,16 +156,15 @@ module GraphQL
162
156
  @current_type = query.schema.public_send(operation_definition.operation_type)
163
157
 
164
158
  if @inline_variables
165
- out = "#{indent}#{operation_definition.operation_type}".dup
166
- out << " #{operation_definition.name}" if operation_definition.name
167
- out << print_directives(operation_definition.directives)
168
- out << print_selections(operation_definition.selections, indent: indent)
159
+ print_string("#{indent}#{operation_definition.operation_type}")
160
+ print_string(" #{operation_definition.name}") if operation_definition.name
161
+ print_directives(operation_definition.directives)
162
+ print_selections(operation_definition.selections, indent: indent)
169
163
  else
170
- out = super
164
+ super
171
165
  end
172
166
 
173
167
  @current_type = old_type
174
- out
175
168
  end
176
169
 
177
170
  private
@@ -210,7 +203,12 @@ module GraphQL
210
203
  [value].map { |v| value_to_ast(v, type.of_type) }
211
204
  end
212
205
  when "ENUM"
213
- GraphQL::Language::Nodes::Enum.new(name: value)
206
+ if value.is_a?(GraphQL::Language::Nodes::Enum)
207
+ # if it was a default value, it's already wrapped
208
+ value
209
+ else
210
+ GraphQL::Language::Nodes::Enum.new(name: value)
211
+ end
214
212
  else
215
213
  value
216
214
  end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Language
4
+ # Like `GraphQL::Language::Visitor` except it doesn't support
5
+ # making changes to the document -- only visiting it as-is.
6
+ class StaticVisitor
7
+ def initialize(document)
8
+ @document = document
9
+ end
10
+
11
+ # Visit `document` and all children
12
+ # @return [void]
13
+ def visit
14
+ # `@document` may be any kind of node:
15
+ visit_method = @document.visit_method
16
+ result = public_send(visit_method, @document, nil)
17
+ @result = if result.is_a?(Array)
18
+ result.first
19
+ else
20
+ # The node wasn't modified
21
+ @document
22
+ end
23
+ end
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
+ def on_document_children(document_node)
59
+ document_node.children.each do |child_node|
60
+ visit_method = child_node.visit_method
61
+ public_send(visit_method, child_node, document_node)
62
+ end
63
+ end
64
+
65
+ def on_field_children(new_node)
66
+ new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop
67
+ on_argument(arg_node, new_node)
68
+ end
69
+ visit_directives(new_node)
70
+ visit_selections(new_node)
71
+ end
72
+
73
+ def visit_directives(new_node)
74
+ new_node.directives.each do |dir_node|
75
+ on_directive(dir_node, new_node)
76
+ end
77
+ end
78
+
79
+ def visit_selections(new_node)
80
+ new_node.selections.each do |selection|
81
+ case selection
82
+ when GraphQL::Language::Nodes::Field
83
+ on_field(selection, new_node)
84
+ when GraphQL::Language::Nodes::InlineFragment
85
+ on_inline_fragment(selection, new_node)
86
+ when GraphQL::Language::Nodes::FragmentSpread
87
+ on_fragment_spread(selection, new_node)
88
+ else
89
+ raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})"
90
+ end
91
+ end
92
+ end
93
+
94
+ def on_fragment_definition_children(new_node)
95
+ visit_directives(new_node)
96
+ visit_selections(new_node)
97
+ end
98
+
99
+ alias :on_inline_fragment_children :on_fragment_definition_children
100
+
101
+ def on_operation_definition_children(new_node)
102
+ new_node.variables.each do |arg_node|
103
+ on_variable_definition(arg_node, new_node)
104
+ end
105
+ visit_directives(new_node)
106
+ visit_selections(new_node)
107
+ end
108
+
109
+ def on_argument_children(new_node)
110
+ new_node.children.each do |value_node|
111
+ case value_node
112
+ when Language::Nodes::VariableIdentifier
113
+ on_variable_identifier(value_node, new_node)
114
+ when Language::Nodes::InputObject
115
+ on_input_object(value_node, new_node)
116
+ when Language::Nodes::Enum
117
+ on_enum(value_node, new_node)
118
+ when Language::Nodes::NullValue
119
+ on_null_value(value_node, new_node)
120
+ else
121
+ raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})"
122
+ end
123
+ end
124
+ end
125
+
126
+ [
127
+ Language::Nodes::Argument,
128
+ Language::Nodes::Directive,
129
+ Language::Nodes::DirectiveDefinition,
130
+ Language::Nodes::DirectiveLocation,
131
+ Language::Nodes::Document,
132
+ Language::Nodes::Enum,
133
+ Language::Nodes::EnumTypeDefinition,
134
+ Language::Nodes::EnumTypeExtension,
135
+ Language::Nodes::EnumValueDefinition,
136
+ Language::Nodes::Field,
137
+ Language::Nodes::FieldDefinition,
138
+ Language::Nodes::FragmentDefinition,
139
+ Language::Nodes::FragmentSpread,
140
+ Language::Nodes::InlineFragment,
141
+ Language::Nodes::InputObject,
142
+ Language::Nodes::InputObjectTypeDefinition,
143
+ Language::Nodes::InputObjectTypeExtension,
144
+ Language::Nodes::InputValueDefinition,
145
+ Language::Nodes::InterfaceTypeDefinition,
146
+ Language::Nodes::InterfaceTypeExtension,
147
+ Language::Nodes::ListType,
148
+ Language::Nodes::NonNullType,
149
+ Language::Nodes::NullValue,
150
+ Language::Nodes::ObjectTypeDefinition,
151
+ Language::Nodes::ObjectTypeExtension,
152
+ Language::Nodes::OperationDefinition,
153
+ Language::Nodes::ScalarTypeDefinition,
154
+ Language::Nodes::ScalarTypeExtension,
155
+ Language::Nodes::SchemaDefinition,
156
+ Language::Nodes::SchemaExtension,
157
+ Language::Nodes::TypeName,
158
+ Language::Nodes::UnionTypeDefinition,
159
+ Language::Nodes::UnionTypeExtension,
160
+ Language::Nodes::VariableDefinition,
161
+ Language::Nodes::VariableIdentifier,
162
+ ].each do |ast_node_class|
163
+ make_visit_methods(ast_node_class)
164
+ end
165
+ end
166
+ end
167
+ end
@@ -30,12 +30,9 @@ module GraphQL
30
30
  # # Check the result
31
31
  # visitor.count
32
32
  # # => 3
33
+ #
34
+ # @see GraphQL::Language::StaticVisitor for a faster visitor that doesn't support modifying the document
33
35
  class Visitor
34
- # If any hook returns this value, the {Visitor} stops visiting this
35
- # node right away
36
- # @deprecated Use `super` to continue the visit; or don't call it to halt.
37
- SKIP = :_skip
38
-
39
36
  class DeleteNode; end
40
37
 
41
38
  # When this is returned from a visitor method,
@@ -44,25 +41,13 @@ module GraphQL
44
41
 
45
42
  def initialize(document)
46
43
  @document = document
47
- @visitors = {}
48
44
  @result = nil
49
45
  end
50
46
 
51
47
  # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied
52
48
  attr_reader :result
53
49
 
54
- # Get a {NodeVisitor} for `node_class`
55
- # @param node_class [Class] The node class that you want to listen to
56
- # @return [NodeVisitor]
57
- #
58
- # @example Run a hook whenever you enter a new Field
59
- # visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { p "Here's a field" }
60
- # @deprecated see `on_` methods, like {#on_field}
61
- def [](node_class)
62
- @visitors[node_class] ||= NodeVisitor.new
63
- end
64
-
65
- # Visit `document` and all children, applying hooks as you go
50
+ # Visit `document` and all children
66
51
  # @return [void]
67
52
  def visit
68
53
  # `@document` may be any kind of node:
@@ -88,7 +73,6 @@ module GraphQL
88
73
  # To customize this hook, override one of its make_visit_methods (or the base method?)
89
74
  # in your subclasses.
90
75
  #
91
- # For compatibility, it calls hook procs, too.
92
76
  # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
93
77
  # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
94
78
  # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
@@ -98,29 +82,24 @@ module GraphQL
98
82
  # by a user hook, don't want to keep visiting in that case.
99
83
  [node, parent]
100
84
  else
101
- # Run hooks if there are any
102
85
  new_node = node
103
- no_hooks = !@visitors.key?(node.class)
104
- if no_hooks || begin_visit(new_node, parent)
105
- #{
106
- if method_defined?(child_visit_method)
107
- "new_node = #{child_visit_method}(new_node)"
108
- elsif children_of_type
109
- children_of_type.map do |child_accessor, child_class|
110
- "node.#{child_accessor}.each do |child_node|
111
- new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
112
- # Reassign `node` in case the child hook makes a modification
113
- if new_child_and_node.is_a?(Array)
114
- new_node = new_child_and_node[1]
115
- end
116
- end"
117
- end.join("\n")
118
- else
119
- ""
120
- end
121
- }
122
- end
123
- end_visit(new_node, parent) unless no_hooks
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
+ }
124
103
 
125
104
  if new_node.equal?(node)
126
105
  [node, parent]
@@ -305,46 +284,6 @@ module GraphQL
305
284
  new_node_and_new_parent
306
285
  end
307
286
  end
308
-
309
- def begin_visit(node, parent)
310
- node_visitor = self[node.class]
311
- self.class.apply_hooks(node_visitor.enter, node, parent)
312
- end
313
-
314
- # Should global `leave` visitors come first or last?
315
- def end_visit(node, parent)
316
- node_visitor = self[node.class]
317
- self.class.apply_hooks(node_visitor.leave, node, parent)
318
- end
319
-
320
- # If one of the visitors returns SKIP, stop visiting this node
321
- def self.apply_hooks(hooks, node, parent)
322
- hooks.each do |proc|
323
- return false if proc.call(node, parent) == SKIP
324
- end
325
- true
326
- end
327
-
328
- # Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes}
329
- #
330
- # Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]}
331
- class NodeVisitor
332
- # @return [Array<Proc>] Hooks to call when entering a node of this type
333
- attr_reader :enter
334
- # @return [Array<Proc>] Hooks to call when leaving a node of this type
335
- attr_reader :leave
336
-
337
- def initialize
338
- @enter = []
339
- @leave = []
340
- end
341
-
342
- # Shorthand to add a hook to the {#enter} array
343
- # @param hook [Proc] A hook to add
344
- def <<(hook)
345
- enter << hook
346
- end
347
- end
348
287
  end
349
288
  end
350
289
  end
@@ -8,6 +8,7 @@ require "graphql/language/lexer"
8
8
  require "graphql/language/nodes"
9
9
  require "graphql/language/cache"
10
10
  require "graphql/language/parser"
11
+ require "graphql/language/static_visitor"
11
12
  require "graphql/language/token"
12
13
  require "graphql/language/visitor"
13
14
  require "graphql/language/definition_slice"
@@ -19,7 +19,14 @@ module GraphQL
19
19
  attr_reader :items
20
20
 
21
21
  # @return [GraphQL::Query::Context]
22
- attr_accessor :context
22
+ attr_reader :context
23
+
24
+ def context=(new_ctx)
25
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
26
+ query_runtime_state = current_runtime_state[new_ctx.query]
27
+ @was_authorized_by_scope_items = query_runtime_state.was_authorized_by_scope_items
28
+ @context = new_ctx
29
+ end
23
30
 
24
31
  # @return [Object] the object this collection belongs to
25
32
  attr_accessor :parent
@@ -83,6 +90,17 @@ module GraphQL
83
90
  else
84
91
  default_page_size
85
92
  end
93
+ @was_authorized_by_scope_items = if @context
94
+ current_runtime_state = Thread.current[:__graphql_runtime_info]
95
+ query_runtime_state = current_runtime_state[@context.query]
96
+ query_runtime_state.was_authorized_by_scope_items
97
+ else
98
+ nil
99
+ end
100
+ end
101
+
102
+ def was_authorized_by_scope_items?
103
+ @was_authorized_by_scope_items
86
104
  end
87
105
 
88
106
  def max_page_size=(new_value)
@@ -247,6 +265,10 @@ module GraphQL
247
265
  def cursor
248
266
  @cursor ||= @connection.cursor_for(@node)
249
267
  end
268
+
269
+ def was_authorized_by_scope_items?
270
+ @connection.was_authorized_by_scope_items?
271
+ end
250
272
  end
251
273
  end
252
274
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ class Context
5
+ class ScopedContext
6
+ def initialize(query_context)
7
+ @query_context = query_context
8
+ @scoped_contexts = nil
9
+ @all_keys = nil
10
+ end
11
+
12
+ def merged_context
13
+ if @scoped_contexts.nil?
14
+ GraphQL::EmptyObjects::EMPTY_HASH
15
+ else
16
+ merged_ctx = {}
17
+ each_present_path_ctx do |path_ctx|
18
+ merged_ctx = path_ctx.merge(merged_ctx)
19
+ end
20
+ merged_ctx
21
+ end
22
+ end
23
+
24
+ def merge!(hash, at: current_path)
25
+ @all_keys ||= Set.new
26
+ @all_keys.merge(hash.keys)
27
+ ctx = @scoped_contexts ||= {}
28
+ at.each do |path_part|
29
+ ctx = ctx[path_part] ||= { parent: ctx }
30
+ end
31
+ this_scoped_ctx = ctx[:scoped_context] ||= {}
32
+ this_scoped_ctx.merge!(hash)
33
+ end
34
+
35
+ def key?(key)
36
+ if @all_keys && @all_keys.include?(key)
37
+ each_present_path_ctx do |path_ctx|
38
+ if path_ctx.key?(key)
39
+ return true
40
+ end
41
+ end
42
+ end
43
+ false
44
+ end
45
+
46
+ def [](key)
47
+ each_present_path_ctx do |path_ctx|
48
+ if path_ctx.key?(key)
49
+ return path_ctx[key]
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ def current_path
56
+ @query_context.current_path || GraphQL::EmptyObjects::EMPTY_ARRAY
57
+ end
58
+
59
+ def dig(key, *other_keys)
60
+ each_present_path_ctx do |path_ctx|
61
+ if path_ctx.key?(key)
62
+ found_value = path_ctx[key]
63
+ if other_keys.any?
64
+ return found_value.dig(*other_keys)
65
+ else
66
+ return found_value
67
+ end
68
+ end
69
+ end
70
+ nil
71
+ end
72
+
73
+ private
74
+
75
+ # Start at the current location,
76
+ # but look up the tree for previously-assigned scoped values
77
+ def each_present_path_ctx
78
+ ctx = @scoped_contexts
79
+ if ctx.nil?
80
+ # no-op
81
+ else
82
+ current_path.each do |path_part|
83
+ if ctx.key?(path_part)
84
+ ctx = ctx[path_part]
85
+ else
86
+ break
87
+ end
88
+ end
89
+
90
+ while ctx
91
+ if (scoped_ctx = ctx[:scoped_context])
92
+ yield(scoped_ctx)
93
+ end
94
+ ctx = ctx[:parent]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end