graphql 2.3.11 → 2.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- 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 +37 -20
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +2 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1fd7e53d9729765a9c020960a968e478cf43efca04789f73b64ac226c5bf3c2
|
4
|
+
data.tar.gz: ed151811454729c3ab5fd4c2877bc4cbb19093e0e85faa630b4472b486061d7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 564d90c0336b0b7d812843bbd3c7db78855820e6fdeaf8d786149c06dcdf2025aabb11dd31b3621ae0631d8d3fd3c4e222c712fa652e2a24a96af9b2d594d660
|
7
|
+
data.tar.gz: b705e105a0c18dd578bac21978bca5d1229e33cef6d6a627d6a47b2e4fafe711aa9b4a1fc956eece47e8e1c67280af05c74cfa01aa367b3e58d38cd5c7b78cd9
|
@@ -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,60 @@ 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
|
465
478
|
else
|
466
|
-
@subscription_object = new_subscription_object
|
467
|
-
|
468
|
-
add_type_and_traverse(new_subscription_object, root: true) unless use_schema_subset?
|
469
|
-
nil
|
479
|
+
@subscription_object = new_subscription_object || lazy_load_block.call
|
480
|
+
add_type_and_traverse(@subscription_object, root: true)
|
470
481
|
end
|
482
|
+
nil
|
483
|
+
elsif @subscription_object.is_a?(Proc)
|
484
|
+
@subscription_object = @subscription_object.call
|
485
|
+
add_subscription_extension_if_necessary
|
486
|
+
@subscription_object
|
471
487
|
else
|
472
488
|
@subscription_object || find_inherited_value(:subscription)
|
473
489
|
end
|
@@ -1373,7 +1389,8 @@ module GraphQL
|
|
1373
1389
|
|
1374
1390
|
# @api private
|
1375
1391
|
def add_subscription_extension_if_necessary
|
1376
|
-
|
1392
|
+
# TODO: when there's a proper API for extending root types, migrat this to use it.
|
1393
|
+
if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
|
1377
1394
|
@subscription_extension_added = true
|
1378
1395
|
subscription.all_field_definitions.each do |field|
|
1379
1396
|
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
@@ -4,6 +4,7 @@ require "json"
|
|
4
4
|
require "set"
|
5
5
|
require "singleton"
|
6
6
|
require "forwardable"
|
7
|
+
require "fiber/storage"
|
7
8
|
|
8
9
|
module GraphQL
|
9
10
|
class Error < StandardError
|
@@ -118,6 +119,7 @@ require "graphql/parse_error"
|
|
118
119
|
require "graphql/backtrace"
|
119
120
|
|
120
121
|
require "graphql/unauthorized_error"
|
122
|
+
require "graphql/unauthorized_enum_value_error"
|
121
123
|
require "graphql/unauthorized_field_error"
|
122
124
|
require "graphql/load_application_object_failed_error"
|
123
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.13
|
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-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fiber-storage
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: benchmark-ips
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -408,6 +422,8 @@ files:
|
|
408
422
|
- lib/graphql/rubocop/graphql/base_cop.rb
|
409
423
|
- lib/graphql/rubocop/graphql/default_null_true.rb
|
410
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
|
411
427
|
- lib/graphql/runtime_type_error.rb
|
412
428
|
- lib/graphql/schema.rb
|
413
429
|
- lib/graphql/schema/addition.rb
|
@@ -616,6 +632,7 @@ files:
|
|
616
632
|
- lib/graphql/types/relay/page_info.rb
|
617
633
|
- lib/graphql/types/relay/page_info_behaviors.rb
|
618
634
|
- lib/graphql/types/string.rb
|
635
|
+
- lib/graphql/unauthorized_enum_value_error.rb
|
619
636
|
- lib/graphql/unauthorized_error.rb
|
620
637
|
- lib/graphql/unauthorized_field_error.rb
|
621
638
|
- lib/graphql/unresolved_type_error.rb
|
@@ -646,7 +663,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
646
663
|
- !ruby/object:Gem::Version
|
647
664
|
version: '0'
|
648
665
|
requirements: []
|
649
|
-
rubygems_version: 3.
|
666
|
+
rubygems_version: 3.3.7
|
650
667
|
signing_key:
|
651
668
|
specification_version: 4
|
652
669
|
summary: A GraphQL language and runtime for Ruby
|