graphql-client 0.16.0 → 0.26.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/graphql/client/collocated_enforcement.rb +21 -14
- data/lib/graphql/client/definition.rb +70 -43
- data/lib/graphql/client/definition_variables.rb +18 -11
- data/lib/graphql/client/document_types.rb +34 -20
- data/lib/graphql/client/erb.rb +1 -0
- data/lib/graphql/client/errors.rb +1 -1
- data/lib/graphql/client/http.rb +8 -2
- data/lib/graphql/client/log_subscriber.rb +14 -2
- data/lib/graphql/client/response.rb +8 -1
- data/lib/graphql/client/schema/enum_type.rb +17 -2
- data/lib/graphql/client/schema/interface_type.rb +1 -1
- data/lib/graphql/client/schema/object_type.rb +152 -93
- data/lib/graphql/client/schema/union_type.rb +1 -1
- data/lib/graphql/client/schema.rb +3 -2
- data/lib/graphql/client/type_stack.rb +144 -0
- data/lib/graphql/client/view_module.rb +1 -1
- data/lib/graphql/client.rb +93 -65
- data/lib/rubocop/cop/graphql/heredoc.rb +3 -3
- data/lib/rubocop/cop/graphql/overfetch.rb +39 -23
- metadata +19 -20
|
@@ -3,7 +3,6 @@ require "active_support/inflector"
|
|
|
3
3
|
require "graphql/client/error"
|
|
4
4
|
require "graphql/client/errors"
|
|
5
5
|
require "graphql/client/schema/base_type"
|
|
6
|
-
require "graphql/client/schema/possible_types"
|
|
7
6
|
|
|
8
7
|
module GraphQL
|
|
9
8
|
class Client
|
|
@@ -16,6 +15,53 @@ module GraphQL
|
|
|
16
15
|
|
|
17
16
|
define_singleton_method(:type) { type }
|
|
18
17
|
define_singleton_method(:fields) { fields }
|
|
18
|
+
|
|
19
|
+
const_set(:READERS, {})
|
|
20
|
+
const_set(:PREDICATES, {})
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class WithDefinition
|
|
25
|
+
include BaseType
|
|
26
|
+
include ObjectType
|
|
27
|
+
|
|
28
|
+
EMPTY_SET = Set.new.freeze
|
|
29
|
+
|
|
30
|
+
attr_reader :klass, :defined_fields, :definition
|
|
31
|
+
|
|
32
|
+
def type
|
|
33
|
+
@klass.type
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fields
|
|
37
|
+
@klass.fields
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def spreads
|
|
41
|
+
if defined?(@spreads)
|
|
42
|
+
@spreads
|
|
43
|
+
else
|
|
44
|
+
EMPTY_SET
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(klass, defined_fields, definition, spreads)
|
|
49
|
+
@klass = klass
|
|
50
|
+
@defined_fields = defined_fields.map do |k, v|
|
|
51
|
+
[-k.to_s, v]
|
|
52
|
+
end.to_h
|
|
53
|
+
@definition = definition
|
|
54
|
+
@spreads = spreads unless spreads.empty?
|
|
55
|
+
|
|
56
|
+
@defined_fields.keys.each do |attr|
|
|
57
|
+
name = ActiveSupport::Inflector.underscore(attr)
|
|
58
|
+
@klass::READERS[:"#{name}"] ||= attr
|
|
59
|
+
@klass::PREDICATES[:"#{name}?"] ||= attr
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def new(data = {}, errors = Errors.new)
|
|
64
|
+
@klass.new(data, errors, self)
|
|
19
65
|
end
|
|
20
66
|
end
|
|
21
67
|
|
|
@@ -46,56 +92,9 @@ module GraphQL
|
|
|
46
92
|
field_classes[result_name.to_sym] = schema_module.define_class(definition, field_ast_nodes, field_return_type)
|
|
47
93
|
end
|
|
48
94
|
|
|
49
|
-
|
|
50
|
-
klass.define_fields(field_classes)
|
|
51
|
-
klass.instance_variable_set(:@source_definition, definition)
|
|
52
|
-
klass.instance_variable_set(:@_spreads, definition.indexes[:spreads][ast_nodes.first])
|
|
53
|
-
|
|
54
|
-
if definition.client.enforce_collocated_callers
|
|
55
|
-
keys = field_classes.keys.map { |key| ActiveSupport::Inflector.underscore(key) }
|
|
56
|
-
Client.enforce_collocated_callers(klass, keys, definition.source_location[0])
|
|
57
|
-
end
|
|
95
|
+
spreads = definition.indexes[:spreads][ast_nodes.first]
|
|
58
96
|
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
PREDICATE_CACHE = Hash.new { |h, name|
|
|
63
|
-
h[name] = -> { @data[name] ? true : false }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
METHOD_CACHE = Hash.new { |h, key|
|
|
67
|
-
h[key] = -> {
|
|
68
|
-
name = key.to_s
|
|
69
|
-
type = self.class::FIELDS[key]
|
|
70
|
-
@casted_data.fetch(name) do
|
|
71
|
-
@casted_data[name] = type.cast(@data[name], @errors.filter_by_path(name))
|
|
72
|
-
end
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
MODULE_CACHE = Hash.new do |h, fields|
|
|
77
|
-
h[fields] = Module.new do
|
|
78
|
-
fields.each do |name|
|
|
79
|
-
GraphQL::Client::Schema::ObjectType.define_cached_field(name, self)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
FIELDS_CACHE = Hash.new { |h, k| h[k] = k }
|
|
85
|
-
|
|
86
|
-
def define_fields(fields)
|
|
87
|
-
const_set :FIELDS, FIELDS_CACHE[fields]
|
|
88
|
-
mod = MODULE_CACHE[fields.keys.sort]
|
|
89
|
-
include mod
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def self.define_cached_field(name, ctx)
|
|
93
|
-
key = name
|
|
94
|
-
name = -name.to_s
|
|
95
|
-
method_name = ActiveSupport::Inflector.underscore(name)
|
|
96
|
-
|
|
97
|
-
ctx.send(:define_method, method_name, &METHOD_CACHE[key])
|
|
98
|
-
ctx.send(:define_method, "#{method_name}?", &PREDICATE_CACHE[name])
|
|
97
|
+
WithDefinition.new(self, field_classes, definition, spreads)
|
|
99
98
|
end
|
|
100
99
|
|
|
101
100
|
def define_field(name, type)
|
|
@@ -132,14 +131,13 @@ module GraphQL
|
|
|
132
131
|
case selected_ast_node
|
|
133
132
|
when GraphQL::Language::Nodes::InlineFragment
|
|
134
133
|
continue_selection = if selected_ast_node.type.nil?
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
end
|
|
134
|
+
true
|
|
135
|
+
else
|
|
136
|
+
type_condition = definition.client.get_type(selected_ast_node.type.name)
|
|
137
|
+
applicable_types = definition.client.possible_types(type_condition)
|
|
138
|
+
# continue if this object type is one of the types matching the fragment condition
|
|
139
|
+
applicable_types.include?(type)
|
|
140
|
+
end
|
|
143
141
|
|
|
144
142
|
if continue_selection
|
|
145
143
|
selected_ast_node.selections.each do |next_selected_ast_node|
|
|
@@ -150,10 +148,8 @@ module GraphQL
|
|
|
150
148
|
fragment_definition = definition.document.definitions.find do |defn|
|
|
151
149
|
defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == selected_ast_node.name
|
|
152
150
|
end
|
|
153
|
-
|
|
154
|
-
schema = definition.client.schema
|
|
155
151
|
type_condition = definition.client.get_type(fragment_definition.type.name)
|
|
156
|
-
applicable_types =
|
|
152
|
+
applicable_types = definition.client.possible_types(type_condition)
|
|
157
153
|
# continue if this object type is one of the types matching the fragment condition
|
|
158
154
|
continue_selection = applicable_types.include?(type)
|
|
159
155
|
|
|
@@ -177,17 +173,16 @@ module GraphQL
|
|
|
177
173
|
end
|
|
178
174
|
|
|
179
175
|
class ObjectClass
|
|
180
|
-
|
|
181
|
-
attr_reader :source_definition
|
|
182
|
-
attr_reader :_spreads
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
extend ClassMethods
|
|
186
|
-
|
|
187
|
-
def initialize(data = {}, errors = Errors.new)
|
|
176
|
+
def initialize(data = {}, errors = Errors.new, definer = nil)
|
|
188
177
|
@data = data
|
|
189
178
|
@casted_data = {}
|
|
190
179
|
@errors = errors
|
|
180
|
+
|
|
181
|
+
# If we are not provided a definition, we can use this empty default
|
|
182
|
+
definer ||= ObjectType::WithDefinition.new(self.class, {}, nil, [])
|
|
183
|
+
|
|
184
|
+
@definer = definer
|
|
185
|
+
@enforce_collocated_callers = source_definition && source_definition.client.enforce_collocated_callers
|
|
191
186
|
end
|
|
192
187
|
|
|
193
188
|
# Public: Returns the raw response data
|
|
@@ -196,43 +191,87 @@ module GraphQL
|
|
|
196
191
|
def to_h
|
|
197
192
|
@data
|
|
198
193
|
end
|
|
194
|
+
alias :to_hash :to_h
|
|
199
195
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
attr_reader :errors
|
|
196
|
+
def _definer
|
|
197
|
+
@definer
|
|
198
|
+
end
|
|
204
199
|
|
|
205
|
-
def
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
type = self.class.type
|
|
200
|
+
def _spreads
|
|
201
|
+
@definer.spreads
|
|
202
|
+
end
|
|
209
203
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
204
|
+
def source_definition
|
|
205
|
+
@definer.definition
|
|
206
|
+
end
|
|
213
207
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
208
|
+
def respond_to_missing?(name, priv)
|
|
209
|
+
if (attr = self.class::READERS[name]) || (attr = self.class::PREDICATES[name])
|
|
210
|
+
@definer.defined_fields.key?(attr) || super
|
|
211
|
+
else
|
|
212
|
+
super
|
|
217
213
|
end
|
|
214
|
+
end
|
|
218
215
|
|
|
219
|
-
|
|
220
|
-
|
|
216
|
+
# Public: Return errors associated with data.
|
|
217
|
+
#
|
|
218
|
+
# It's possible to define "errors" as a field. Ideally this shouldn't
|
|
219
|
+
# happen, but if it does we should prefer the field rather than the
|
|
220
|
+
# builtin error type.
|
|
221
|
+
#
|
|
222
|
+
# Returns Errors collection.
|
|
223
|
+
def errors
|
|
224
|
+
if type = @definer.defined_fields["errors"]
|
|
225
|
+
read_attribute("errors", type)
|
|
226
|
+
else
|
|
227
|
+
@errors
|
|
221
228
|
end
|
|
229
|
+
end
|
|
222
230
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
def method_missing(name, *args)
|
|
232
|
+
if (attr = self.class::READERS[name]) && (type = @definer.defined_fields[attr])
|
|
233
|
+
if @enforce_collocated_callers
|
|
234
|
+
verify_collocated_path do
|
|
235
|
+
read_attribute(attr, type)
|
|
236
|
+
end
|
|
237
|
+
else
|
|
238
|
+
read_attribute(attr, type)
|
|
239
|
+
end
|
|
240
|
+
elsif (attr = self.class::PREDICATES[name]) && @definer.defined_fields[attr]
|
|
241
|
+
has_attribute?(attr)
|
|
226
242
|
else
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
begin
|
|
244
|
+
super
|
|
245
|
+
rescue NoMethodError => e
|
|
246
|
+
type = self.class.type
|
|
230
247
|
|
|
231
|
-
|
|
248
|
+
if ActiveSupport::Inflector.underscore(e.name.to_s) != e.name.to_s
|
|
249
|
+
raise e
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
|
|
253
|
+
field = all_fields.find do |f|
|
|
254
|
+
f.name == e.name.to_s || ActiveSupport::Inflector.underscore(f.name) == e.name.to_s
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
unless field
|
|
258
|
+
raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type.graphql_name} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unimplemented-field-error.md"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if @data.key?(field.name)
|
|
262
|
+
raise ImplicitlyFetchedFieldError, "implicitly fetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md"
|
|
263
|
+
else
|
|
264
|
+
raise UnfetchedFieldError, "unfetched field `#{field.name}' on #{type} type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unfetched-field-error.md"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
232
268
|
end
|
|
233
269
|
|
|
234
270
|
def inspect
|
|
235
|
-
parent = self.class
|
|
271
|
+
parent = self.class
|
|
272
|
+
until parent.superclass == ObjectClass
|
|
273
|
+
parent = parent.superclass
|
|
274
|
+
end
|
|
236
275
|
|
|
237
276
|
ivars = @data.map { |key, value|
|
|
238
277
|
if value.is_a?(Hash) || value.is_a?(Array)
|
|
@@ -247,6 +286,26 @@ module GraphQL
|
|
|
247
286
|
buf << ">"
|
|
248
287
|
buf
|
|
249
288
|
end
|
|
289
|
+
|
|
290
|
+
private
|
|
291
|
+
|
|
292
|
+
def verify_collocated_path
|
|
293
|
+
location = caller_locations(2, 1)[0]
|
|
294
|
+
|
|
295
|
+
CollocatedEnforcement.verify_collocated_path(location, source_definition.source_location[0]) do
|
|
296
|
+
yield
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def read_attribute(attr, type)
|
|
301
|
+
@casted_data.fetch(attr) do
|
|
302
|
+
@casted_data[attr] = type.cast(@data[attr], @errors.filter_by_path(attr))
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def has_attribute?(attr)
|
|
307
|
+
!!@data[attr]
|
|
308
|
+
end
|
|
250
309
|
end
|
|
251
310
|
end
|
|
252
311
|
end
|
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def define_class(definition, ast_nodes)
|
|
24
|
-
possible_type_names = definition.client.
|
|
24
|
+
possible_type_names = definition.client.possible_types(type).map(&:graphql_name)
|
|
25
25
|
possible_types = possible_type_names.map { |concrete_type_name|
|
|
26
26
|
schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
|
|
27
27
|
}
|
|
@@ -43,7 +43,7 @@ module GraphQL
|
|
|
43
43
|
def set_class(type_name, klass)
|
|
44
44
|
class_name = normalize_type_name(type_name)
|
|
45
45
|
|
|
46
|
-
if
|
|
46
|
+
if const_defined?(class_name, false)
|
|
47
47
|
raise ArgumentError,
|
|
48
48
|
"Can't define #{class_name} to represent type #{type_name} " \
|
|
49
49
|
"because it's already defined"
|
|
@@ -66,9 +66,10 @@ module GraphQL
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
def self.generate(schema)
|
|
69
|
+
def self.generate(schema, raise_on_unknown_enum_value: true)
|
|
70
70
|
mod = Module.new
|
|
71
71
|
mod.extend ClassMethods
|
|
72
|
+
mod.define_singleton_method(:raise_on_unknown_enum_value) { raise_on_unknown_enum_value }
|
|
72
73
|
|
|
73
74
|
cache = {}
|
|
74
75
|
schema.types.each do |name, type|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
class Client
|
|
4
|
+
module TypeStack
|
|
5
|
+
# @return [GraphQL::Schema] the schema whose types are present in this document
|
|
6
|
+
attr_reader :schema
|
|
7
|
+
|
|
8
|
+
# When it enters an object (starting with query or mutation root), it's pushed on this stack.
|
|
9
|
+
# When it exits, it's popped off.
|
|
10
|
+
# @return [Array<GraphQL::ObjectType, GraphQL::Union, GraphQL::Interface>]
|
|
11
|
+
attr_reader :object_types
|
|
12
|
+
|
|
13
|
+
# When it enters a field, it's pushed on this stack (useful for nested fields, args).
|
|
14
|
+
# When it exits, it's popped off.
|
|
15
|
+
# @return [Array<GraphQL::Field>] fields which have been entered
|
|
16
|
+
attr_reader :field_definitions
|
|
17
|
+
|
|
18
|
+
# Directives are pushed on, then popped off while traversing the tree
|
|
19
|
+
# @return [Array<GraphQL::Node::Directive>] directives which have been entered
|
|
20
|
+
attr_reader :directive_definitions
|
|
21
|
+
|
|
22
|
+
# @return [Array<GraphQL::Node::Argument>] arguments which have been entered
|
|
23
|
+
attr_reader :argument_definitions
|
|
24
|
+
|
|
25
|
+
# @return [Array<String>] fields which have been entered (by their AST name)
|
|
26
|
+
attr_reader :path
|
|
27
|
+
|
|
28
|
+
# @param schema [GraphQL::Schema] the schema whose types to use when climbing this document
|
|
29
|
+
# @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types
|
|
30
|
+
def initialize(document, schema:, **rest)
|
|
31
|
+
@schema = schema
|
|
32
|
+
@object_types = []
|
|
33
|
+
@field_definitions = []
|
|
34
|
+
@directive_definitions = []
|
|
35
|
+
@argument_definitions = []
|
|
36
|
+
@path = []
|
|
37
|
+
super(document, **rest)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_directive(node, parent)
|
|
41
|
+
directive_defn = @schema.directives[node.name]
|
|
42
|
+
@directive_definitions.push(directive_defn)
|
|
43
|
+
super(node, parent)
|
|
44
|
+
ensure
|
|
45
|
+
@directive_definitions.pop
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def on_field(node, parent)
|
|
49
|
+
parent_type = @object_types.last
|
|
50
|
+
parent_type = parent_type.unwrap
|
|
51
|
+
|
|
52
|
+
field_definition = @schema.get_field(parent_type, node.name)
|
|
53
|
+
@field_definitions.push(field_definition)
|
|
54
|
+
if !field_definition.nil?
|
|
55
|
+
next_object_type = field_definition.type
|
|
56
|
+
@object_types.push(next_object_type)
|
|
57
|
+
else
|
|
58
|
+
@object_types.push(nil)
|
|
59
|
+
end
|
|
60
|
+
@path.push(node.alias || node.name)
|
|
61
|
+
super(node, parent)
|
|
62
|
+
ensure
|
|
63
|
+
@field_definitions.pop
|
|
64
|
+
@object_types.pop
|
|
65
|
+
@path.pop
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def on_argument(node, parent)
|
|
69
|
+
if @argument_definitions.last
|
|
70
|
+
arg_type = @argument_definitions.last.type.unwrap
|
|
71
|
+
if arg_type.kind.input_object?
|
|
72
|
+
argument_defn = arg_type.arguments[node.name]
|
|
73
|
+
else
|
|
74
|
+
argument_defn = nil
|
|
75
|
+
end
|
|
76
|
+
elsif @directive_definitions.last
|
|
77
|
+
argument_defn = @directive_definitions.last.arguments[node.name]
|
|
78
|
+
elsif @field_definitions.last
|
|
79
|
+
argument_defn = @field_definitions.last.arguments[node.name]
|
|
80
|
+
else
|
|
81
|
+
argument_defn = nil
|
|
82
|
+
end
|
|
83
|
+
@argument_definitions.push(argument_defn)
|
|
84
|
+
@path.push(node.name)
|
|
85
|
+
super(node, parent)
|
|
86
|
+
ensure
|
|
87
|
+
@argument_definitions.pop
|
|
88
|
+
@path.pop
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def on_operation_definition(node, parent)
|
|
92
|
+
# eg, QueryType, MutationType
|
|
93
|
+
object_type = @schema.root_type_for_operation(node.operation_type)
|
|
94
|
+
@object_types.push(object_type)
|
|
95
|
+
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
|
|
96
|
+
super(node, parent)
|
|
97
|
+
ensure
|
|
98
|
+
@object_types.pop
|
|
99
|
+
@path.pop
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def on_inline_fragment(node, parent)
|
|
103
|
+
object_type = if node.type
|
|
104
|
+
@schema.get_type(node.type.name)
|
|
105
|
+
else
|
|
106
|
+
@object_types.last
|
|
107
|
+
end
|
|
108
|
+
if !object_type.nil?
|
|
109
|
+
object_type = object_type.unwrap
|
|
110
|
+
end
|
|
111
|
+
@object_types.push(object_type)
|
|
112
|
+
@path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
|
|
113
|
+
super(node, parent)
|
|
114
|
+
ensure
|
|
115
|
+
@object_types.pop
|
|
116
|
+
@path.pop
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def on_fragment_definition(node, parent)
|
|
120
|
+
object_type = if node.type
|
|
121
|
+
@schema.get_type(node.type.name)
|
|
122
|
+
else
|
|
123
|
+
@object_types.last
|
|
124
|
+
end
|
|
125
|
+
if !object_type.nil?
|
|
126
|
+
object_type = object_type.unwrap
|
|
127
|
+
end
|
|
128
|
+
@object_types.push(object_type)
|
|
129
|
+
@path.push("fragment #{node.name}")
|
|
130
|
+
super(node, parent)
|
|
131
|
+
ensure
|
|
132
|
+
@object_types.pop
|
|
133
|
+
@path.pop
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def on_fragment_spread(node, parent)
|
|
137
|
+
@path.push("... #{node.name}")
|
|
138
|
+
super(node, parent)
|
|
139
|
+
ensure
|
|
140
|
+
@path.pop
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|