graphql 2.3.5 → 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/generators/graphql/install_generator.rb +46 -0
- data/lib/graphql/analysis/analyzer.rb +89 -0
- data/lib/graphql/analysis/field_usage.rb +82 -0
- data/lib/graphql/analysis/max_query_complexity.rb +20 -0
- data/lib/graphql/analysis/max_query_depth.rb +20 -0
- data/lib/graphql/analysis/query_complexity.rb +183 -0
- data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
- data/lib/graphql/analysis/visitor.rb +283 -0
- data/lib/graphql/analysis.rb +92 -1
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +2 -0
- data/lib/graphql/dataloader/source.rb +5 -2
- data/lib/graphql/dataloader.rb +4 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/runtime.rb +29 -25
- data/lib/graphql/execution/interpreter.rb +3 -1
- data/lib/graphql/execution/lookahead.rb +10 -10
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +6 -11
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/language/document_from_schema_definition.rb +19 -26
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/nodes.rb +2 -2
- data/lib/graphql/language/parser.rb +9 -1
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +0 -1
- data/lib/graphql/query/context.rb +7 -1
- data/lib/graphql/query/null_context.rb +2 -2
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query.rb +26 -7
- 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/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +19 -5
- data/lib/graphql/schema/build_from_definition.rb +8 -1
- data/lib/graphql/schema/directive/flagged.rb +1 -1
- data/lib/graphql/schema/directive.rb +2 -0
- data/lib/graphql/schema/enum.rb +51 -20
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +85 -39
- data/lib/graphql/schema/has_single_input_argument.rb +2 -1
- data/lib/graphql/schema/input_object.rb +8 -7
- data/lib/graphql/schema/interface.rb +20 -4
- data/lib/graphql/schema/introspection_system.rb +5 -16
- data/lib/graphql/schema/member/has_arguments.rb +14 -9
- data/lib/graphql/schema/member/has_fields.rb +8 -6
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/resolver.rb +5 -5
- data/lib/graphql/schema/subset.rb +509 -0
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/types_migration.rb +187 -0
- data/lib/graphql/schema/validator/all_validator.rb +62 -0
- data/lib/graphql/schema/validator.rb +2 -0
- data/lib/graphql/schema/warden.rb +89 -5
- data/lib/graphql/schema.rb +109 -53
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +7 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +2 -2
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +3 -3
- data/lib/graphql/testing/helpers.rb +8 -5
- data/lib/graphql/types/relay/connection_behaviors.rb +10 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +10 -0
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +31 -13
- data/lib/graphql/analysis/ast/analyzer.rb +0 -91
- data/lib/graphql/analysis/ast/field_usage.rb +0 -84
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
- data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
- data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
- data/lib/graphql/analysis/ast/visitor.rb +0 -284
- data/lib/graphql/analysis/ast.rb +0 -94
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -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)
|
@@ -312,10 +312,15 @@ module GraphQL
|
|
312
312
|
context.query.after_lazy(custom_loaded_value) do |custom_value|
|
313
313
|
if loads
|
314
314
|
if type.list?
|
315
|
-
loaded_values =
|
316
|
-
|
317
|
-
|
318
|
-
|
315
|
+
loaded_values = []
|
316
|
+
context.dataloader.run_isolated do
|
317
|
+
custom_value.each_with_index.map { |custom_val, idx|
|
318
|
+
id = coerced_value[idx]
|
319
|
+
context.dataloader.append_job do
|
320
|
+
loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val)
|
321
|
+
end
|
322
|
+
}
|
323
|
+
end
|
319
324
|
context.schema.after_any_lazies(loaded_values, &:itself)
|
320
325
|
else
|
321
326
|
load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
|
@@ -326,7 +331,16 @@ module GraphQL
|
|
326
331
|
end
|
327
332
|
elsif loads
|
328
333
|
if type.list?
|
329
|
-
loaded_values =
|
334
|
+
loaded_values = []
|
335
|
+
# We want to run these list items all together,
|
336
|
+
# but we also need to wait for the result so we can return it :S
|
337
|
+
context.dataloader.run_isolated do
|
338
|
+
coerced_value.each_with_index { |val, idx|
|
339
|
+
context.dataloader.append_job do
|
340
|
+
loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context)
|
341
|
+
end
|
342
|
+
}
|
343
|
+
end
|
330
344
|
context.schema.after_any_lazies(loaded_values, &:itself)
|
331
345
|
else
|
332
346
|
load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
|
@@ -127,11 +127,12 @@ module GraphQL
|
|
127
127
|
builder = self
|
128
128
|
|
129
129
|
found_types = types.values
|
130
|
+
object_types = found_types.select { |t| t.respond_to?(:kind) && t.kind.object? }
|
130
131
|
schema_class = Class.new(schema_superclass) do
|
131
132
|
begin
|
132
133
|
# Add these first so that there's some chance of resolving late-bound types
|
133
134
|
add_type_and_traverse(found_types, root: false)
|
134
|
-
orphan_types(
|
135
|
+
orphan_types(object_types)
|
135
136
|
query query_root_type
|
136
137
|
mutation mutation_root_type
|
137
138
|
subscription subscription_root_type
|
@@ -141,6 +142,12 @@ module GraphQL
|
|
141
142
|
raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace
|
142
143
|
end
|
143
144
|
|
145
|
+
object_types.each do |t|
|
146
|
+
t.interfaces.each do |int_t|
|
147
|
+
int_t.orphan_types(t)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
144
151
|
if default_resolve.respond_to?(:resolve_type)
|
145
152
|
def self.resolve_type(*args)
|
146
153
|
self.definition_default_resolve.resolve_type(*args)
|
@@ -7,7 +7,7 @@ module GraphQL
|
|
7
7
|
# In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present.
|
8
8
|
class Flagged < GraphQL::Schema::Directive
|
9
9
|
def initialize(target, **options)
|
10
|
-
if target.is_a?(Module)
|
10
|
+
if target.is_a?(Module)
|
11
11
|
# This is type class of some kind, `include` will put this module
|
12
12
|
# in between the type class itself and its super class, so `super` will work fine
|
13
13
|
target.include(VisibleByFlag)
|
@@ -188,6 +188,8 @@ module GraphQL
|
|
188
188
|
assert_has_location(SCALAR)
|
189
189
|
elsif @owner < GraphQL::Schema
|
190
190
|
assert_has_location(SCHEMA)
|
191
|
+
elsif @owner < GraphQL::Schema::Resolver
|
192
|
+
assert_has_location(FIELD_DEFINITION)
|
191
193
|
else
|
192
194
|
raise "Unexpected directive owner class: #{@owner}"
|
193
195
|
end
|
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)
|
@@ -130,7 +144,7 @@ module GraphQL
|
|
130
144
|
end
|
131
145
|
|
132
146
|
def validate_non_null_input(value_name, ctx, max_errors: nil)
|
133
|
-
allowed_values = ctx.
|
147
|
+
allowed_values = ctx.types.enum_values(self)
|
134
148
|
matching_value = allowed_values.find { |v| v.graphql_name == value_name }
|
135
149
|
|
136
150
|
if matching_value.nil?
|
@@ -140,33 +154,50 @@ 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
|
-
|
145
|
-
all_values =
|
164
|
+
types = ctx.types
|
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
|
-
all_values = ctx.
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
181
|
+
all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value
|
182
|
+
|
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
|
166
193
|
end
|
167
194
|
|
168
195
|
def inherited(child_class)
|
169
|
-
child_class.
|
196
|
+
if child_class.name
|
197
|
+
# Don't assign a custom error class to anonymous classes
|
198
|
+
# because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
|
199
|
+
child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
|
200
|
+
end
|
170
201
|
super
|
171
202
|
end
|
172
203
|
|
@@ -50,7 +50,7 @@ module GraphQL
|
|
50
50
|
if field.has_default_page_size? && !value.has_default_page_size_override?
|
51
51
|
value.default_page_size = field.default_page_size
|
52
52
|
end
|
53
|
-
if
|
53
|
+
if (custom_t = context.schema.connections.edge_class_for_field(@field))
|
54
54
|
value.edge_class = custom_t
|
55
55
|
end
|
56
56
|
value
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -41,6 +41,18 @@ module GraphQL
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
def directives
|
45
|
+
if @resolver_class && (r_dirs = @resolver_class.directives).any?
|
46
|
+
if (own_dirs = super).any?
|
47
|
+
own_dirs + r_dirs
|
48
|
+
else
|
49
|
+
r_dirs
|
50
|
+
end
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
44
56
|
# @return [Class] The thing this field was defined on (type, mutation, resolver)
|
45
57
|
attr_accessor :owner
|
46
58
|
|
@@ -134,11 +146,16 @@ module GraphQL
|
|
134
146
|
Member::BuildType.to_type_name(@return_type_expr)
|
135
147
|
elsif @resolver_class && @resolver_class.type
|
136
148
|
Member::BuildType.to_type_name(@resolver_class.type)
|
137
|
-
|
149
|
+
elsif type
|
138
150
|
# As a last ditch, try to force loading the return type:
|
139
151
|
type.unwrap.name
|
140
152
|
end
|
141
|
-
|
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
|
142
159
|
else
|
143
160
|
@connection
|
144
161
|
end
|
@@ -224,8 +241,8 @@ module GraphQL
|
|
224
241
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
225
242
|
end
|
226
243
|
if !(resolver_class)
|
227
|
-
if type.nil?
|
228
|
-
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(...)`"
|
229
246
|
end
|
230
247
|
end
|
231
248
|
@original_name = name
|
@@ -290,6 +307,7 @@ module GraphQL
|
|
290
307
|
@ast_node = ast_node
|
291
308
|
@method_conflict_warning = method_conflict_warning
|
292
309
|
@fallback_value = fallback_value
|
310
|
+
@definition_block = nil
|
293
311
|
|
294
312
|
arguments.each do |name, arg|
|
295
313
|
case arg
|
@@ -308,26 +326,14 @@ module GraphQL
|
|
308
326
|
@subscription_scope = subscription_scope
|
309
327
|
|
310
328
|
@extensions = EMPTY_ARRAY
|
311
|
-
|
312
|
-
# This should run before connection extension,
|
313
|
-
# but should it run after the definition block?
|
314
|
-
if scoped?
|
315
|
-
self.extension(ScopeExtension)
|
316
|
-
end
|
317
|
-
|
318
|
-
# The problem with putting this after the definition_block
|
319
|
-
# is that it would override arguments
|
320
|
-
if connection? && connection_extension
|
321
|
-
self.extension(connection_extension)
|
322
|
-
end
|
323
|
-
|
329
|
+
set_pagination_extensions(connection_extension: connection_extension)
|
324
330
|
# Do this last so we have as much context as possible when initializing them:
|
325
331
|
if extensions.any?
|
326
|
-
self.extensions(extensions)
|
332
|
+
self.extensions(extensions, call_after_define: false)
|
327
333
|
end
|
328
334
|
|
329
335
|
if resolver_class && resolver_class.extensions.any?
|
330
|
-
self.extensions(resolver_class.extensions)
|
336
|
+
self.extensions(resolver_class.extensions, call_after_define: false)
|
331
337
|
end
|
332
338
|
|
333
339
|
if directives.any?
|
@@ -341,15 +347,28 @@ module GraphQL
|
|
341
347
|
end
|
342
348
|
|
343
349
|
if block_given?
|
344
|
-
|
345
|
-
|
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)
|
346
365
|
else
|
347
|
-
instance_eval(
|
366
|
+
instance_eval(&@definition_block)
|
348
367
|
end
|
368
|
+
self.extensions.each(&:after_define_apply)
|
369
|
+
@definition_block = nil
|
349
370
|
end
|
350
|
-
|
351
|
-
self.extensions.each(&:after_define_apply)
|
352
|
-
@call_after_define = true
|
371
|
+
self
|
353
372
|
end
|
354
373
|
|
355
374
|
attr_accessor :dynamic_introspection
|
@@ -396,14 +415,14 @@ module GraphQL
|
|
396
415
|
#
|
397
416
|
# @param extensions [Array<Class, Hash<Class => Hash>>] Add extensions to this field. For hash elements, only the first key/value is used.
|
398
417
|
# @return [Array<GraphQL::Schema::FieldExtension>] extensions to apply to this field
|
399
|
-
def extensions(new_extensions = nil)
|
418
|
+
def extensions(new_extensions = nil, call_after_define: !@definition_block)
|
400
419
|
if new_extensions
|
401
420
|
new_extensions.each do |extension_config|
|
402
421
|
if extension_config.is_a?(Hash)
|
403
422
|
extension_class, options = *extension_config.to_a[0]
|
404
|
-
self.extension(extension_class, options)
|
423
|
+
self.extension(extension_class, call_after_define: call_after_define, **options)
|
405
424
|
else
|
406
|
-
self.extension(extension_config)
|
425
|
+
self.extension(extension_config, call_after_define: call_after_define)
|
407
426
|
end
|
408
427
|
end
|
409
428
|
end
|
@@ -421,12 +440,12 @@ module GraphQL
|
|
421
440
|
# @param extension_class [Class] subclass of {Schema::FieldExtension}
|
422
441
|
# @param options [Hash] if provided, given as `options:` when initializing `extension`.
|
423
442
|
# @return [void]
|
424
|
-
def extension(extension_class,
|
443
|
+
def extension(extension_class, call_after_define: !@definition_block, **options)
|
425
444
|
extension_inst = extension_class.new(field: self, options: options)
|
426
445
|
if @extensions.frozen?
|
427
446
|
@extensions = @extensions.dup
|
428
447
|
end
|
429
|
-
if
|
448
|
+
if call_after_define
|
430
449
|
extension_inst.after_define_apply
|
431
450
|
end
|
432
451
|
@extensions << extension_inst
|
@@ -565,16 +584,29 @@ module GraphQL
|
|
565
584
|
class MissingReturnTypeError < GraphQL::Error; end
|
566
585
|
attr_writer :type
|
567
586
|
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
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)
|
573
603
|
end
|
574
|
-
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
575
|
-
Member::BuildType.parse_type(return_type, null: nullable)
|
576
604
|
else
|
577
|
-
@
|
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)
|
578
610
|
end
|
579
611
|
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
580
612
|
# Let this propagate up
|
@@ -606,7 +638,7 @@ module GraphQL
|
|
606
638
|
using_arg_values = false
|
607
639
|
end
|
608
640
|
|
609
|
-
args = context.
|
641
|
+
args = context.types.arguments(self)
|
610
642
|
args.each do |arg|
|
611
643
|
arg_key = arg.keyword
|
612
644
|
if arg_values.key?(arg_key)
|
@@ -885,6 +917,20 @@ ERR
|
|
885
917
|
raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
|
886
918
|
end
|
887
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
|
888
934
|
end
|
889
935
|
end
|
890
936
|
end
|
@@ -149,7 +149,8 @@ module GraphQL
|
|
149
149
|
|
150
150
|
def authorize_arguments(args, values)
|
151
151
|
# remove the `input` wrapper to match values
|
152
|
-
|
152
|
+
input_type = args.find { |a| a.graphql_name == "input" }.type.unwrap
|
153
|
+
input_args = context.types.arguments(input_type)
|
153
154
|
super(input_args, values)
|
154
155
|
end
|
155
156
|
end
|
@@ -23,7 +23,8 @@ module GraphQL
|
|
23
23
|
@ruby_style_hash = ruby_kwargs
|
24
24
|
@arguments = arguments
|
25
25
|
# Apply prepares, not great to have it duplicated here.
|
26
|
-
self.class.arguments(context).each_value
|
26
|
+
arg_defns = context ? context.types.arguments(self.class) : self.class.arguments(context).each_value
|
27
|
+
arg_defns.each do |arg_defn|
|
27
28
|
ruby_kwargs_key = arg_defn.keyword
|
28
29
|
if @ruby_style_hash.key?(ruby_kwargs_key)
|
29
30
|
# Weirdly, procs are applied during coercion, but not methods.
|
@@ -58,7 +59,7 @@ module GraphQL
|
|
58
59
|
def self.authorized?(obj, value, ctx)
|
59
60
|
# Authorize each argument (but this doesn't apply if `prepare` is implemented):
|
60
61
|
if value.respond_to?(:key?)
|
61
|
-
arguments(
|
62
|
+
ctx.types.arguments(self).each do |input_obj_arg|
|
62
63
|
if value.key?(input_obj_arg.keyword) &&
|
63
64
|
!input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
|
64
65
|
return false
|
@@ -149,7 +150,7 @@ module GraphQL
|
|
149
150
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object."
|
150
151
|
|
151
152
|
def validate_non_null_input(input, ctx, max_errors: nil)
|
152
|
-
|
153
|
+
types = ctx.types
|
153
154
|
|
154
155
|
if input.is_a?(Array)
|
155
156
|
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
@@ -161,9 +162,9 @@ module GraphQL
|
|
161
162
|
end
|
162
163
|
|
163
164
|
# Inject missing required arguments
|
164
|
-
missing_required_inputs =
|
165
|
-
if !input.key?(
|
166
|
-
m[
|
165
|
+
missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
|
166
|
+
if !input.key?(argument.graphql_name) && argument.type.non_null? && types.argument(self, argument.graphql_name)
|
167
|
+
m[argument.graphql_name] = nil
|
167
168
|
end
|
168
169
|
|
169
170
|
m
|
@@ -172,7 +173,7 @@ module GraphQL
|
|
172
173
|
result = nil
|
173
174
|
[input, missing_required_inputs].each do |args_to_validate|
|
174
175
|
args_to_validate.each do |argument_name, value|
|
175
|
-
argument =
|
176
|
+
argument = types.argument(self, argument_name)
|
176
177
|
# Items in the input that are unexpected
|
177
178
|
if argument.nil?
|
178
179
|
result ||= Query::InputValidationResult.new
|
@@ -82,13 +82,29 @@ module GraphQL
|
|
82
82
|
super
|
83
83
|
end
|
84
84
|
|
85
|
+
# Register other Interface or Object types as implementers of this Interface.
|
86
|
+
#
|
87
|
+
# When those Interfaces or Objects aren't used as the return values of fields,
|
88
|
+
# they may have to be registered using this method so that GraphQL-Ruby can find them.
|
89
|
+
# @param types [Class, Module]
|
90
|
+
# @return [Array<Module, Class>] Implementers of this interface, if they're registered
|
85
91
|
def orphan_types(*types)
|
86
92
|
if types.any?
|
87
|
-
@orphan_types
|
93
|
+
@orphan_types ||= []
|
94
|
+
@orphan_types.concat(types)
|
88
95
|
else
|
89
|
-
|
90
|
-
|
91
|
-
|
96
|
+
if defined?(@orphan_types)
|
97
|
+
all_orphan_types = @orphan_types.dup
|
98
|
+
if defined?(super)
|
99
|
+
all_orphan_types += super
|
100
|
+
all_orphan_types.uniq!
|
101
|
+
end
|
102
|
+
all_orphan_types
|
103
|
+
elsif defined?(super)
|
104
|
+
super
|
105
|
+
else
|
106
|
+
EmptyObjects::EMPTY_ARRAY
|
107
|
+
end
|
92
108
|
end
|
93
109
|
end
|
94
110
|
|
@@ -25,7 +25,7 @@ module GraphQL
|
|
25
25
|
load_constant(:DirectiveLocationEnum)
|
26
26
|
]
|
27
27
|
@types = {}
|
28
|
-
@possible_types = {}.
|
28
|
+
@possible_types = {}.compare_by_identity
|
29
29
|
type_defns.each do |t|
|
30
30
|
@types[t.graphql_name] = t
|
31
31
|
@possible_types[t] = [t]
|
@@ -69,7 +69,7 @@ module GraphQL
|
|
69
69
|
def resolve_late_bindings
|
70
70
|
@types.each do |name, t|
|
71
71
|
if t.kind.fields?
|
72
|
-
t.
|
72
|
+
t.all_field_definitions.each do |field_defn|
|
73
73
|
field_defn.type = resolve_late_binding(field_defn.type)
|
74
74
|
end
|
75
75
|
end
|
@@ -90,7 +90,8 @@ module GraphQL
|
|
90
90
|
def resolve_late_binding(late_bound_type)
|
91
91
|
case late_bound_type
|
92
92
|
when GraphQL::Schema::LateBoundType
|
93
|
-
|
93
|
+
type_name = late_bound_type.name
|
94
|
+
@types[type_name] || @schema.get_type(type_name)
|
94
95
|
when GraphQL::Schema::List
|
95
96
|
resolve_late_binding(late_bound_type.of_type).to_list_type
|
96
97
|
when GraphQL::Schema::NonNull
|
@@ -113,19 +114,7 @@ module GraphQL
|
|
113
114
|
|
114
115
|
def get_fields_from_class(class_sym:)
|
115
116
|
object_type_defn = load_constant(class_sym)
|
116
|
-
|
117
|
-
if object_type_defn.is_a?(Module)
|
118
|
-
object_type_defn.fields
|
119
|
-
else
|
120
|
-
extracted_field_defns = {}
|
121
|
-
object_class = object_type_defn.metadata[:type_class]
|
122
|
-
object_type_defn.all_fields.each do |field_defn|
|
123
|
-
inner_resolve = field_defn.resolve_proc
|
124
|
-
resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve)
|
125
|
-
extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate)
|
126
|
-
end
|
127
|
-
extracted_field_defns
|
128
|
-
end
|
117
|
+
object_type_defn.fields
|
129
118
|
end
|
130
119
|
|
131
120
|
# This is probably not 100% robust -- but it has to be good enough to avoid modifying the built-in introspection types
|