graphql 2.3.12 → 2.3.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/execution/interpreter/runtime.rb +21 -11
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +129 -0
- data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
- data/lib/graphql/rubocop.rb +2 -0
- data/lib/graphql/schema/addition.rb +1 -0
- data/lib/graphql/schema/enum.rb +41 -14
- data/lib/graphql/schema/field.rb +72 -38
- data/lib/graphql/schema/member/has_fields.rb +3 -3
- data/lib/graphql/schema/subset.rb +6 -7
- data/lib/graphql/schema/types_migration.rb +2 -0
- data/lib/graphql/schema/validator/all_validator.rb +2 -0
- data/lib/graphql/schema.rb +38 -19
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a0c7d49f75d9f1740b415ca0c088733fa4ddf380da43dcd469d20654d89ad93
|
4
|
+
data.tar.gz: 0bf6ad1c22adc0a8d9e948287a751a561fb7203ca02cb902341b13ad3df40db1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73583aaf16bda54678168edf89e471ee1b3b54d7a2f1aa1bc0c9b959113000768a8d12a8f3ffeec29561639917fcb694ec2fd8c25f22dd8eaf33c46133f40160
|
7
|
+
data.tar.gz: 615f46f473d89526106f9d85d3560052dacfd35f5f31119a037fa2b1fda8f92445bd15c5993df3ebce8af10df9907a2d41576cc4444822df0606a13b0d9ed7c9
|
@@ -207,22 +207,32 @@ module GraphQL
|
|
207
207
|
finished_jobs = 0
|
208
208
|
enqueued_jobs = gathered_selections.size
|
209
209
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
210
|
-
|
211
|
-
evaluate_selection(
|
212
|
-
result_name, field_ast_nodes_or_ast_node, selections_result
|
213
|
-
)
|
214
|
-
finished_jobs += 1
|
215
|
-
if target_result && finished_jobs == enqueued_jobs
|
216
|
-
selections_result.merge_into(target_result)
|
217
|
-
end
|
218
|
-
}
|
210
|
+
|
219
211
|
# Field resolution may pause the fiber,
|
220
212
|
# so it wouldn't get to the `Resolve` call that happens below.
|
221
213
|
# So instead trigger a run from this outer context.
|
222
214
|
if selections_result.graphql_is_eager
|
223
215
|
@dataloader.clear_cache
|
224
|
-
@dataloader.
|
225
|
-
|
216
|
+
@dataloader.run_isolated {
|
217
|
+
evaluate_selection(
|
218
|
+
result_name, field_ast_nodes_or_ast_node, selections_result
|
219
|
+
)
|
220
|
+
finished_jobs += 1
|
221
|
+
if target_result && finished_jobs == enqueued_jobs
|
222
|
+
selections_result.merge_into(target_result)
|
223
|
+
end
|
224
|
+
@dataloader.clear_cache
|
225
|
+
}
|
226
|
+
else
|
227
|
+
@dataloader.append_job {
|
228
|
+
evaluate_selection(
|
229
|
+
result_name, field_ast_nodes_or_ast_node, selections_result
|
230
|
+
)
|
231
|
+
finished_jobs += 1
|
232
|
+
if target_result && finished_jobs == enqueued_jobs
|
233
|
+
selections_result.merge_into(target_result)
|
234
|
+
end
|
235
|
+
}
|
226
236
|
end
|
227
237
|
end
|
228
238
|
selections_result
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./base_cop"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Rubocop
|
6
|
+
module GraphQL
|
7
|
+
# Identify (and auto-correct) any field whose type configuration isn't given
|
8
|
+
# in the configuration block.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad, immediately causes Rails to load `app/graphql/types/thing.rb`
|
12
|
+
# field :thing, Types::Thing
|
13
|
+
#
|
14
|
+
# # good, defers loading until the file is needed
|
15
|
+
# field :thing do
|
16
|
+
# type(Types::Thing)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class FieldTypeInBlock < BaseCop
|
20
|
+
MSG = "type configuration can be moved to a block to defer loading the type's file"
|
21
|
+
|
22
|
+
BUILT_IN_SCALAR_NAMES = ["Float", "Int", "Integer", "String", "ID", "Boolean"]
|
23
|
+
def_node_matcher :field_config_with_inline_type, <<-Pattern
|
24
|
+
(
|
25
|
+
send {nil? _} :field sym ${const array} ...
|
26
|
+
)
|
27
|
+
Pattern
|
28
|
+
|
29
|
+
def_node_matcher :field_config_with_inline_type_and_block, <<-Pattern
|
30
|
+
(
|
31
|
+
block
|
32
|
+
(send {nil? _} :field sym ${const array}) ...
|
33
|
+
(args)
|
34
|
+
_
|
35
|
+
|
36
|
+
)
|
37
|
+
Pattern
|
38
|
+
|
39
|
+
def on_block(node)
|
40
|
+
field_config_with_inline_type_and_block(node) do |type_const|
|
41
|
+
ignore_node(type_const)
|
42
|
+
type_const_str = get_type_argument_str(node, type_const)
|
43
|
+
if ignore_inline_type_str?(type_const_str)
|
44
|
+
# Do nothing ...
|
45
|
+
else
|
46
|
+
add_offense(type_const) do |corrector|
|
47
|
+
cleaned_node_source = delete_type_argument(node, type_const)
|
48
|
+
field_indent = determine_field_indent(node)
|
49
|
+
cleaned_node_source.sub!(/(\{|do)/, "\\1\n#{field_indent} type #{type_const_str}")
|
50
|
+
corrector.replace(node, cleaned_node_source)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_send(node)
|
57
|
+
field_config_with_inline_type(node) do |type_const|
|
58
|
+
return if ignored_node?(type_const)
|
59
|
+
type_const_str = get_type_argument_str(node, type_const)
|
60
|
+
if ignore_inline_type_str?(type_const_str)
|
61
|
+
# Do nothing -- not loading from another file
|
62
|
+
else
|
63
|
+
add_offense(type_const) do |corrector|
|
64
|
+
cleaned_node_source = delete_type_argument(node, type_const)
|
65
|
+
field_indent = determine_field_indent(node)
|
66
|
+
cleaned_node_source += " do\n#{field_indent} type #{type_const_str}\n#{field_indent}end"
|
67
|
+
corrector.replace(node, cleaned_node_source)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def ignore_inline_type_str?(type_str)
|
77
|
+
BUILT_IN_SCALAR_NAMES.include?(type_str)
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_type_argument_str(send_node, type_const)
|
81
|
+
first_pos = type_const.location.expression.begin_pos
|
82
|
+
end_pos = type_const.location.expression.end_pos
|
83
|
+
node_source = send_node.source_range.source
|
84
|
+
node_first_pos = send_node.location.expression.begin_pos
|
85
|
+
|
86
|
+
relative_first_pos = first_pos - node_first_pos
|
87
|
+
end_removal_pos = end_pos - node_first_pos
|
88
|
+
|
89
|
+
node_source[relative_first_pos...end_removal_pos]
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete_type_argument(send_node, type_const)
|
93
|
+
first_pos = type_const.location.expression.begin_pos
|
94
|
+
end_pos = type_const.location.expression.end_pos
|
95
|
+
node_source = send_node.source_range.source
|
96
|
+
node_first_pos = send_node.location.expression.begin_pos
|
97
|
+
|
98
|
+
relative_first_pos = first_pos - node_first_pos
|
99
|
+
end_removal_pos = end_pos - node_first_pos
|
100
|
+
|
101
|
+
begin_removal_pos = relative_first_pos
|
102
|
+
while node_source[begin_removal_pos] != ","
|
103
|
+
begin_removal_pos -= 1
|
104
|
+
if begin_removal_pos < 1
|
105
|
+
raise "Invariant: somehow backtracked to beginning of node looking for a comma (node source: #{node_source.inspect})"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
node_source[0...begin_removal_pos] + node_source[end_removal_pos..-1]
|
110
|
+
end
|
111
|
+
|
112
|
+
def determine_field_indent(send_node)
|
113
|
+
surrounding_node = send_node.parent.parent
|
114
|
+
surrounding_source = surrounding_node.source
|
115
|
+
indent_test_idx = send_node.location.expression.begin_pos - surrounding_node.source_range.begin_pos - 1
|
116
|
+
field_indent = "".dup
|
117
|
+
while surrounding_source[indent_test_idx] == " "
|
118
|
+
field_indent << " "
|
119
|
+
indent_test_idx -= 1
|
120
|
+
if indent_test_idx == 0
|
121
|
+
raise "Invariant: somehow backtracted to beginning of class when looking for field indent (source: #{node_source.inspect})"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
field_indent
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "./base_cop"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Rubocop
|
6
|
+
module GraphQL
|
7
|
+
# Identify (and auto-correct) any root types in your schema file.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad, immediately causes Rails to load `app/graphql/types/query.rb`
|
11
|
+
# query Types::Query
|
12
|
+
#
|
13
|
+
# # good, defers loading until the file is needed
|
14
|
+
# query { Types::Query }
|
15
|
+
#
|
16
|
+
class RootTypesInBlock < BaseCop
|
17
|
+
MSG = "type configuration can be moved to a block to defer loading the type's file"
|
18
|
+
|
19
|
+
def_node_matcher :root_type_config_without_block, <<-Pattern
|
20
|
+
(
|
21
|
+
send nil? {:query :mutation :subscription} const
|
22
|
+
)
|
23
|
+
Pattern
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
root_type_config_without_block(node) do
|
27
|
+
add_offense(node) do |corrector|
|
28
|
+
new_node_source = node.source_range.source
|
29
|
+
new_node_source.sub!(/(query|mutation|subscription)/, '\1 {')
|
30
|
+
new_node_source << " }"
|
31
|
+
corrector.replace(node, new_node_source)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/graphql/rubocop.rb
CHANGED
@@ -189,6 +189,7 @@ module GraphQL
|
|
189
189
|
add_directives_from(type)
|
190
190
|
if type.kind.fields?
|
191
191
|
type.all_field_definitions.each do |field|
|
192
|
+
field.ensure_loaded
|
192
193
|
name = field.graphql_name
|
193
194
|
field_type = field.type.unwrap
|
194
195
|
if !field_type.is_a?(GraphQL::Schema::LateBoundType)
|
data/lib/graphql/schema/enum.rb
CHANGED
@@ -22,9 +22,21 @@ module GraphQL
|
|
22
22
|
class Enum < GraphQL::Schema::Member
|
23
23
|
extend GraphQL::Schema::Member::ValidatesInput
|
24
24
|
|
25
|
+
# This is raised when either:
|
26
|
+
#
|
27
|
+
# - A resolver returns a value which doesn't match any of the enum's configured values;
|
28
|
+
# - Or, the resolver returns a value which matches a value, but that value's `authorized?` check returns false.
|
29
|
+
#
|
30
|
+
# In either case, the field should be modified so that the invalid value isn't returned.
|
31
|
+
#
|
32
|
+
# {GraphQL::Schema::Enum} subclasses get their own subclass of this error, so that bug trackers can better show where they came from.
|
25
33
|
class UnresolvedValueError < GraphQL::Error
|
26
|
-
def initialize(value:, enum:, context:)
|
27
|
-
fix_message =
|
34
|
+
def initialize(value:, enum:, context:, authorized:)
|
35
|
+
fix_message = if authorized == false
|
36
|
+
", but this value was unauthorized. Update the field or resolver to return a different value in this case (or return `nil`)."
|
37
|
+
else
|
38
|
+
", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
|
39
|
+
end
|
28
40
|
message = if (cp = context[:current_path]) && (cf = context[:current_field])
|
29
41
|
"`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
|
30
42
|
else
|
@@ -34,6 +46,8 @@ module GraphQL
|
|
34
46
|
end
|
35
47
|
end
|
36
48
|
|
49
|
+
# Raised when a {GraphQL::Schema::Enum} is defined to have no values.
|
50
|
+
# This can also happen when all values return false for `.visible?`.
|
37
51
|
class MissingValuesError < GraphQL::Error
|
38
52
|
def initialize(enum_type)
|
39
53
|
@enum_type = enum_type
|
@@ -43,10 +57,10 @@ module GraphQL
|
|
43
57
|
|
44
58
|
class << self
|
45
59
|
# Define a value for this enum
|
46
|
-
# @
|
47
|
-
# @
|
48
|
-
# @
|
49
|
-
# @
|
60
|
+
# @option kwargs [String, Symbol] :graphql_name the GraphQL value for this, usually `SCREAMING_CASE`
|
61
|
+
# @option kwargs [String] :description, the GraphQL description for this value, present in documentation
|
62
|
+
# @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
|
63
|
+
# @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
|
50
64
|
# @return [void]
|
51
65
|
# @see {Schema::EnumValue} which handles these inputs by default
|
52
66
|
def value(*args, **kwargs, &block)
|
@@ -140,26 +154,39 @@ module GraphQL
|
|
140
154
|
end
|
141
155
|
end
|
142
156
|
|
157
|
+
# Called by the runtime when a field returns a value to give back to the client.
|
158
|
+
# This method checks that the incoming {value} matches one of the enum's defined values.
|
159
|
+
# @param value [Object] Any value matching the values for this enum.
|
160
|
+
# @param ctx [GraphQL::Query::Context]
|
161
|
+
# @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized.
|
162
|
+
# @return [String] The GraphQL-ready string for {value}
|
143
163
|
def coerce_result(value, ctx)
|
144
164
|
types = ctx.types
|
145
165
|
all_values = types ? types.enum_values(self) : values.each_value
|
146
166
|
enum_value = all_values.find { |val| val.value == value }
|
147
|
-
if enum_value
|
167
|
+
if enum_value && (was_authed = enum_value.authorized?(ctx))
|
148
168
|
enum_value.graphql_name
|
149
169
|
else
|
150
|
-
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
|
170
|
+
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx, authorized: was_authed)
|
151
171
|
end
|
152
172
|
end
|
153
173
|
|
174
|
+
# Called by the runtime with incoming string representations from a query.
|
175
|
+
# It will match the string to a configured by name or by Ruby value.
|
176
|
+
# @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration
|
177
|
+
# @param ctx [GraphQL::Query::Context]
|
178
|
+
# @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}.
|
179
|
+
# @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue}
|
154
180
|
def coerce_input(value_name, ctx)
|
155
181
|
all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value
|
156
182
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
183
|
+
# This tries matching by incoming GraphQL string, then checks Ruby-defined values
|
184
|
+
if v = (all_values.find { |val| val.graphql_name == value_name } || all_values.find { |val| val.value == value_name })
|
185
|
+
if v.authorized?(ctx)
|
186
|
+
v.value
|
187
|
+
else
|
188
|
+
raise GraphQL::UnauthorizedEnumValueError.new(type: self, enum_value: v, context: ctx)
|
189
|
+
end
|
163
190
|
else
|
164
191
|
nil
|
165
192
|
end
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -146,11 +146,16 @@ module GraphQL
|
|
146
146
|
Member::BuildType.to_type_name(@return_type_expr)
|
147
147
|
elsif @resolver_class && @resolver_class.type
|
148
148
|
Member::BuildType.to_type_name(@resolver_class.type)
|
149
|
-
|
149
|
+
elsif type
|
150
150
|
# As a last ditch, try to force loading the return type:
|
151
151
|
type.unwrap.name
|
152
152
|
end
|
153
|
-
|
153
|
+
if return_type_name
|
154
|
+
@connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
|
155
|
+
else
|
156
|
+
# TODO set this when type is set by method
|
157
|
+
false # not loaded yet?
|
158
|
+
end
|
154
159
|
else
|
155
160
|
@connection
|
156
161
|
end
|
@@ -236,8 +241,8 @@ module GraphQL
|
|
236
241
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
237
242
|
end
|
238
243
|
if !(resolver_class)
|
239
|
-
if type.nil?
|
240
|
-
raise ArgumentError, "missing second `type` argument or
|
244
|
+
if type.nil? && !block_given?
|
245
|
+
raise ArgumentError, "missing second `type` argument, keyword `type:`, or a block containing `type(...)`"
|
241
246
|
end
|
242
247
|
end
|
243
248
|
@original_name = name
|
@@ -302,6 +307,7 @@ module GraphQL
|
|
302
307
|
@ast_node = ast_node
|
303
308
|
@method_conflict_warning = method_conflict_warning
|
304
309
|
@fallback_value = fallback_value
|
310
|
+
@definition_block = nil
|
305
311
|
|
306
312
|
arguments.each do |name, arg|
|
307
313
|
case arg
|
@@ -320,26 +326,14 @@ module GraphQL
|
|
320
326
|
@subscription_scope = subscription_scope
|
321
327
|
|
322
328
|
@extensions = EMPTY_ARRAY
|
323
|
-
|
324
|
-
# This should run before connection extension,
|
325
|
-
# but should it run after the definition block?
|
326
|
-
if scoped?
|
327
|
-
self.extension(ScopeExtension)
|
328
|
-
end
|
329
|
-
|
330
|
-
# The problem with putting this after the definition_block
|
331
|
-
# is that it would override arguments
|
332
|
-
if connection? && connection_extension
|
333
|
-
self.extension(connection_extension)
|
334
|
-
end
|
335
|
-
|
329
|
+
set_pagination_extensions(connection_extension: connection_extension)
|
336
330
|
# Do this last so we have as much context as possible when initializing them:
|
337
331
|
if extensions.any?
|
338
|
-
self.extensions(extensions)
|
332
|
+
self.extensions(extensions, call_after_define: false)
|
339
333
|
end
|
340
334
|
|
341
335
|
if resolver_class && resolver_class.extensions.any?
|
342
|
-
self.extensions(resolver_class.extensions)
|
336
|
+
self.extensions(resolver_class.extensions, call_after_define: false)
|
343
337
|
end
|
344
338
|
|
345
339
|
if directives.any?
|
@@ -353,15 +347,28 @@ module GraphQL
|
|
353
347
|
end
|
354
348
|
|
355
349
|
if block_given?
|
356
|
-
|
357
|
-
|
350
|
+
@definition_block = definition_block
|
351
|
+
else
|
352
|
+
self.extensions.each(&:after_define_apply)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Calls the definition block, if one was given.
|
357
|
+
# This is deferred so that references to the return type
|
358
|
+
# can be lazily evaluated, reducing Rails boot time.
|
359
|
+
# @return [self]
|
360
|
+
# @api private
|
361
|
+
def ensure_loaded
|
362
|
+
if @definition_block
|
363
|
+
if @definition_block.arity == 1
|
364
|
+
@definition_block.call(self)
|
358
365
|
else
|
359
|
-
instance_eval(
|
366
|
+
instance_eval(&@definition_block)
|
360
367
|
end
|
368
|
+
self.extensions.each(&:after_define_apply)
|
369
|
+
@definition_block = nil
|
361
370
|
end
|
362
|
-
|
363
|
-
self.extensions.each(&:after_define_apply)
|
364
|
-
@call_after_define = true
|
371
|
+
self
|
365
372
|
end
|
366
373
|
|
367
374
|
attr_accessor :dynamic_introspection
|
@@ -408,14 +415,14 @@ module GraphQL
|
|
408
415
|
#
|
409
416
|
# @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
|
410
417
|
# @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
|
411
|
-
def extensions(new_extensions = nil)
|
418
|
+
def extensions(new_extensions = nil, call_after_define: !@definition_block)
|
412
419
|
if new_extensions
|
413
420
|
new_extensions.each do |extension_config|
|
414
421
|
if extension_config.is_a?(Hash)
|
415
422
|
extension_class, options = *extension_config.to_a[0]
|
416
|
-
self.extension(extension_class, options)
|
423
|
+
self.extension(extension_class, call_after_define: call_after_define, **options)
|
417
424
|
else
|
418
|
-
self.extension(extension_config)
|
425
|
+
self.extension(extension_config, call_after_define: call_after_define)
|
419
426
|
end
|
420
427
|
end
|
421
428
|
end
|
@@ -433,12 +440,12 @@ module GraphQL
|
|
433
440
|
# @param extension_class [Class] subclass of {Schema::FieldExtension}
|
434
441
|
# @param options [Hash] if provided, given as `options:` when initializing `extension`.
|
435
442
|
# @return [void]
|
436
|
-
def extension(extension_class,
|
443
|
+
def extension(extension_class, call_after_define: !@definition_block, **options)
|
437
444
|
extension_inst = extension_class.new(field: self, options: options)
|
438
445
|
if @extensions.frozen?
|
439
446
|
@extensions = @extensions.dup
|
440
447
|
end
|
441
|
-
if
|
448
|
+
if call_after_define
|
442
449
|
extension_inst.after_define_apply
|
443
450
|
end
|
444
451
|
@extensions << extension_inst
|
@@ -577,16 +584,29 @@ module GraphQL
|
|
577
584
|
class MissingReturnTypeError < GraphQL::Error; end
|
578
585
|
attr_writer :type
|
579
586
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
587
|
+
# Get or set the return type of this field.
|
588
|
+
#
|
589
|
+
# It may return nil if no type was configured or if the given definition block wasn't called yet.
|
590
|
+
# @param new_type [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List] A GraphQL return type
|
591
|
+
# @return [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil] the configured type for this field
|
592
|
+
def type(new_type = NOT_CONFIGURED)
|
593
|
+
if NOT_CONFIGURED.equal?(new_type)
|
594
|
+
if @resolver_class
|
595
|
+
return_type = @return_type_expr || @resolver_class.type_expr
|
596
|
+
if return_type.nil?
|
597
|
+
raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
|
598
|
+
end
|
599
|
+
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
600
|
+
Member::BuildType.parse_type(return_type, null: nullable)
|
601
|
+
elsif !@return_type_expr.nil?
|
602
|
+
@type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
585
603
|
end
|
586
|
-
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
587
|
-
Member::BuildType.parse_type(return_type, null: nullable)
|
588
604
|
else
|
589
|
-
@
|
605
|
+
@return_type_expr = new_type
|
606
|
+
# If `type` is set in the definition block, then the `connection_extension: ...` given as a keyword won't be used, hmm...
|
607
|
+
# Also, arguments added by `connection_extension` will clobber anything previously defined,
|
608
|
+
# so `type(...)` should go first.
|
609
|
+
set_pagination_extensions(connection_extension: self.class.connection_extension)
|
590
610
|
end
|
591
611
|
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
592
612
|
# Let this propagate up
|
@@ -897,6 +917,20 @@ ERR
|
|
897
917
|
raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
|
898
918
|
end
|
899
919
|
end
|
920
|
+
|
921
|
+
def set_pagination_extensions(connection_extension:)
|
922
|
+
# This should run before connection extension,
|
923
|
+
# but should it run after the definition block?
|
924
|
+
if scoped?
|
925
|
+
self.extension(ScopeExtension, call_after_define: false)
|
926
|
+
end
|
927
|
+
|
928
|
+
# The problem with putting this after the definition_block
|
929
|
+
# is that it would override arguments
|
930
|
+
if connection? && connection_extension
|
931
|
+
self.extension(connection_extension, call_after_define: false)
|
932
|
+
end
|
933
|
+
end
|
900
934
|
end
|
901
935
|
end
|
902
936
|
end
|
@@ -121,7 +121,7 @@ module GraphQL
|
|
121
121
|
# Choose the most local definition that passes `.visible?` --
|
122
122
|
# stop checking for fields by name once one has been found.
|
123
123
|
if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
|
124
|
-
visible_fields[field_name] = f
|
124
|
+
visible_fields[field_name] = f.ensure_loaded
|
125
125
|
end
|
126
126
|
end
|
127
127
|
end
|
@@ -142,7 +142,7 @@ module GraphQL
|
|
142
142
|
visible_interface_implementation?(ancestor, context, warden) &&
|
143
143
|
(f_entry = ancestor.own_fields[field_name]) &&
|
144
144
|
(skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden)))
|
145
|
-
return f_entry
|
145
|
+
return (skip_visible ? f_entry : f_entry.ensure_loaded)
|
146
146
|
end
|
147
147
|
i += 1
|
148
148
|
end
|
@@ -161,7 +161,7 @@ module GraphQL
|
|
161
161
|
# Choose the most local definition that passes `.visible?` --
|
162
162
|
# stop checking for fields by name once one has been found.
|
163
163
|
if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
|
164
|
-
visible_fields[field_name] = f
|
164
|
+
visible_fields[field_name] = f.ensure_loaded
|
165
165
|
end
|
166
166
|
end
|
167
167
|
end
|
@@ -129,7 +129,7 @@ module GraphQL
|
|
129
129
|
end.compare_by_identity
|
130
130
|
|
131
131
|
@cached_fields = Hash.new do |h, owner|
|
132
|
-
h[owner] = non_duplicate_items(owner.all_field_definitions, @cached_visible_fields[owner])
|
132
|
+
h[owner] = non_duplicate_items(owner.all_field_definitions.each(&:ensure_loaded), @cached_visible_fields[owner])
|
133
133
|
end.compare_by_identity
|
134
134
|
|
135
135
|
@cached_arguments = Hash.new do |h, owner|
|
@@ -213,13 +213,11 @@ module GraphQL
|
|
213
213
|
end
|
214
214
|
end
|
215
215
|
end
|
216
|
-
visible_f
|
216
|
+
visible_f.ensure_loaded
|
217
|
+
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
218
|
+
f
|
217
219
|
else
|
218
|
-
|
219
|
-
f
|
220
|
-
else
|
221
|
-
nil
|
222
|
-
end
|
220
|
+
nil
|
223
221
|
end
|
224
222
|
end
|
225
223
|
|
@@ -446,6 +444,7 @@ module GraphQL
|
|
446
444
|
# recurse into visible fields
|
447
445
|
t_f = type.all_field_definitions
|
448
446
|
t_f.each do |field|
|
447
|
+
field.ensure_loaded
|
449
448
|
if @cached_visible[field]
|
450
449
|
visit_directives(field)
|
451
450
|
field_type = field.type.unwrap
|
@@ -165,6 +165,8 @@ module GraphQL
|
|
165
165
|
equivalent_schema_members?(inner_member1, inner_member2)
|
166
166
|
end
|
167
167
|
when GraphQL::Schema::Field
|
168
|
+
member1.ensure_loaded
|
169
|
+
member2.ensure_loaded
|
168
170
|
if member1.introspection? && member2.introspection?
|
169
171
|
member1.inspect == member2.inspect
|
170
172
|
else
|
data/lib/graphql/schema.rb
CHANGED
@@ -430,44 +430,62 @@ module GraphQL
|
|
430
430
|
end
|
431
431
|
end
|
432
432
|
|
433
|
-
def query(new_query_object = nil)
|
434
|
-
if new_query_object
|
433
|
+
def query(new_query_object = nil, &lazy_load_block)
|
434
|
+
if new_query_object || block_given?
|
435
435
|
if @query_object
|
436
|
-
|
436
|
+
dup_defn = new_query_object || yield
|
437
|
+
raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
|
438
|
+
elsif use_schema_subset?
|
439
|
+
@query_object = block_given? ? lazy_load_block : new_query_object
|
437
440
|
else
|
438
|
-
@query_object = new_query_object
|
439
|
-
add_type_and_traverse(
|
440
|
-
nil
|
441
|
+
@query_object = new_query_object || lazy_load_block.call
|
442
|
+
add_type_and_traverse(@query_object, root: true)
|
441
443
|
end
|
444
|
+
nil
|
445
|
+
elsif @query_object.is_a?(Proc)
|
446
|
+
@query_object = @query_object.call
|
442
447
|
else
|
443
448
|
@query_object || find_inherited_value(:query)
|
444
449
|
end
|
445
450
|
end
|
446
451
|
|
447
|
-
def mutation(new_mutation_object = nil)
|
448
|
-
if new_mutation_object
|
452
|
+
def mutation(new_mutation_object = nil, &lazy_load_block)
|
453
|
+
if new_mutation_object || block_given?
|
449
454
|
if @mutation_object
|
450
|
-
|
455
|
+
dup_defn = new_mutation_object || yield
|
456
|
+
raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
|
457
|
+
elsif use_schema_subset?
|
458
|
+
@mutation_object = block_given? ? lazy_load_block : new_mutation_object
|
451
459
|
else
|
452
|
-
@mutation_object = new_mutation_object
|
453
|
-
add_type_and_traverse(
|
454
|
-
nil
|
460
|
+
@mutation_object = new_mutation_object || lazy_load_block.call
|
461
|
+
add_type_and_traverse(@mutation_object, root: true)
|
455
462
|
end
|
463
|
+
nil
|
464
|
+
elsif @mutation_object.is_a?(Proc)
|
465
|
+
@mutation_object = @mutation_object.call
|
456
466
|
else
|
457
467
|
@mutation_object || find_inherited_value(:mutation)
|
458
468
|
end
|
459
469
|
end
|
460
470
|
|
461
|
-
def subscription(new_subscription_object = nil)
|
462
|
-
if new_subscription_object
|
471
|
+
def subscription(new_subscription_object = nil, &lazy_load_block)
|
472
|
+
if new_subscription_object || block_given?
|
463
473
|
if @subscription_object
|
464
|
-
|
474
|
+
dup_defn = new_subscription_object || yield
|
475
|
+
raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
|
476
|
+
elsif use_schema_subset?
|
477
|
+
@subscription_object = block_given? ? lazy_load_block : new_subscription_object
|
478
|
+
add_subscription_extension_if_necessary
|
465
479
|
else
|
466
|
-
@subscription_object = new_subscription_object
|
480
|
+
@subscription_object = new_subscription_object || lazy_load_block.call
|
467
481
|
add_subscription_extension_if_necessary
|
468
|
-
add_type_and_traverse(
|
469
|
-
nil
|
482
|
+
add_type_and_traverse(@subscription_object, root: true)
|
470
483
|
end
|
484
|
+
nil
|
485
|
+
elsif @subscription_object.is_a?(Proc)
|
486
|
+
@subscription_object = @subscription_object.call
|
487
|
+
add_subscription_extension_if_necessary
|
488
|
+
@subscription_object
|
471
489
|
else
|
472
490
|
@subscription_object || find_inherited_value(:subscription)
|
473
491
|
end
|
@@ -1373,7 +1391,8 @@ module GraphQL
|
|
1373
1391
|
|
1374
1392
|
# @api private
|
1375
1393
|
def add_subscription_extension_if_necessary
|
1376
|
-
|
1394
|
+
# TODO: when there's a proper API for extending root types, migrat this to use it.
|
1395
|
+
if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
|
1377
1396
|
@subscription_extension_added = true
|
1378
1397
|
subscription.all_field_definitions.each do |field|
|
1379
1398
|
if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class UnauthorizedEnumValueError < GraphQL::UnauthorizedError
|
4
|
+
# @return [GraphQL::Schema::EnumValue] The value whose `#authorized?` check returned false
|
5
|
+
attr_accessor :enum_value
|
6
|
+
|
7
|
+
def initialize(type:, context:, enum_value:)
|
8
|
+
@enum_value = enum_value
|
9
|
+
message ||= "#{enum_value.path} failed authorization"
|
10
|
+
super(message, object: enum_value.value, type: type, context: context)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/graphql/version.rb
CHANGED
data/lib/graphql.rb
CHANGED
@@ -119,6 +119,7 @@ require "graphql/parse_error"
|
|
119
119
|
require "graphql/backtrace"
|
120
120
|
|
121
121
|
require "graphql/unauthorized_error"
|
122
|
+
require "graphql/unauthorized_enum_value_error"
|
122
123
|
require "graphql/unauthorized_field_error"
|
123
124
|
require "graphql/load_application_object_failed_error"
|
124
125
|
require "graphql/testing"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.
|
4
|
+
version: 2.3.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|
@@ -422,6 +422,8 @@ files:
|
|
422
422
|
- lib/graphql/rubocop/graphql/base_cop.rb
|
423
423
|
- lib/graphql/rubocop/graphql/default_null_true.rb
|
424
424
|
- lib/graphql/rubocop/graphql/default_required_true.rb
|
425
|
+
- lib/graphql/rubocop/graphql/field_type_in_block.rb
|
426
|
+
- lib/graphql/rubocop/graphql/root_types_in_block.rb
|
425
427
|
- lib/graphql/runtime_type_error.rb
|
426
428
|
- lib/graphql/schema.rb
|
427
429
|
- lib/graphql/schema/addition.rb
|
@@ -630,6 +632,7 @@ files:
|
|
630
632
|
- lib/graphql/types/relay/page_info.rb
|
631
633
|
- lib/graphql/types/relay/page_info_behaviors.rb
|
632
634
|
- lib/graphql/types/string.rb
|
635
|
+
- lib/graphql/unauthorized_enum_value_error.rb
|
633
636
|
- lib/graphql/unauthorized_error.rb
|
634
637
|
- lib/graphql/unauthorized_field_error.rb
|
635
638
|
- lib/graphql/unresolved_type_error.rb
|