graphql 2.3.5 → 2.3.14
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/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
|