graphql 2.3.7 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install_generator.rb +46 -0
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis/field_usage.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +3 -3
- data/lib/graphql/analysis/visitor.rb +7 -6
- data/lib/graphql/analysis.rb +1 -1
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +3 -2
- data/lib/graphql/dataloader/source.rb +6 -3
- data/lib/graphql/dataloader.rb +35 -11
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- data/lib/graphql/execution/interpreter/runtime.rb +29 -25
- data/lib/graphql/execution/interpreter.rb +2 -0
- 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/invalid_null_error.rb +1 -1
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +55 -28
- data/lib/graphql/language/lexer.rb +15 -15
- data/lib/graphql/language/nodes.rb +24 -16
- data/lib/graphql/language/parser.rb +14 -1
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +6 -6
- data/lib/graphql/query/context.rb +7 -1
- data/lib/graphql/query/null_context.rb +2 -2
- data/lib/graphql/query.rb +60 -8
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -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 +6 -2
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +9 -1
- data/lib/graphql/schema/directive/flagged.rb +1 -1
- data/lib/graphql/schema/enum.rb +54 -20
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +92 -37
- data/lib/graphql/schema/has_single_input_argument.rb +2 -1
- data/lib/graphql/schema/input_object.rb +27 -13
- data/lib/graphql/schema/interface.rb +21 -4
- data/lib/graphql/schema/introspection_system.rb +5 -16
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- 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/printer.rb +1 -0
- data/lib/graphql/schema/resolver.rb +7 -9
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/validator/all_validator.rb +62 -0
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/validator.rb +2 -0
- data/lib/graphql/schema/visibility/migration.rb +186 -0
- data/lib/graphql/schema/visibility/profile.rb +523 -0
- data/lib/graphql/schema/visibility.rb +75 -0
- data/lib/graphql/schema/warden.rb +156 -10
- data/lib/graphql/schema.rb +283 -85
- 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 +3 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +3 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
- 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 +11 -2
- data/lib/graphql/static_validation/validation_context.rb +17 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +5 -3
- data/lib/graphql/testing/helpers.rb +9 -5
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- 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 +29 -8
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
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,11 @@ 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 [String] :comment, the GraphQL comment for this value, present in documentation
|
|
63
|
+
# @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
|
|
64
|
+
# @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
|
|
50
65
|
# @return [void]
|
|
51
66
|
# @see {Schema::EnumValue} which handles these inputs by default
|
|
52
67
|
def value(*args, **kwargs, &block)
|
|
@@ -130,7 +145,7 @@ module GraphQL
|
|
|
130
145
|
end
|
|
131
146
|
|
|
132
147
|
def validate_non_null_input(value_name, ctx, max_errors: nil)
|
|
133
|
-
allowed_values = ctx.
|
|
148
|
+
allowed_values = ctx.types.enum_values(self)
|
|
134
149
|
matching_value = allowed_values.find { |v| v.graphql_name == value_name }
|
|
135
150
|
|
|
136
151
|
if matching_value.nil?
|
|
@@ -138,35 +153,54 @@ module GraphQL
|
|
|
138
153
|
else
|
|
139
154
|
nil
|
|
140
155
|
end
|
|
156
|
+
# rescue MissingValuesError
|
|
157
|
+
# nil
|
|
141
158
|
end
|
|
142
159
|
|
|
160
|
+
# Called by the runtime when a field returns a value to give back to the client.
|
|
161
|
+
# This method checks that the incoming {value} matches one of the enum's defined values.
|
|
162
|
+
# @param value [Object] Any value matching the values for this enum.
|
|
163
|
+
# @param ctx [GraphQL::Query::Context]
|
|
164
|
+
# @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized.
|
|
165
|
+
# @return [String] The GraphQL-ready string for {value}
|
|
143
166
|
def coerce_result(value, ctx)
|
|
144
|
-
|
|
145
|
-
all_values =
|
|
167
|
+
types = ctx.types
|
|
168
|
+
all_values = types ? types.enum_values(self) : values.each_value
|
|
146
169
|
enum_value = all_values.find { |val| val.value == value }
|
|
147
|
-
if enum_value
|
|
170
|
+
if enum_value && (was_authed = enum_value.authorized?(ctx))
|
|
148
171
|
enum_value.graphql_name
|
|
149
172
|
else
|
|
150
|
-
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
|
|
173
|
+
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx, authorized: was_authed)
|
|
151
174
|
end
|
|
152
175
|
end
|
|
153
176
|
|
|
177
|
+
# Called by the runtime with incoming string representations from a query.
|
|
178
|
+
# It will match the string to a configured by name or by Ruby value.
|
|
179
|
+
# @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration
|
|
180
|
+
# @param ctx [GraphQL::Query::Context]
|
|
181
|
+
# @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}.
|
|
182
|
+
# @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue}
|
|
154
183
|
def coerce_input(value_name, ctx)
|
|
155
|
-
all_values = ctx.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
184
|
+
all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value
|
|
185
|
+
|
|
186
|
+
# This tries matching by incoming GraphQL string, then checks Ruby-defined values
|
|
187
|
+
if v = (all_values.find { |val| val.graphql_name == value_name } || all_values.find { |val| val.value == value_name })
|
|
188
|
+
if v.authorized?(ctx)
|
|
189
|
+
v.value
|
|
190
|
+
else
|
|
191
|
+
raise GraphQL::UnauthorizedEnumValueError.new(type: self, enum_value: v, context: ctx)
|
|
192
|
+
end
|
|
163
193
|
else
|
|
164
194
|
nil
|
|
165
195
|
end
|
|
166
196
|
end
|
|
167
197
|
|
|
168
198
|
def inherited(child_class)
|
|
169
|
-
child_class.
|
|
199
|
+
if child_class.name
|
|
200
|
+
# Don't assign a custom error class to anonymous classes
|
|
201
|
+
# because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
|
|
202
|
+
child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
|
|
203
|
+
end
|
|
170
204
|
super
|
|
171
205
|
end
|
|
172
206
|
|
|
@@ -30,10 +30,11 @@ module GraphQL
|
|
|
30
30
|
# @return [Class] The enum type that owns this value
|
|
31
31
|
attr_reader :owner
|
|
32
32
|
|
|
33
|
-
def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block)
|
|
33
|
+
def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, comment: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block)
|
|
34
34
|
@graphql_name = graphql_name.to_s
|
|
35
35
|
GraphQL::NameValidator.validate!(@graphql_name)
|
|
36
36
|
@description = desc || description
|
|
37
|
+
@comment = comment
|
|
37
38
|
@value = value == NOT_CONFIGURED ? @graphql_name : value
|
|
38
39
|
if deprecation_reason
|
|
39
40
|
self.deprecation_reason = deprecation_reason
|
|
@@ -58,6 +59,13 @@ module GraphQL
|
|
|
58
59
|
@description
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
def comment(new_comment = nil)
|
|
63
|
+
if new_comment
|
|
64
|
+
@comment = new_comment
|
|
65
|
+
end
|
|
66
|
+
@comment
|
|
67
|
+
end
|
|
68
|
+
|
|
61
69
|
def value(new_val = nil)
|
|
62
70
|
unless new_val.nil?
|
|
63
71
|
@value = new_val
|
|
@@ -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
|
@@ -106,7 +106,7 @@ module GraphQL
|
|
|
106
106
|
# @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
|
|
107
107
|
# @return [GraphQL::Schema:Field] an instance of `self`
|
|
108
108
|
# @see {.initialize} for other options
|
|
109
|
-
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
|
109
|
+
def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
|
110
110
|
if (resolver_class = resolver || mutation || subscription)
|
|
111
111
|
# Add a reference to that parent class
|
|
112
112
|
kwargs[:resolver_class] = resolver_class
|
|
@@ -116,6 +116,10 @@ module GraphQL
|
|
|
116
116
|
kwargs[:name] = name
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
if comment
|
|
120
|
+
kwargs[:comment] = comment
|
|
121
|
+
end
|
|
122
|
+
|
|
119
123
|
if !type.nil?
|
|
120
124
|
if desc
|
|
121
125
|
if kwargs[:description]
|
|
@@ -146,11 +150,16 @@ module GraphQL
|
|
|
146
150
|
Member::BuildType.to_type_name(@return_type_expr)
|
|
147
151
|
elsif @resolver_class && @resolver_class.type
|
|
148
152
|
Member::BuildType.to_type_name(@resolver_class.type)
|
|
149
|
-
|
|
153
|
+
elsif type
|
|
150
154
|
# As a last ditch, try to force loading the return type:
|
|
151
155
|
type.unwrap.name
|
|
152
156
|
end
|
|
153
|
-
|
|
157
|
+
if return_type_name
|
|
158
|
+
@connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
|
|
159
|
+
else
|
|
160
|
+
# TODO set this when type is set by method
|
|
161
|
+
false # not loaded yet?
|
|
162
|
+
end
|
|
154
163
|
else
|
|
155
164
|
@connection
|
|
156
165
|
end
|
|
@@ -207,6 +216,7 @@ module GraphQL
|
|
|
207
216
|
# @param owner [Class] The type that this field belongs to
|
|
208
217
|
# @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
|
|
209
218
|
# @param description [String] Field description
|
|
219
|
+
# @param comment [String] Field comment
|
|
210
220
|
# @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
|
|
211
221
|
# @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
|
|
212
222
|
# @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
|
@@ -231,13 +241,13 @@ module GraphQL
|
|
|
231
241
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
|
232
242
|
# @param validates [Array<Hash>] Configurations for validating this field
|
|
233
243
|
# @param fallback_value [Object] A fallback value if the method is not defined
|
|
234
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
244
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
235
245
|
if name.nil?
|
|
236
246
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
237
247
|
end
|
|
238
248
|
if !(resolver_class)
|
|
239
|
-
if type.nil?
|
|
240
|
-
raise ArgumentError, "missing second `type` argument or
|
|
249
|
+
if type.nil? && !block_given?
|
|
250
|
+
raise ArgumentError, "missing second `type` argument, keyword `type:`, or a block containing `type(...)`"
|
|
241
251
|
end
|
|
242
252
|
end
|
|
243
253
|
@original_name = name
|
|
@@ -247,6 +257,7 @@ module GraphQL
|
|
|
247
257
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
|
248
258
|
|
|
249
259
|
@description = description
|
|
260
|
+
@comment = comment
|
|
250
261
|
@type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
|
|
251
262
|
|
|
252
263
|
self.deprecation_reason = deprecation_reason
|
|
@@ -302,6 +313,7 @@ module GraphQL
|
|
|
302
313
|
@ast_node = ast_node
|
|
303
314
|
@method_conflict_warning = method_conflict_warning
|
|
304
315
|
@fallback_value = fallback_value
|
|
316
|
+
@definition_block = definition_block
|
|
305
317
|
|
|
306
318
|
arguments.each do |name, arg|
|
|
307
319
|
case arg
|
|
@@ -321,18 +333,7 @@ module GraphQL
|
|
|
321
333
|
|
|
322
334
|
@extensions = EMPTY_ARRAY
|
|
323
335
|
@call_after_define = false
|
|
324
|
-
|
|
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
|
-
|
|
336
|
+
set_pagination_extensions(connection_extension: connection_extension)
|
|
336
337
|
# Do this last so we have as much context as possible when initializing them:
|
|
337
338
|
if extensions.any?
|
|
338
339
|
self.extensions(extensions)
|
|
@@ -352,16 +353,29 @@ module GraphQL
|
|
|
352
353
|
self.validates(validates)
|
|
353
354
|
end
|
|
354
355
|
|
|
355
|
-
if
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
if @definition_block.nil?
|
|
357
|
+
self.extensions.each(&:after_define_apply)
|
|
358
|
+
@call_after_define = true
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Calls the definition block, if one was given.
|
|
363
|
+
# This is deferred so that references to the return type
|
|
364
|
+
# can be lazily evaluated, reducing Rails boot time.
|
|
365
|
+
# @return [self]
|
|
366
|
+
# @api private
|
|
367
|
+
def ensure_loaded
|
|
368
|
+
if @definition_block
|
|
369
|
+
if @definition_block.arity == 1
|
|
370
|
+
@definition_block.call(self)
|
|
358
371
|
else
|
|
359
|
-
instance_eval(
|
|
372
|
+
instance_eval(&@definition_block)
|
|
360
373
|
end
|
|
374
|
+
self.extensions.each(&:after_define_apply)
|
|
375
|
+
@call_after_define = true
|
|
376
|
+
@definition_block = nil
|
|
361
377
|
end
|
|
362
|
-
|
|
363
|
-
self.extensions.each(&:after_define_apply)
|
|
364
|
-
@call_after_define = true
|
|
378
|
+
self
|
|
365
379
|
end
|
|
366
380
|
|
|
367
381
|
attr_accessor :dynamic_introspection
|
|
@@ -393,6 +407,20 @@ module GraphQL
|
|
|
393
407
|
end
|
|
394
408
|
end
|
|
395
409
|
|
|
410
|
+
# @param text [String]
|
|
411
|
+
# @return [String, nil]
|
|
412
|
+
def comment(text = nil)
|
|
413
|
+
if text
|
|
414
|
+
@comment = text
|
|
415
|
+
elsif !NOT_CONFIGURED.equal?(@comment)
|
|
416
|
+
@comment
|
|
417
|
+
elsif @resolver_class
|
|
418
|
+
@resolver_class.comment
|
|
419
|
+
else
|
|
420
|
+
nil
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
396
424
|
# Read extension instances from this field,
|
|
397
425
|
# or add new classes/options to be initialized on this field.
|
|
398
426
|
# Extensions are executed in the order they are added.
|
|
@@ -413,7 +441,7 @@ module GraphQL
|
|
|
413
441
|
new_extensions.each do |extension_config|
|
|
414
442
|
if extension_config.is_a?(Hash)
|
|
415
443
|
extension_class, options = *extension_config.to_a[0]
|
|
416
|
-
self.extension(extension_class, options)
|
|
444
|
+
self.extension(extension_class, **options)
|
|
417
445
|
else
|
|
418
446
|
self.extension(extension_config)
|
|
419
447
|
end
|
|
@@ -433,7 +461,7 @@ module GraphQL
|
|
|
433
461
|
# @param extension_class [Class] subclass of {Schema::FieldExtension}
|
|
434
462
|
# @param options [Hash] if provided, given as `options:` when initializing `extension`.
|
|
435
463
|
# @return [void]
|
|
436
|
-
def extension(extension_class, options
|
|
464
|
+
def extension(extension_class, **options)
|
|
437
465
|
extension_inst = extension_class.new(field: self, options: options)
|
|
438
466
|
if @extensions.frozen?
|
|
439
467
|
@extensions = @extensions.dup
|
|
@@ -483,7 +511,7 @@ module GraphQL
|
|
|
483
511
|
if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
|
|
484
512
|
max_possible_page_size = arguments[:last]
|
|
485
513
|
end
|
|
486
|
-
elsif arguments.is_a?(GraphQL::UnauthorizedError)
|
|
514
|
+
elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError)
|
|
487
515
|
raise arguments
|
|
488
516
|
end
|
|
489
517
|
|
|
@@ -577,16 +605,29 @@ module GraphQL
|
|
|
577
605
|
class MissingReturnTypeError < GraphQL::Error; end
|
|
578
606
|
attr_writer :type
|
|
579
607
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
608
|
+
# Get or set the return type of this field.
|
|
609
|
+
#
|
|
610
|
+
# It may return nil if no type was configured or if the given definition block wasn't called yet.
|
|
611
|
+
# @param new_type [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List] A GraphQL return type
|
|
612
|
+
# @return [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil] the configured type for this field
|
|
613
|
+
def type(new_type = NOT_CONFIGURED)
|
|
614
|
+
if NOT_CONFIGURED.equal?(new_type)
|
|
615
|
+
if @resolver_class
|
|
616
|
+
return_type = @return_type_expr || @resolver_class.type_expr
|
|
617
|
+
if return_type.nil?
|
|
618
|
+
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)"
|
|
619
|
+
end
|
|
620
|
+
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
|
621
|
+
Member::BuildType.parse_type(return_type, null: nullable)
|
|
622
|
+
elsif !@return_type_expr.nil?
|
|
623
|
+
@type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
|
585
624
|
end
|
|
586
|
-
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
|
587
|
-
Member::BuildType.parse_type(return_type, null: nullable)
|
|
588
625
|
else
|
|
589
|
-
@
|
|
626
|
+
@return_type_expr = new_type
|
|
627
|
+
# If `type` is set in the definition block, then the `connection_extension: ...` given as a keyword won't be used, hmm...
|
|
628
|
+
# Also, arguments added by `connection_extension` will clobber anything previously defined,
|
|
629
|
+
# so `type(...)` should go first.
|
|
630
|
+
set_pagination_extensions(connection_extension: self.class.connection_extension)
|
|
590
631
|
end
|
|
591
632
|
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
|
592
633
|
# Let this propagate up
|
|
@@ -618,7 +659,7 @@ module GraphQL
|
|
|
618
659
|
using_arg_values = false
|
|
619
660
|
end
|
|
620
661
|
|
|
621
|
-
args = context.
|
|
662
|
+
args = context.types.arguments(self)
|
|
622
663
|
args.each do |arg|
|
|
623
664
|
arg_key = arg.keyword
|
|
624
665
|
if arg_values.key?(arg_key)
|
|
@@ -897,6 +938,20 @@ ERR
|
|
|
897
938
|
raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
|
|
898
939
|
end
|
|
899
940
|
end
|
|
941
|
+
|
|
942
|
+
def set_pagination_extensions(connection_extension:)
|
|
943
|
+
# This should run before connection extension,
|
|
944
|
+
# but should it run after the definition block?
|
|
945
|
+
if scoped?
|
|
946
|
+
self.extension(ScopeExtension, call_after_define: false)
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# The problem with putting this after the definition_block
|
|
950
|
+
# is that it would override arguments
|
|
951
|
+
if connection? && connection_extension
|
|
952
|
+
self.extension(connection_extension, call_after_define: false)
|
|
953
|
+
end
|
|
954
|
+
end
|
|
900
955
|
end
|
|
901
956
|
end
|
|
902
957
|
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
|
|
@@ -132,12 +133,14 @@ module GraphQL
|
|
|
132
133
|
end
|
|
133
134
|
# Add a method access
|
|
134
135
|
method_name = argument_defn.keyword
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
suppress_redefinition_warning do
|
|
137
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
|
138
|
+
def #{method_name}
|
|
139
|
+
self[#{method_name.inspect}]
|
|
140
|
+
end
|
|
141
|
+
alias_method :#{method_name}, :#{method_name}
|
|
142
|
+
RUBY
|
|
143
|
+
end
|
|
141
144
|
argument_defn
|
|
142
145
|
end
|
|
143
146
|
|
|
@@ -149,7 +152,7 @@ module GraphQL
|
|
|
149
152
|
INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object."
|
|
150
153
|
|
|
151
154
|
def validate_non_null_input(input, ctx, max_errors: nil)
|
|
152
|
-
|
|
155
|
+
types = ctx.types
|
|
153
156
|
|
|
154
157
|
if input.is_a?(Array)
|
|
155
158
|
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
|
|
@@ -161,9 +164,9 @@ module GraphQL
|
|
|
161
164
|
end
|
|
162
165
|
|
|
163
166
|
# Inject missing required arguments
|
|
164
|
-
missing_required_inputs =
|
|
165
|
-
if !input.key?(
|
|
166
|
-
m[
|
|
167
|
+
missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
|
|
168
|
+
if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? && types.argument(self, argument.graphql_name)
|
|
169
|
+
m[argument.graphql_name] = nil
|
|
167
170
|
end
|
|
168
171
|
|
|
169
172
|
m
|
|
@@ -172,7 +175,7 @@ module GraphQL
|
|
|
172
175
|
result = nil
|
|
173
176
|
[input, missing_required_inputs].each do |args_to_validate|
|
|
174
177
|
args_to_validate.each do |argument_name, value|
|
|
175
|
-
argument =
|
|
178
|
+
argument = types.argument(self, argument_name)
|
|
176
179
|
# Items in the input that are unexpected
|
|
177
180
|
if argument.nil?
|
|
178
181
|
result ||= Query::InputValidationResult.new
|
|
@@ -242,6 +245,17 @@ module GraphQL
|
|
|
242
245
|
|
|
243
246
|
result
|
|
244
247
|
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
# Suppress redefinition warning for objectId arguments
|
|
252
|
+
def suppress_redefinition_warning
|
|
253
|
+
verbose = $VERBOSE
|
|
254
|
+
$VERBOSE = nil
|
|
255
|
+
yield
|
|
256
|
+
ensure
|
|
257
|
+
$VERBOSE = verbose
|
|
258
|
+
end
|
|
245
259
|
end
|
|
246
260
|
|
|
247
261
|
private
|
|
@@ -63,6 +63,7 @@ module GraphQL
|
|
|
63
63
|
|
|
64
64
|
child_class.introspection(introspection)
|
|
65
65
|
child_class.description(description)
|
|
66
|
+
child_class.comment(nil)
|
|
66
67
|
# If interfaces are mixed into each other, only define this class once
|
|
67
68
|
if !child_class.const_defined?(:UnresolvedTypeError, false)
|
|
68
69
|
add_unresolved_type_error(child_class)
|
|
@@ -82,13 +83,29 @@ module GraphQL
|
|
|
82
83
|
super
|
|
83
84
|
end
|
|
84
85
|
|
|
86
|
+
# Register other Interface or Object types as implementers of this Interface.
|
|
87
|
+
#
|
|
88
|
+
# When those Interfaces or Objects aren't used as the return values of fields,
|
|
89
|
+
# they may have to be registered using this method so that GraphQL-Ruby can find them.
|
|
90
|
+
# @param types [Class, Module]
|
|
91
|
+
# @return [Array<Module, Class>] Implementers of this interface, if they're registered
|
|
85
92
|
def orphan_types(*types)
|
|
86
93
|
if types.any?
|
|
87
|
-
@orphan_types
|
|
94
|
+
@orphan_types ||= []
|
|
95
|
+
@orphan_types.concat(types)
|
|
88
96
|
else
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
if defined?(@orphan_types)
|
|
98
|
+
all_orphan_types = @orphan_types.dup
|
|
99
|
+
if defined?(super)
|
|
100
|
+
all_orphan_types += super
|
|
101
|
+
all_orphan_types.uniq!
|
|
102
|
+
end
|
|
103
|
+
all_orphan_types
|
|
104
|
+
elsif defined?(super)
|
|
105
|
+
super
|
|
106
|
+
else
|
|
107
|
+
EmptyObjects::EMPTY_ARRAY
|
|
108
|
+
end
|
|
92
109
|
end
|
|
93
110
|
end
|
|
94
111
|
|
|
@@ -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
|
|
@@ -50,12 +50,27 @@ module GraphQL
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Call this method to provide a new comment; OR
|
|
54
|
+
# call it without an argument to get the comment
|
|
55
|
+
# @param new_comment [String]
|
|
56
|
+
# @return [String, nil]
|
|
57
|
+
def comment(new_comment = NOT_CONFIGURED)
|
|
58
|
+
if !NOT_CONFIGURED.equal?(new_comment)
|
|
59
|
+
@comment = new_comment
|
|
60
|
+
elsif defined?(@comment)
|
|
61
|
+
@comment
|
|
62
|
+
else
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
53
67
|
# This pushes some configurations _down_ the inheritance tree,
|
|
54
68
|
# in order to prevent repetitive lookups at runtime.
|
|
55
69
|
module ConfigurationExtension
|
|
56
70
|
def inherited(child_class)
|
|
57
71
|
child_class.introspection(introspection)
|
|
58
72
|
child_class.description(description)
|
|
73
|
+
child_class.comment(nil)
|
|
59
74
|
child_class.default_graphql_name = nil
|
|
60
75
|
|
|
61
76
|
if defined?(@graphql_name) && @graphql_name && (self.name.nil? || graphql_name != default_graphql_name)
|