graphql 2.5.22 → 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 +93 -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 +3 -1
- 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 +3 -4
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +5 -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
|
@@ -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,
|
|
@@ -10,9 +10,12 @@ module GraphQL
|
|
|
10
10
|
if !@types.directive_exists?(node.name)
|
|
11
11
|
@directives_are_defined_errors_by_name ||= {}
|
|
12
12
|
error = @directives_are_defined_errors_by_name[node.name] ||= begin
|
|
13
|
-
|
|
13
|
+
suggestion = if @schema.did_you_mean
|
|
14
|
+
@directive_names ||= @types.directives.map(&:graphql_name)
|
|
15
|
+
context.did_you_mean_suggestion(node.name, @directive_names)
|
|
16
|
+
end
|
|
14
17
|
err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
|
|
15
|
-
"Directive @#{node.name} is not defined#{
|
|
18
|
+
"Directive @#{node.name} is not defined#{suggestion}",
|
|
16
19
|
nodes: [],
|
|
17
20
|
directive: node.name
|
|
18
21
|
)
|
|
@@ -3,7 +3,7 @@ module GraphQL
|
|
|
3
3
|
module StaticValidation
|
|
4
4
|
module FieldsAreDefinedOnType
|
|
5
5
|
def on_field(node, parent)
|
|
6
|
-
parent_type = @
|
|
6
|
+
parent_type = @parent_object_type
|
|
7
7
|
field = context.query.types.field(parent_type, node.name)
|
|
8
8
|
|
|
9
9
|
if field.nil?
|
|
@@ -14,8 +14,9 @@ module GraphQL
|
|
|
14
14
|
node_name: parent_type.graphql_name
|
|
15
15
|
))
|
|
16
16
|
else
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
suggestion = if @schema.did_you_mean
|
|
18
|
+
context.did_you_mean_suggestion(node.name, possible_fields(context, parent_type))
|
|
19
|
+
end
|
|
19
20
|
message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{suggestion}"
|
|
20
21
|
add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new(
|
|
21
22
|
message,
|
|
@@ -7,8 +7,7 @@ module GraphQL
|
|
|
7
7
|
include GraphQL::StaticValidation::Error::ErrorHelper
|
|
8
8
|
|
|
9
9
|
def on_field(node, parent)
|
|
10
|
-
|
|
11
|
-
if validate_field_selections(node, field_defn.type.unwrap)
|
|
10
|
+
if validate_field_selections(node, @current_object_type)
|
|
12
11
|
super
|
|
13
12
|
end
|
|
14
13
|
end
|
|
@@ -23,6 +22,17 @@ module GraphQL
|
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
def validate_field_selections(ast_node, resolved_type)
|
|
25
|
+
# Fast paths for the two most common cases:
|
|
26
|
+
# 1. Leaf type with no selections (scalars, enums) — most fields
|
|
27
|
+
# 2. Non-leaf type with selections (objects, interfaces)
|
|
28
|
+
if resolved_type
|
|
29
|
+
if ast_node.selections.empty?
|
|
30
|
+
return true if resolved_type.kind.leaf?
|
|
31
|
+
else
|
|
32
|
+
return true unless resolved_type.kind.leaf?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
26
36
|
msg = if resolved_type.nil?
|
|
27
37
|
nil
|
|
28
38
|
elsif resolved_type.kind.leaf?
|