graphql 1.12.10 → 1.13.4
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/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +28 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +15 -3
- data/lib/graphql/backtrace/tracer.rb +7 -4
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/dataloader/source.rb +50 -2
- data/lib/graphql/dataloader.rb +110 -41
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -4
- data/lib/graphql/execution/interpreter/resolve.rb +6 -2
- data/lib/graphql/execution/interpreter/runtime.rb +513 -213
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +10 -4
- data/lib/graphql/introspection/schema_type.rb +3 -3
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +2 -6
- data/lib/graphql/language/document_from_schema_definition.rb +10 -4
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +13 -3
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/connections.rb +40 -16
- data/lib/graphql/pagination/relation_connection.rb +57 -27
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +15 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/query.rb +5 -1
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +247 -0
- data/lib/graphql/schema/argument.rb +103 -45
- data/lib/graphql/schema/build_from_definition.rb +13 -7
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +14 -2
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +70 -11
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +243 -81
- data/lib/graphql/schema/field_extension.rb +89 -2
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +39 -29
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +1 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +145 -57
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/non_null.rb +7 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +12 -17
- data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +75 -65
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +36 -8
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +8 -1
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -5
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +33 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +162 -227
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +8 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
- data/lib/graphql/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +36 -6
- data/lib/graphql/subscriptions/event.rb +68 -31
- data/lib/graphql/subscriptions/serialize.rb +23 -3
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/types/big_int.rb +5 -1
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/has_node_field.rb +2 -2
- data/lib/graphql/types/relay/has_nodes_field.rb +2 -2
- data/lib/graphql/types/relay/node_field.rb +15 -4
- data/lib/graphql/types/relay/nodes_field.rb +14 -4
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -28
- data/readme.md +1 -4
- metadata +17 -21
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
|
@@ -28,7 +28,7 @@ module GraphQL
|
|
|
28
28
|
include Schema::Member::HasPath
|
|
29
29
|
extend Schema::Member::HasPath
|
|
30
30
|
|
|
31
|
-
# @param object [Object]
|
|
31
|
+
# @param object [Object] The application object that this field is being resolved on
|
|
32
32
|
# @param context [GraphQL::Query::Context]
|
|
33
33
|
# @param field [GraphQL::Schema::Field]
|
|
34
34
|
def initialize(object:, context:, field:)
|
|
@@ -37,10 +37,9 @@ module GraphQL
|
|
|
37
37
|
@field = field
|
|
38
38
|
# Since this hash is constantly rebuilt, cache it for this call
|
|
39
39
|
@arguments_by_keyword = {}
|
|
40
|
-
self.class.arguments.each do |name, arg|
|
|
40
|
+
self.class.arguments(context).each do |name, arg|
|
|
41
41
|
@arguments_by_keyword[arg.keyword] = arg
|
|
42
42
|
end
|
|
43
|
-
@arguments_loads_as_type = self.class.arguments_loads_as_type
|
|
44
43
|
@prepared_arguments = nil
|
|
45
44
|
end
|
|
46
45
|
|
|
@@ -110,7 +109,7 @@ module GraphQL
|
|
|
110
109
|
public_send(self.class.resolve_method)
|
|
111
110
|
end
|
|
112
111
|
else
|
|
113
|
-
|
|
112
|
+
raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
|
|
114
113
|
end
|
|
115
114
|
end
|
|
116
115
|
end
|
|
@@ -146,7 +145,25 @@ module GraphQL
|
|
|
146
145
|
# @raise [GraphQL::UnauthorizedError] To signal an authorization failure
|
|
147
146
|
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
|
148
147
|
def authorized?(**inputs)
|
|
149
|
-
self.class
|
|
148
|
+
arg_owner = @field # || self.class
|
|
149
|
+
args = arg_owner.arguments(context)
|
|
150
|
+
authorize_arguments(args, inputs)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
|
154
|
+
#
|
|
155
|
+
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
|
156
|
+
#
|
|
157
|
+
# Any value returned here will be used _instead of_ of the loaded object.
|
|
158
|
+
# @param err [GraphQL::UnauthorizedError]
|
|
159
|
+
def unauthorized_object(err)
|
|
160
|
+
raise err
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def authorize_arguments(args, inputs)
|
|
166
|
+
args.each_value do |argument|
|
|
150
167
|
arg_keyword = argument.keyword
|
|
151
168
|
if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
|
|
152
169
|
arg_auth, err = argument.authorized?(self, arg_value, context)
|
|
@@ -161,8 +178,6 @@ module GraphQL
|
|
|
161
178
|
end
|
|
162
179
|
end
|
|
163
180
|
|
|
164
|
-
private
|
|
165
|
-
|
|
166
181
|
def load_arguments(args)
|
|
167
182
|
prepared_args = {}
|
|
168
183
|
prepare_lazies = []
|
|
@@ -170,18 +185,14 @@ module GraphQL
|
|
|
170
185
|
args.each do |key, value|
|
|
171
186
|
arg_defn = @arguments_by_keyword[key]
|
|
172
187
|
if arg_defn
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if context.schema.lazy?(prepped_value)
|
|
178
|
-
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
|
179
|
-
prepared_args[key] = finished_prepped_value
|
|
180
|
-
end
|
|
188
|
+
prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
|
|
189
|
+
if context.schema.lazy?(prepped_value)
|
|
190
|
+
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
|
191
|
+
prepared_args[key] = finished_prepped_value
|
|
181
192
|
end
|
|
182
193
|
end
|
|
183
194
|
else
|
|
184
|
-
#
|
|
195
|
+
# these are `extras:`
|
|
185
196
|
prepared_args[key] = value
|
|
186
197
|
end
|
|
187
198
|
end
|
|
@@ -194,8 +205,8 @@ module GraphQL
|
|
|
194
205
|
end
|
|
195
206
|
end
|
|
196
207
|
|
|
197
|
-
def
|
|
198
|
-
|
|
208
|
+
def get_argument(name, context = GraphQL::Query::NullContext)
|
|
209
|
+
self.class.get_argument(name, context)
|
|
199
210
|
end
|
|
200
211
|
|
|
201
212
|
class << self
|
|
@@ -218,8 +229,10 @@ module GraphQL
|
|
|
218
229
|
own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
|
|
219
230
|
end
|
|
220
231
|
|
|
221
|
-
#
|
|
222
|
-
#
|
|
232
|
+
# If `true` (default), then the return type for this resolver will be nullable.
|
|
233
|
+
# If `false`, then the return type is non-null.
|
|
234
|
+
#
|
|
235
|
+
# @see #type which sets the return type of this field and accepts a `null:` option
|
|
223
236
|
# @param allow_null [Boolean] Whether or not the response can be null
|
|
224
237
|
def null(allow_null = nil)
|
|
225
238
|
if !allow_null.nil?
|
|
@@ -298,19 +311,38 @@ module GraphQL
|
|
|
298
311
|
end
|
|
299
312
|
|
|
300
313
|
def field_options
|
|
314
|
+
|
|
315
|
+
all_args = {}
|
|
316
|
+
all_argument_definitions.each do |arg|
|
|
317
|
+
if (prev_entry = all_args[arg.graphql_name])
|
|
318
|
+
if prev_entry.is_a?(Array)
|
|
319
|
+
prev_entry << arg
|
|
320
|
+
else
|
|
321
|
+
all_args[arg.graphql_name] = [prev_entry, arg]
|
|
322
|
+
end
|
|
323
|
+
else
|
|
324
|
+
all_args[arg.graphql_name] = arg
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
301
328
|
field_opts = {
|
|
302
329
|
type: type_expr,
|
|
303
330
|
description: description,
|
|
304
331
|
extras: extras,
|
|
305
332
|
resolver_method: :resolve_with_support,
|
|
306
333
|
resolver_class: self,
|
|
307
|
-
arguments:
|
|
334
|
+
arguments: all_args,
|
|
308
335
|
null: null,
|
|
309
336
|
complexity: complexity,
|
|
310
|
-
extensions: extensions,
|
|
311
337
|
broadcastable: broadcastable?,
|
|
312
338
|
}
|
|
313
339
|
|
|
340
|
+
# If there aren't any, then the returned array is `[].freeze`,
|
|
341
|
+
# but passing that along breaks some user code.
|
|
342
|
+
if (exts = extensions).any?
|
|
343
|
+
field_opts[:extensions] = exts
|
|
344
|
+
end
|
|
345
|
+
|
|
314
346
|
if has_max_page_size?
|
|
315
347
|
field_opts[:max_page_size] = max_page_size
|
|
316
348
|
end
|
|
@@ -327,65 +359,43 @@ module GraphQL
|
|
|
327
359
|
# also add some preparation hook methods which will be used for this argument
|
|
328
360
|
# @see {GraphQL::Schema::Argument#initialize} for the signature
|
|
329
361
|
def argument(*args, **kwargs, &block)
|
|
330
|
-
loads = kwargs[:loads]
|
|
331
362
|
# Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
|
|
332
363
|
# so that we can support `#load_{x}` methods below.
|
|
333
|
-
|
|
334
|
-
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
|
335
|
-
|
|
336
|
-
if !method_defined?(:"load_#{arg_defn.keyword}")
|
|
337
|
-
if loads && arg_defn.type.list?
|
|
338
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
339
|
-
def load_#{arg_defn.keyword}(values)
|
|
340
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
|
341
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
|
342
|
-
context.schema.after_lazy(values) do |values2|
|
|
343
|
-
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
RUBY
|
|
347
|
-
elsif loads
|
|
348
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
349
|
-
def load_#{arg_defn.keyword}(value)
|
|
350
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
|
351
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
|
352
|
-
load_application_object(argument, lookup_as_type, value, context)
|
|
353
|
-
end
|
|
354
|
-
RUBY
|
|
355
|
-
else
|
|
356
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
357
|
-
def load_#{arg_defn.keyword}(value)
|
|
358
|
-
value
|
|
359
|
-
end
|
|
360
|
-
RUBY
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
arg_defn
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
# @api private
|
|
368
|
-
def arguments_loads_as_type
|
|
369
|
-
inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
|
|
370
|
-
inherited_lookups.merge(own_arguments_loads_as_type)
|
|
364
|
+
super(*args, from_resolver: true, **kwargs)
|
|
371
365
|
end
|
|
372
366
|
|
|
373
367
|
# Registers new extension
|
|
374
368
|
# @param extension [Class] Extension class
|
|
375
369
|
# @param options [Hash] Optional extension options
|
|
376
370
|
def extension(extension, **options)
|
|
377
|
-
|
|
371
|
+
@own_extensions ||= []
|
|
372
|
+
@own_extensions << {extension => options}
|
|
378
373
|
end
|
|
379
374
|
|
|
380
375
|
# @api private
|
|
381
376
|
def extensions
|
|
382
|
-
|
|
377
|
+
own_exts = @own_extensions
|
|
378
|
+
# Jump through some hoops to avoid creating arrays when we don't actually need them
|
|
379
|
+
if superclass.respond_to?(:extensions)
|
|
380
|
+
s_exts = superclass.extensions
|
|
381
|
+
if own_exts
|
|
382
|
+
if s_exts.any?
|
|
383
|
+
own_exts + s_exts
|
|
384
|
+
else
|
|
385
|
+
own_exts
|
|
386
|
+
end
|
|
387
|
+
else
|
|
388
|
+
s_exts
|
|
389
|
+
end
|
|
390
|
+
else
|
|
391
|
+
own_exts || EMPTY_ARRAY
|
|
392
|
+
end
|
|
383
393
|
end
|
|
384
394
|
|
|
385
395
|
private
|
|
386
396
|
|
|
387
|
-
def
|
|
388
|
-
@
|
|
397
|
+
def own_extensions
|
|
398
|
+
@own_extensions
|
|
389
399
|
end
|
|
390
400
|
end
|
|
391
401
|
end
|
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
|
14
14
|
class Subscription < GraphQL::Schema::Resolver
|
|
15
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
|
16
16
|
extend GraphQL::Schema::Member::HasFields
|
|
17
|
-
|
|
17
|
+
NO_UPDATE = :no_update
|
|
18
18
|
# The generated payload type is required; If there's no payload,
|
|
19
19
|
# propagate null.
|
|
20
20
|
null false
|
|
@@ -58,11 +58,9 @@ module GraphQL
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
#
|
|
61
|
+
# The default implementation returns nothing on subscribe.
|
|
62
62
|
# Override it to return an object or
|
|
63
|
-
# `:no_response` to return nothing.
|
|
64
|
-
#
|
|
65
|
-
# The default is `:no_response`.
|
|
63
|
+
# `:no_response` to (explicitly) return nothing.
|
|
66
64
|
def subscribe(args = {})
|
|
67
65
|
:no_response
|
|
68
66
|
end
|
|
@@ -70,7 +68,7 @@ module GraphQL
|
|
|
70
68
|
# Wrap the user-provided `#update` hook
|
|
71
69
|
def resolve_update(**args)
|
|
72
70
|
ret_val = args.any? ? update(**args) : update
|
|
73
|
-
if ret_val ==
|
|
71
|
+
if ret_val == NO_UPDATE
|
|
74
72
|
context.namespace(:subscriptions)[:no_update] = true
|
|
75
73
|
context.skip
|
|
76
74
|
else
|
|
@@ -79,7 +77,7 @@ module GraphQL
|
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
# The default implementation returns the root object.
|
|
82
|
-
# Override it to return
|
|
80
|
+
# Override it to return {NO_UPDATE} if you want to
|
|
83
81
|
# skip updates sometimes. Or override it to return a different object.
|
|
84
82
|
def update(args = {})
|
|
85
83
|
object
|
|
@@ -105,10 +103,12 @@ module GraphQL
|
|
|
105
103
|
# Call this method to provide a new subscription_scope; OR
|
|
106
104
|
# call it without an argument to get the subscription_scope
|
|
107
105
|
# @param new_scope [Symbol]
|
|
106
|
+
# @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
|
|
108
107
|
# @return [Symbol]
|
|
109
|
-
def self.subscription_scope(new_scope = READING_SCOPE)
|
|
108
|
+
def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
|
|
110
109
|
if new_scope != READING_SCOPE
|
|
111
110
|
@subscription_scope = new_scope
|
|
111
|
+
@subscription_scope_optional = optional
|
|
112
112
|
elsif defined?(@subscription_scope)
|
|
113
113
|
@subscription_scope
|
|
114
114
|
else
|
|
@@ -116,6 +116,34 @@ module GraphQL
|
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
def self.subscription_scope_optional?
|
|
120
|
+
if defined?(@subscription_scope_optional)
|
|
121
|
+
@subscription_scope_optional
|
|
122
|
+
else
|
|
123
|
+
find_inherited_value(:subscription_scope_optional, false)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# This is called during initial subscription to get a "name" for this subscription.
|
|
128
|
+
# Later, when `.trigger` is called, this will be called again to build another "name".
|
|
129
|
+
# Any subscribers with matching topic will begin the update flow.
|
|
130
|
+
#
|
|
131
|
+
# The default implementation creates a string using the field name, subscription scope, and argument keys and values.
|
|
132
|
+
# In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
|
|
133
|
+
#
|
|
134
|
+
# To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
|
|
135
|
+
# Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
|
|
136
|
+
# update should be filtered out.
|
|
137
|
+
#
|
|
138
|
+
# @see {#update} for how to skip updates when an event comes with a matching topic.
|
|
139
|
+
# @param arguments [Hash<String => Object>] The arguments for this topic, in GraphQL-style (camelized strings)
|
|
140
|
+
# @param field [GraphQL::Schema::Field]
|
|
141
|
+
# @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions).
|
|
142
|
+
# @return [String] An identifier corresponding to a stream of updates
|
|
143
|
+
def self.topic_for(arguments:, field:, scope:)
|
|
144
|
+
Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
|
|
145
|
+
end
|
|
146
|
+
|
|
119
147
|
# Overriding Resolver#field_options to include subscription_scope
|
|
120
148
|
def self.field_options
|
|
121
149
|
super.merge(
|
|
@@ -173,7 +173,7 @@ Some late-bound types couldn't be resolved:
|
|
|
173
173
|
end
|
|
174
174
|
when Class
|
|
175
175
|
if member.respond_to?(:graphql_definition)
|
|
176
|
-
graphql_member = member.graphql_definition
|
|
176
|
+
graphql_member = member.graphql_definition(silence_deprecation_warning: true)
|
|
177
177
|
visit(schema, graphql_member, context_description)
|
|
178
178
|
else
|
|
179
179
|
raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})")
|
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
|
11
11
|
def self.build_type(type_owner, ast_node)
|
|
12
12
|
case ast_node
|
|
13
13
|
when GraphQL::Language::Nodes::TypeName
|
|
14
|
-
type_owner.get_type(ast_node.name)
|
|
14
|
+
type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
|
|
15
15
|
when GraphQL::Language::Nodes::NonNullType
|
|
16
16
|
ast_inner_type = ast_node.of_type
|
|
17
17
|
inner_type = build_type(type_owner, ast_inner_type)
|
|
@@ -4,8 +4,6 @@ module GraphQL
|
|
|
4
4
|
class Schema
|
|
5
5
|
# This class joins an object type to an abstract type (interface or union) of which
|
|
6
6
|
# it is a member.
|
|
7
|
-
#
|
|
8
|
-
# TODO: Not yet implemented for interfaces.
|
|
9
7
|
class TypeMembership
|
|
10
8
|
# @return [Class<GraphQL::Schema::Object>]
|
|
11
9
|
attr_accessor :object_type
|
|
@@ -26,9 +24,25 @@ module GraphQL
|
|
|
26
24
|
end
|
|
27
25
|
|
|
28
26
|
# @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
|
|
29
|
-
def visible?(
|
|
30
|
-
|
|
27
|
+
def visible?(ctx)
|
|
28
|
+
warden = Warden.from_context(ctx)
|
|
29
|
+
(@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
|
|
30
|
+
(@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
def graphql_name
|
|
34
|
+
"#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def path
|
|
38
|
+
graphql_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inspect
|
|
42
|
+
"#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias :type_class :itself
|
|
32
46
|
end
|
|
33
47
|
end
|
|
34
48
|
end
|
data/lib/graphql/schema/union.rb
CHANGED
|
@@ -19,8 +19,9 @@ module GraphQL
|
|
|
19
19
|
end
|
|
20
20
|
else
|
|
21
21
|
visible_types = []
|
|
22
|
+
warden = Warden.from_context(context)
|
|
22
23
|
type_memberships.each do |type_membership|
|
|
23
|
-
if
|
|
24
|
+
if warden.visible_type_membership?(type_membership, context)
|
|
24
25
|
visible_types << type_membership.object_type
|
|
25
26
|
end
|
|
26
27
|
end
|
|
@@ -28,6 +29,12 @@ module GraphQL
|
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
def all_possible_types
|
|
33
|
+
type_memberships.map(&:object_type)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
prepend GraphQL::Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
|
37
|
+
|
|
31
38
|
def to_graphql
|
|
32
39
|
type_defn = GraphQL::UnionType.new
|
|
33
40
|
type_defn.name = graphql_name
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
class Schema
|
|
5
|
+
class Validator
|
|
6
|
+
# Use this to specifically reject values that respond to `.blank?` and respond truthy for that method.
|
|
7
|
+
#
|
|
8
|
+
# @example Require a non-empty string for an argument
|
|
9
|
+
# argument :name, String, required: true, validate: { allow_blank: false }
|
|
10
|
+
class AllowBlankValidator < Validator
|
|
11
|
+
def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
|
|
12
|
+
@message = message
|
|
13
|
+
super(**default_options)
|
|
14
|
+
@allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate(_object, _context, value)
|
|
18
|
+
if value.respond_to?(:blank?) && value.blank?
|
|
19
|
+
if (value.nil? && @allow_null) || @allow_blank
|
|
20
|
+
# pass
|
|
21
|
+
else
|
|
22
|
+
@message
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
class Schema
|
|
5
|
+
class Validator
|
|
6
|
+
# Use this to specifically reject or permit `nil` values (given as `null` from GraphQL).
|
|
7
|
+
#
|
|
8
|
+
# @example require a non-null value for an argument if it is provided
|
|
9
|
+
# argument :name, String, required: false, validates: { allow_null: false }
|
|
10
|
+
class AllowNullValidator < Validator
|
|
11
|
+
MESSAGE = "%{validated} can't be null"
|
|
12
|
+
def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
|
|
13
|
+
@message = message
|
|
14
|
+
super(**default_options)
|
|
15
|
+
@allow_null = allow_null.nil? ? allow_null_positional : allow_null
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def validate(_object, _context, value)
|
|
19
|
+
if value.nil? && !@allow_null
|
|
20
|
+
@message
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -18,10 +18,6 @@ module GraphQL
|
|
|
18
18
|
# # It's pretty hard to come up with a legitimate use case for `without:`
|
|
19
19
|
#
|
|
20
20
|
class FormatValidator < Validator
|
|
21
|
-
if !String.method_defined?(:match?)
|
|
22
|
-
using GraphQL::StringMatchBackport
|
|
23
|
-
end
|
|
24
|
-
|
|
25
21
|
# @param with [RegExp, nil]
|
|
26
22
|
# @param without [Regexp, nil]
|
|
27
23
|
# @param message [String]
|
|
@@ -38,7 +34,10 @@ module GraphQL
|
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
def validate(_object, _context, value)
|
|
41
|
-
if (
|
|
37
|
+
if permitted_empty_value?(value)
|
|
38
|
+
# Do nothing
|
|
39
|
+
elsif value.nil? ||
|
|
40
|
+
(@with_pattern && !value.match?(@with_pattern)) ||
|
|
42
41
|
(@without_pattern && value.match?(@without_pattern))
|
|
43
42
|
@message
|
|
44
43
|
end
|
|
@@ -43,11 +43,13 @@ module GraphQL
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def validate(_object, _context, value)
|
|
46
|
-
if
|
|
46
|
+
return if permitted_empty_value?(value) # pass in this case
|
|
47
|
+
length = value.nil? ? 0 : value.length
|
|
48
|
+
if @maximum && length > @maximum
|
|
47
49
|
partial_format(@too_long, { count: @maximum })
|
|
48
|
-
elsif @minimum &&
|
|
50
|
+
elsif @minimum && length < @minimum
|
|
49
51
|
partial_format(@too_short, { count: @minimum })
|
|
50
|
-
elsif @is &&
|
|
52
|
+
elsif @is && length != @is
|
|
51
53
|
partial_format(@wrong_length, { count: @is })
|
|
52
54
|
end
|
|
53
55
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module GraphQL
|
|
2
3
|
class Schema
|
|
3
4
|
class Validator
|
|
@@ -24,13 +25,15 @@ module GraphQL
|
|
|
24
25
|
# @param other_than [Integer]
|
|
25
26
|
# @param odd [Boolean]
|
|
26
27
|
# @param even [Boolean]
|
|
28
|
+
# @param within [Range]
|
|
27
29
|
# @param message [String] used for all validation failures
|
|
28
30
|
def initialize(
|
|
29
31
|
greater_than: nil, greater_than_or_equal_to: nil,
|
|
30
32
|
less_than: nil, less_than_or_equal_to: nil,
|
|
31
33
|
equal_to: nil, other_than: nil,
|
|
32
|
-
odd: nil, even: nil,
|
|
34
|
+
odd: nil, even: nil, within: nil,
|
|
33
35
|
message: "%{validated} must be %{comparison} %{target}",
|
|
36
|
+
null_message: Validator::AllowNullValidator::MESSAGE,
|
|
34
37
|
**default_options
|
|
35
38
|
)
|
|
36
39
|
|
|
@@ -42,12 +45,18 @@ module GraphQL
|
|
|
42
45
|
@other_than = other_than
|
|
43
46
|
@odd = odd
|
|
44
47
|
@even = even
|
|
48
|
+
@within = within
|
|
45
49
|
@message = message
|
|
50
|
+
@null_message = null_message
|
|
46
51
|
super(**default_options)
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
def validate(object, context, value)
|
|
50
|
-
if
|
|
55
|
+
if permitted_empty_value?(value)
|
|
56
|
+
# pass in this case
|
|
57
|
+
elsif value.nil? # @allow_null is handled in the parent class
|
|
58
|
+
@null_message
|
|
59
|
+
elsif @greater_than && value <= @greater_than
|
|
51
60
|
partial_format(@message, { comparison: "greater than", target: @greater_than })
|
|
52
61
|
elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to
|
|
53
62
|
partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to })
|
|
@@ -63,6 +72,8 @@ module GraphQL
|
|
|
63
72
|
(partial_format(@message, { comparison: "even", target: "" })).strip
|
|
64
73
|
elsif @odd && !value.odd?
|
|
65
74
|
(partial_format(@message, { comparison: "odd", target: "" })).strip
|
|
75
|
+
elsif @within && !@within.include?(value)
|
|
76
|
+
partial_format(@message, { comparison: "within", target: @within })
|
|
66
77
|
end
|
|
67
78
|
end
|
|
68
79
|
end
|
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
|
14
14
|
# argument :ingredient_id, ID, required: true
|
|
15
15
|
# argument :cups, Integer, required: false
|
|
16
16
|
# argument :tablespoons, Integer, required: false
|
|
17
|
-
# argument :teaspoons, Integer, required:
|
|
17
|
+
# argument :teaspoons, Integer, required: false
|
|
18
18
|
# validates required: { one_of: [:cups, :tablespoons, :teaspoons] }
|
|
19
19
|
# end
|
|
20
20
|
#
|
|
@@ -28,11 +28,23 @@ module GraphQL
|
|
|
28
28
|
# validates required: { one_of: [:node_id, [:object_type, :object_id]] }
|
|
29
29
|
# end
|
|
30
30
|
#
|
|
31
|
+
# @example require _some_ value for an argument, even if it's null
|
|
32
|
+
# field :update_settings, AccountSettings do
|
|
33
|
+
# # `required: :nullable` means this argument must be given, but may be `null`
|
|
34
|
+
# argument :age, Integer, required: :nullable
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
31
37
|
class RequiredValidator < Validator
|
|
32
38
|
# @param one_of [Symbol, Array<Symbol>] An argument, or a list of arguments, that represents a valid set of inputs for this field
|
|
33
39
|
# @param message [String]
|
|
34
|
-
def initialize(one_of
|
|
35
|
-
@one_of = one_of
|
|
40
|
+
def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options)
|
|
41
|
+
@one_of = if one_of
|
|
42
|
+
one_of
|
|
43
|
+
elsif argument
|
|
44
|
+
[argument]
|
|
45
|
+
else
|
|
46
|
+
raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
|
|
47
|
+
end
|
|
36
48
|
@message = message
|
|
37
49
|
super(**default_options)
|
|
38
50
|
end
|
|
@@ -40,19 +52,21 @@ module GraphQL
|
|
|
40
52
|
def validate(_object, _context, value)
|
|
41
53
|
matched_conditions = 0
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
if !value.nil?
|
|
56
|
+
@one_of.each do |one_of_condition|
|
|
57
|
+
case one_of_condition
|
|
58
|
+
when Symbol
|
|
59
|
+
if value.key?(one_of_condition)
|
|
60
|
+
matched_conditions += 1
|
|
61
|
+
end
|
|
62
|
+
when Array
|
|
63
|
+
if one_of_condition.all? { |k| value.key?(k) }
|
|
64
|
+
matched_conditions += 1
|
|
65
|
+
break
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
|
|
53
69
|
end
|
|
54
|
-
else
|
|
55
|
-
raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
|
|
56
70
|
end
|
|
57
71
|
end
|
|
58
72
|
|