graphql 2.5.21 → 2.5.23
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/lib/graphql/execution/interpreter/runtime.rb +3 -2
- data/lib/graphql/execution/interpreter.rb +6 -9
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/next/field_resolve_step.rb +114 -61
- data/lib/graphql/execution/next/load_argument_step.rb +5 -1
- data/lib/graphql/execution/next/prepare_object_step.rb +2 -2
- data/lib/graphql/execution/next/runner.rb +48 -26
- data/lib/graphql/execution/next.rb +5 -2
- data/lib/graphql/execution.rb +7 -4
- data/lib/graphql/execution_error.rb +5 -1
- data/lib/graphql/query/context.rb +1 -1
- data/lib/graphql/schema/field.rb +8 -5
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/has_dataloader.rb +20 -0
- data/lib/graphql/schema/member/has_fields.rb +6 -1
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/resolver.rb +18 -3
- data/lib/graphql/schema/subscription.rb +0 -2
- data/lib/graphql/schema/visibility/profile.rb +68 -49
- data/lib/graphql/schema/wrapper.rb +7 -1
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +25 -1
- data/lib/graphql/subscriptions/event.rb +1 -0
- data/lib/graphql/subscriptions.rb +20 -0
- data/lib/graphql/tracing/perfetto_trace.rb +2 -2
- data/lib/graphql/unauthorized_error.rb +4 -0
- data/lib/graphql/version.rb +1 -1
- metadata +3 -3
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -197,6 +197,7 @@ module GraphQL
|
|
|
197
197
|
# @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
198
198
|
# @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
199
199
|
# @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
|
|
200
|
+
# @param dataload [Class, Hash] Shorthand for making dataloader calls
|
|
200
201
|
# @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
201
202
|
# @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
|
202
203
|
# @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
|
@@ -219,7 +220,7 @@ module GraphQL
|
|
|
219
220
|
# @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
|
|
220
221
|
# @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field
|
|
221
222
|
# @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
|
|
222
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
223
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
223
224
|
if name.nil?
|
|
224
225
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
225
226
|
end
|
|
@@ -289,6 +290,9 @@ module GraphQL
|
|
|
289
290
|
elsif resolve_legacy_instance_method
|
|
290
291
|
@execution_next_mode = :resolve_legacy_instance_method
|
|
291
292
|
@execution_next_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
|
|
293
|
+
elsif dataload
|
|
294
|
+
@execution_next_mode = :dataload
|
|
295
|
+
@execution_next_mode_key = dataload
|
|
292
296
|
else
|
|
293
297
|
@execution_next_mode = :direct_send
|
|
294
298
|
@execution_next_mode_key = @method_sym
|
|
@@ -661,10 +665,9 @@ module GraphQL
|
|
|
661
665
|
end
|
|
662
666
|
|
|
663
667
|
def authorizes?(context)
|
|
664
|
-
method(:authorized?).owner != GraphQL::Schema::Field ||
|
|
665
|
-
(args = context.types.arguments(self)) &&
|
|
666
|
-
|
|
667
|
-
)
|
|
668
|
+
method(:authorized?).owner != GraphQL::Schema::Field ||
|
|
669
|
+
((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) ||
|
|
670
|
+
(@resolver_class&.authorizes?(context)) || false
|
|
668
671
|
end
|
|
669
672
|
|
|
670
673
|
def authorized?(object, args, context)
|
data/lib/graphql/schema/list.rb
CHANGED
|
@@ -56,6 +56,16 @@ module GraphQL
|
|
|
56
56
|
source.load(find_by_value)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# @see dataload_record Like `dataload_record`, but accepts an Array of `find_by_values`
|
|
60
|
+
def dataload_all_records(model, find_by_values, find_by: nil)
|
|
61
|
+
source = if find_by
|
|
62
|
+
dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
|
|
63
|
+
else
|
|
64
|
+
dataloader.with(Dataloader::ActiveRecordSource, model)
|
|
65
|
+
end
|
|
66
|
+
source.load_all(find_by_values)
|
|
67
|
+
end
|
|
68
|
+
|
|
59
69
|
# Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
|
|
60
70
|
# @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
|
|
61
71
|
# @param record [ActiveRecord::Base] The object that the association belongs to.
|
|
@@ -73,6 +83,16 @@ module GraphQL
|
|
|
73
83
|
end
|
|
74
84
|
source.load(record)
|
|
75
85
|
end
|
|
86
|
+
|
|
87
|
+
# @see dataload_association Like `dataload_assocation` but accepts an Array of records (required param)
|
|
88
|
+
def dataload_all_associations(records, association_name, scope: nil)
|
|
89
|
+
source = if scope
|
|
90
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
|
|
91
|
+
else
|
|
92
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
|
|
93
|
+
end
|
|
94
|
+
source.load_all(records)
|
|
95
|
+
end
|
|
76
96
|
end
|
|
77
97
|
end
|
|
78
98
|
end
|
|
@@ -48,6 +48,7 @@ module GraphQL
|
|
|
48
48
|
# @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby)
|
|
49
49
|
# @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby)
|
|
50
50
|
# @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby)
|
|
51
|
+
# @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups
|
|
51
52
|
# @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field
|
|
52
53
|
# @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled.
|
|
53
54
|
# @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
|
|
@@ -149,10 +150,14 @@ module GraphQL
|
|
|
149
150
|
|
|
150
151
|
def global_id_field(field_name, **kwargs)
|
|
151
152
|
type = self
|
|
152
|
-
field field_name, "ID", **kwargs, null: false
|
|
153
|
+
field field_name, "ID", **kwargs, null: false, resolve_each: true
|
|
153
154
|
define_method(field_name) do
|
|
154
155
|
context.schema.id_from_object(object, type, context)
|
|
155
156
|
end
|
|
157
|
+
|
|
158
|
+
define_singleton_method(field_name) do |object, context|
|
|
159
|
+
context.schema.id_from_object(object, type, context)
|
|
160
|
+
end
|
|
156
161
|
end
|
|
157
162
|
|
|
158
163
|
# @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
|
|
@@ -46,7 +46,7 @@ module GraphQL
|
|
|
46
46
|
@prepared_arguments = nil
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
attr_accessor :exec_result, :exec_index, :field_resolve_step
|
|
49
|
+
attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments
|
|
50
50
|
|
|
51
51
|
# @return [Object] The application object this field is being resolved on
|
|
52
52
|
attr_accessor :object
|
|
@@ -66,7 +66,12 @@ module GraphQL
|
|
|
66
66
|
q = context.query
|
|
67
67
|
trace_objs = [object]
|
|
68
68
|
q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
|
|
69
|
-
|
|
69
|
+
begin
|
|
70
|
+
is_authed, new_return_value = authorized?(**@prepared_arguments)
|
|
71
|
+
rescue GraphQL::UnauthorizedError => err
|
|
72
|
+
new_return_value = q.schema.unauthorized_object(err)
|
|
73
|
+
is_authed = true # the error was handled
|
|
74
|
+
end
|
|
70
75
|
|
|
71
76
|
if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
|
|
72
77
|
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
|
|
@@ -74,13 +79,19 @@ module GraphQL
|
|
|
74
79
|
|
|
75
80
|
result = if is_authed
|
|
76
81
|
Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
|
|
82
|
+
if q.subscription? && @field.owner == context.schema.subscription
|
|
83
|
+
# This needs to use arguments without `loads:`
|
|
84
|
+
@original_arguments = @field_resolve_step.coerce_arguments(@field, @field_resolve_step.ast_node.arguments, false)
|
|
85
|
+
end
|
|
77
86
|
call_resolve(@prepared_arguments)
|
|
87
|
+
elsif new_return_value.nil?
|
|
88
|
+
err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
|
|
89
|
+
context.schema.unauthorized_field(err)
|
|
78
90
|
else
|
|
79
91
|
new_return_value
|
|
80
92
|
end
|
|
81
93
|
q = context.query
|
|
82
94
|
q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
|
|
83
|
-
|
|
84
95
|
exec_result[exec_index] = result
|
|
85
96
|
rescue RuntimeError => err
|
|
86
97
|
exec_result[exec_index] = err
|
|
@@ -198,6 +209,10 @@ module GraphQL
|
|
|
198
209
|
authorize_arguments(args, inputs)
|
|
199
210
|
end
|
|
200
211
|
|
|
212
|
+
def self.authorizes?(context)
|
|
213
|
+
self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
|
|
214
|
+
end
|
|
215
|
+
|
|
201
216
|
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
|
202
217
|
#
|
|
203
218
|
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
|
@@ -15,8 +15,6 @@ module GraphQL
|
|
|
15
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
|
16
16
|
extend GraphQL::Schema::Member::HasFields
|
|
17
17
|
NO_UPDATE = :no_update
|
|
18
|
-
# The generated payload type is required; If there's no payload,
|
|
19
|
-
# propagate null.
|
|
20
18
|
null false
|
|
21
19
|
|
|
22
20
|
# @api private
|
|
@@ -54,6 +54,9 @@ module GraphQL
|
|
|
54
54
|
@cached_fields.default_proc = nil
|
|
55
55
|
@cached_arguments.default_proc = nil
|
|
56
56
|
@loadable_possible_types.default_proc = nil
|
|
57
|
+
@cached_field_result.default_proc = nil
|
|
58
|
+
@cached_field_result.each { |_, h| h.default_proc = nil }
|
|
59
|
+
@cached_type_result.default_proc = nil
|
|
57
60
|
super
|
|
58
61
|
end
|
|
59
62
|
|
|
@@ -122,6 +125,14 @@ module GraphQL
|
|
|
122
125
|
end.compare_by_identity
|
|
123
126
|
|
|
124
127
|
@loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
|
|
128
|
+
|
|
129
|
+
# Combined cache for field(owner, field_name) — avoids repeated kind check + parent lookup + visibility check
|
|
130
|
+
@cached_field_result = Hash.new { |h, owner|
|
|
131
|
+
h[owner] = Hash.new { |h2, field_name| h2[field_name] = compute_field(owner, field_name) }
|
|
132
|
+
}.compare_by_identity
|
|
133
|
+
|
|
134
|
+
# Cache for type(type_name) — avoids repeated get_type + visibility + referenced? checks
|
|
135
|
+
@cached_type_result = Hash.new { |h, type_name| h[type_name] = compute_type(type_name) }
|
|
125
136
|
end
|
|
126
137
|
|
|
127
138
|
def field_on_visible_interface?(field, owner)
|
|
@@ -149,58 +160,11 @@ module GraphQL
|
|
|
149
160
|
end
|
|
150
161
|
|
|
151
162
|
def type(type_name)
|
|
152
|
-
|
|
153
|
-
if t
|
|
154
|
-
if t.is_a?(Array)
|
|
155
|
-
vis_t = nil
|
|
156
|
-
t.each do |t_defn|
|
|
157
|
-
if @cached_visible[t_defn] && referenced?(t_defn)
|
|
158
|
-
if vis_t.nil?
|
|
159
|
-
vis_t = t_defn
|
|
160
|
-
else
|
|
161
|
-
raise_duplicate_definition(vis_t, t_defn)
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
vis_t
|
|
166
|
-
else
|
|
167
|
-
if t && @cached_visible[t] && referenced?(t)
|
|
168
|
-
t
|
|
169
|
-
else
|
|
170
|
-
nil
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
163
|
+
@cached_type_result[type_name]
|
|
174
164
|
end
|
|
175
165
|
|
|
176
166
|
def field(owner, field_name)
|
|
177
|
-
|
|
178
|
-
field
|
|
179
|
-
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
|
180
|
-
entry_point_field
|
|
181
|
-
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
|
182
|
-
dynamic_field
|
|
183
|
-
else
|
|
184
|
-
nil
|
|
185
|
-
end
|
|
186
|
-
if f.is_a?(Array)
|
|
187
|
-
visible_f = nil
|
|
188
|
-
f.each do |f_defn|
|
|
189
|
-
if @cached_visible_fields[owner][f_defn]
|
|
190
|
-
|
|
191
|
-
if visible_f.nil?
|
|
192
|
-
visible_f = f_defn
|
|
193
|
-
else
|
|
194
|
-
raise_duplicate_definition(visible_f, f_defn)
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
visible_f&.ensure_loaded
|
|
199
|
-
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
|
200
|
-
f
|
|
201
|
-
else
|
|
202
|
-
nil
|
|
203
|
-
end
|
|
167
|
+
@cached_field_result[owner][field_name]
|
|
204
168
|
end
|
|
205
169
|
|
|
206
170
|
def fields(owner)
|
|
@@ -306,6 +270,7 @@ module GraphQL
|
|
|
306
270
|
def preload
|
|
307
271
|
load_all_types
|
|
308
272
|
@all_types.each do |type_name, type_defn|
|
|
273
|
+
type(type_name)
|
|
309
274
|
if type_defn.kind.fields?
|
|
310
275
|
fields(type_defn).each do |f|
|
|
311
276
|
field(type_defn, f.graphql_name)
|
|
@@ -341,6 +306,60 @@ module GraphQL
|
|
|
341
306
|
|
|
342
307
|
private
|
|
343
308
|
|
|
309
|
+
def compute_type(type_name)
|
|
310
|
+
t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
311
|
+
if t
|
|
312
|
+
if t.is_a?(Array)
|
|
313
|
+
vis_t = nil
|
|
314
|
+
t.each do |t_defn|
|
|
315
|
+
if @cached_visible[t_defn] && referenced?(t_defn)
|
|
316
|
+
if vis_t.nil?
|
|
317
|
+
vis_t = t_defn
|
|
318
|
+
else
|
|
319
|
+
raise_duplicate_definition(vis_t, t_defn)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
vis_t
|
|
324
|
+
else
|
|
325
|
+
if t && @cached_visible[t] && referenced?(t)
|
|
326
|
+
t
|
|
327
|
+
else
|
|
328
|
+
nil
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def compute_field(owner, field_name)
|
|
335
|
+
f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
|
|
336
|
+
field
|
|
337
|
+
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
|
338
|
+
entry_point_field
|
|
339
|
+
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
|
340
|
+
dynamic_field
|
|
341
|
+
else
|
|
342
|
+
nil
|
|
343
|
+
end
|
|
344
|
+
if f.is_a?(Array)
|
|
345
|
+
visible_f = nil
|
|
346
|
+
f.each do |f_defn|
|
|
347
|
+
if @cached_visible_fields[owner][f_defn]
|
|
348
|
+
if visible_f.nil?
|
|
349
|
+
visible_f = f_defn
|
|
350
|
+
else
|
|
351
|
+
raise_duplicate_definition(visible_f, f_defn)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
visible_f&.ensure_loaded
|
|
356
|
+
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
|
357
|
+
f
|
|
358
|
+
else
|
|
359
|
+
nil
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
344
363
|
def non_duplicate_items(definitions, visibility_cache)
|
|
345
364
|
non_dups = []
|
|
346
365
|
names = Set.new
|
|
@@ -4,25 +4,26 @@ module GraphQL
|
|
|
4
4
|
class BaseVisitor < GraphQL::Language::StaticVisitor
|
|
5
5
|
def initialize(document, context)
|
|
6
6
|
@path = []
|
|
7
|
-
@
|
|
8
|
-
@
|
|
9
|
-
@
|
|
10
|
-
@
|
|
11
|
-
@
|
|
7
|
+
@path_depth = 0
|
|
8
|
+
@current_object_type = nil
|
|
9
|
+
@parent_object_type = nil
|
|
10
|
+
@current_field_definition = nil
|
|
11
|
+
@current_argument_definition = nil
|
|
12
|
+
@parent_argument_definition = nil
|
|
13
|
+
@current_directive_definition = nil
|
|
12
14
|
@context = context
|
|
13
15
|
@types = context.query.types
|
|
14
16
|
@schema = context.schema
|
|
17
|
+
@inline_fragment_paths = {}
|
|
18
|
+
@field_unwrapped_types = {}.compare_by_identity
|
|
15
19
|
super(document)
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
attr_reader :context
|
|
19
23
|
|
|
20
|
-
# @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
|
|
21
|
-
attr_reader :object_types
|
|
22
|
-
|
|
23
24
|
# @return [Array<String>] The nesting of the current position in the AST
|
|
24
25
|
def path
|
|
25
|
-
@path
|
|
26
|
+
@path[0, @path_depth]
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
# Build a class to visit the AST and perform validation,
|
|
@@ -55,86 +56,125 @@ module GraphQL
|
|
|
55
56
|
module ContextMethods
|
|
56
57
|
def on_operation_definition(node, parent)
|
|
57
58
|
object_type = @schema.root_type_for_operation(node.operation_type)
|
|
58
|
-
|
|
59
|
-
@
|
|
59
|
+
prev_parent_ot = @parent_object_type
|
|
60
|
+
@parent_object_type = @current_object_type
|
|
61
|
+
@current_object_type = object_type
|
|
62
|
+
@path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}"
|
|
63
|
+
@path_depth += 1
|
|
60
64
|
super
|
|
61
|
-
@
|
|
62
|
-
@
|
|
65
|
+
@current_object_type = @parent_object_type
|
|
66
|
+
@parent_object_type = prev_parent_ot
|
|
67
|
+
@path_depth -= 1
|
|
63
68
|
end
|
|
64
69
|
|
|
65
70
|
def on_fragment_definition(node, parent)
|
|
66
|
-
|
|
67
|
-
@
|
|
68
|
-
|
|
71
|
+
object_type = if node.type
|
|
72
|
+
@types.type(node.type.name)
|
|
73
|
+
else
|
|
74
|
+
@current_object_type
|
|
69
75
|
end
|
|
76
|
+
prev_parent_ot = @parent_object_type
|
|
77
|
+
@parent_object_type = @current_object_type
|
|
78
|
+
@current_object_type = object_type
|
|
79
|
+
@path[@path_depth] = "fragment #{node.name}"
|
|
80
|
+
@path_depth += 1
|
|
81
|
+
super
|
|
82
|
+
@current_object_type = @parent_object_type
|
|
83
|
+
@parent_object_type = prev_parent_ot
|
|
84
|
+
@path_depth -= 1
|
|
70
85
|
end
|
|
71
86
|
|
|
87
|
+
INLINE_FRAGMENT_NO_TYPE = "..."
|
|
88
|
+
|
|
72
89
|
def on_inline_fragment(node, parent)
|
|
73
|
-
|
|
74
|
-
@
|
|
75
|
-
|
|
90
|
+
if node.type
|
|
91
|
+
object_type = @types.type(node.type.name)
|
|
92
|
+
@path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}"
|
|
93
|
+
@path_depth += 1
|
|
94
|
+
else
|
|
95
|
+
object_type = @current_object_type
|
|
96
|
+
@path[@path_depth] = INLINE_FRAGMENT_NO_TYPE
|
|
97
|
+
@path_depth += 1
|
|
76
98
|
end
|
|
99
|
+
prev_parent_ot = @parent_object_type
|
|
100
|
+
@parent_object_type = @current_object_type
|
|
101
|
+
@current_object_type = object_type
|
|
102
|
+
super
|
|
103
|
+
@current_object_type = @parent_object_type
|
|
104
|
+
@parent_object_type = prev_parent_ot
|
|
105
|
+
@path_depth -= 1
|
|
77
106
|
end
|
|
78
107
|
|
|
79
108
|
def on_field(node, parent)
|
|
80
|
-
parent_type = @
|
|
109
|
+
parent_type = @current_object_type
|
|
81
110
|
field_definition = @types.field(parent_type, node.name)
|
|
82
|
-
@
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
111
|
+
prev_field_definition = @current_field_definition
|
|
112
|
+
@current_field_definition = field_definition
|
|
113
|
+
prev_parent_ot = @parent_object_type
|
|
114
|
+
@parent_object_type = @current_object_type
|
|
115
|
+
if field_definition
|
|
116
|
+
@current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap
|
|
86
117
|
else
|
|
87
|
-
|
|
118
|
+
@current_object_type = nil
|
|
88
119
|
end
|
|
89
|
-
@path
|
|
120
|
+
@path[@path_depth] = node.alias || node.name
|
|
121
|
+
@path_depth += 1
|
|
90
122
|
super
|
|
91
|
-
@
|
|
92
|
-
@
|
|
93
|
-
@
|
|
123
|
+
@current_field_definition = prev_field_definition
|
|
124
|
+
@current_object_type = @parent_object_type
|
|
125
|
+
@parent_object_type = prev_parent_ot
|
|
126
|
+
@path_depth -= 1
|
|
94
127
|
end
|
|
95
128
|
|
|
96
129
|
def on_directive(node, parent)
|
|
97
130
|
directive_defn = @context.schema_directives[node.name]
|
|
98
|
-
@
|
|
131
|
+
prev_directive_definition = @current_directive_definition
|
|
132
|
+
@current_directive_definition = directive_defn
|
|
99
133
|
super
|
|
100
|
-
@
|
|
134
|
+
@current_directive_definition = prev_directive_definition
|
|
101
135
|
end
|
|
102
136
|
|
|
103
137
|
def on_argument(node, parent)
|
|
104
|
-
argument_defn = if (arg = @
|
|
138
|
+
argument_defn = if (arg = @current_argument_definition)
|
|
105
139
|
arg_type = arg.type.unwrap
|
|
106
140
|
if arg_type.kind.input_object?
|
|
107
141
|
@types.argument(arg_type, node.name)
|
|
108
142
|
else
|
|
109
143
|
nil
|
|
110
144
|
end
|
|
111
|
-
elsif (directive_defn = @
|
|
145
|
+
elsif (directive_defn = @current_directive_definition)
|
|
112
146
|
@types.argument(directive_defn, node.name)
|
|
113
|
-
elsif (field_defn = @
|
|
147
|
+
elsif (field_defn = @current_field_definition)
|
|
114
148
|
@types.argument(field_defn, node.name)
|
|
115
149
|
else
|
|
116
150
|
nil
|
|
117
151
|
end
|
|
118
152
|
|
|
119
|
-
@
|
|
120
|
-
@
|
|
153
|
+
prev_parent = @parent_argument_definition
|
|
154
|
+
@parent_argument_definition = @current_argument_definition
|
|
155
|
+
@current_argument_definition = argument_defn
|
|
156
|
+
@path[@path_depth] = node.name
|
|
157
|
+
@path_depth += 1
|
|
121
158
|
super
|
|
122
|
-
@
|
|
123
|
-
@
|
|
159
|
+
@current_argument_definition = @parent_argument_definition
|
|
160
|
+
@parent_argument_definition = prev_parent
|
|
161
|
+
@path_depth -= 1
|
|
124
162
|
end
|
|
125
163
|
|
|
126
164
|
def on_fragment_spread(node, parent)
|
|
127
|
-
@path
|
|
165
|
+
@path[@path_depth] = "... #{node.name}"
|
|
166
|
+
@path_depth += 1
|
|
128
167
|
super
|
|
129
|
-
@
|
|
168
|
+
@path_depth -= 1
|
|
130
169
|
end
|
|
131
170
|
|
|
132
171
|
def on_input_object(node, parent)
|
|
133
|
-
arg_defn = @
|
|
172
|
+
arg_defn = @current_argument_definition
|
|
134
173
|
if arg_defn && arg_defn.type.list?
|
|
135
|
-
@path
|
|
174
|
+
@path[@path_depth] = parent.children.index(node)
|
|
175
|
+
@path_depth += 1
|
|
136
176
|
super
|
|
137
|
-
@
|
|
177
|
+
@path_depth -= 1
|
|
138
178
|
else
|
|
139
179
|
super
|
|
140
180
|
end
|
|
@@ -142,48 +182,32 @@ module GraphQL
|
|
|
142
182
|
|
|
143
183
|
# @return [GraphQL::BaseType] The current object type
|
|
144
184
|
def type_definition
|
|
145
|
-
@
|
|
185
|
+
@current_object_type
|
|
146
186
|
end
|
|
147
187
|
|
|
148
188
|
# @return [GraphQL::BaseType] The type which the current type came from
|
|
149
189
|
def parent_type_definition
|
|
150
|
-
@
|
|
190
|
+
@parent_object_type
|
|
151
191
|
end
|
|
152
192
|
|
|
153
193
|
# @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
|
|
154
194
|
def field_definition
|
|
155
|
-
@
|
|
195
|
+
@current_field_definition
|
|
156
196
|
end
|
|
157
197
|
|
|
158
198
|
# @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
|
|
159
199
|
def directive_definition
|
|
160
|
-
@
|
|
200
|
+
@current_directive_definition
|
|
161
201
|
end
|
|
162
202
|
|
|
163
203
|
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
|
|
164
204
|
def argument_definition
|
|
165
|
-
#
|
|
166
|
-
|
|
167
|
-
@argument_definitions[-2]
|
|
205
|
+
# Return the parent argument definition (not the current one).
|
|
206
|
+
@parent_argument_definition
|
|
168
207
|
end
|
|
169
208
|
|
|
170
209
|
private
|
|
171
210
|
|
|
172
|
-
def on_fragment_with_type(node)
|
|
173
|
-
object_type = if node.type
|
|
174
|
-
@types.type(node.type.name)
|
|
175
|
-
else
|
|
176
|
-
@object_types.last
|
|
177
|
-
end
|
|
178
|
-
push_type(object_type)
|
|
179
|
-
yield(node)
|
|
180
|
-
@object_types.pop
|
|
181
|
-
@path.pop
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def push_type(t)
|
|
185
|
-
@object_types.push(t)
|
|
186
|
-
end
|
|
187
211
|
end
|
|
188
212
|
|
|
189
213
|
private
|
|
@@ -192,7 +216,7 @@ module GraphQL
|
|
|
192
216
|
if @context.too_many_errors?
|
|
193
217
|
throw :too_many_validation_errors
|
|
194
218
|
end
|
|
195
|
-
error.path ||= (path || @path
|
|
219
|
+
error.path ||= (path || @path[0, @path_depth])
|
|
196
220
|
context.errors << error
|
|
197
221
|
end
|
|
198
222
|
|
|
@@ -12,7 +12,7 @@ module GraphQL
|
|
|
12
12
|
return
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@
|
|
15
|
+
if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path_depth) == @path[0, @path_depth] }
|
|
16
16
|
parent_defn = parent_definition(parent)
|
|
17
17
|
|
|
18
18
|
if parent_defn && (arg_defn = @types.argument(parent_defn, node.name))
|
|
@@ -16,12 +16,24 @@ module GraphQL
|
|
|
16
16
|
|
|
17
17
|
def validate_arguments(node)
|
|
18
18
|
argument_defns = node.arguments
|
|
19
|
-
if
|
|
20
|
-
|
|
21
|
-
argument_defns.each
|
|
22
|
-
|
|
23
|
-
if
|
|
24
|
-
|
|
19
|
+
if argument_defns.size > 1
|
|
20
|
+
seen = {}
|
|
21
|
+
argument_defns.each do |a|
|
|
22
|
+
name = a.name
|
|
23
|
+
if seen.key?(name)
|
|
24
|
+
prev = seen[name]
|
|
25
|
+
if prev.is_a?(Array)
|
|
26
|
+
prev << a
|
|
27
|
+
else
|
|
28
|
+
seen[name] = [prev, a]
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
seen[name] = a
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
seen.each do |name, val|
|
|
35
|
+
if val.is_a?(Array)
|
|
36
|
+
add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: val, name: name))
|
|
25
37
|
end
|
|
26
38
|
end
|
|
27
39
|
end
|
|
@@ -10,9 +10,12 @@ module GraphQL
|
|
|
10
10
|
elsif parent_defn
|
|
11
11
|
kind_of_node = node_type(parent)
|
|
12
12
|
error_arg_name = parent_name(parent, parent_defn)
|
|
13
|
-
|
|
13
|
+
suggestion = if @schema.did_you_mean
|
|
14
|
+
arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
|
|
15
|
+
context.did_you_mean_suggestion(node.name, arg_names)
|
|
16
|
+
end
|
|
14
17
|
add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new(
|
|
15
|
-
"#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{
|
|
18
|
+
"#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{suggestion}",
|
|
16
19
|
nodes: node,
|
|
17
20
|
name: error_arg_name,
|
|
18
21
|
type: kind_of_node,
|