graphql 2.3.14 → 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/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.rb +1 -1
- data/lib/graphql/dataloader/async_dataloader.rb +3 -2
- data/lib/graphql/dataloader/source.rb +1 -1
- data/lib/graphql/dataloader.rb +31 -10
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- 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 +38 -4
- data/lib/graphql/language/lexer.rb +15 -12
- data/lib/graphql/language/nodes.rb +22 -14
- data/lib/graphql/language/parser.rb +5 -0
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language.rb +6 -5
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query.rb +49 -16
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +23 -8
- data/lib/graphql/schema/always_visible.rb +6 -3
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +1 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field.rb +35 -14
- data/lib/graphql/schema/input_object.rb +20 -7
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/resolver.rb +3 -4
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- 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 +77 -15
- data/lib/graphql/schema.rb +203 -61
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
- 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 +2 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
- data/lib/graphql/static_validation/validation_context.rb +15 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions.rb +3 -1
- data/lib/graphql/testing/helpers.rb +2 -1
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- metadata +11 -9
- data/lib/graphql/schema/subset.rb +0 -509
- data/lib/graphql/schema/types_migration.rb +0 -187
data/lib/graphql/schema.rb
CHANGED
@@ -45,8 +45,7 @@ require "graphql/schema/mutation"
|
|
45
45
|
require "graphql/schema/has_single_input_argument"
|
46
46
|
require "graphql/schema/relay_classic_mutation"
|
47
47
|
require "graphql/schema/subscription"
|
48
|
-
require "graphql/schema/
|
49
|
-
require "graphql/schema/types_migration"
|
48
|
+
require "graphql/schema/visibility"
|
50
49
|
|
51
50
|
module GraphQL
|
52
51
|
# A GraphQL schema which may be queried with {GraphQL::Query}.
|
@@ -163,6 +162,7 @@ module GraphQL
|
|
163
162
|
# re-apply them here
|
164
163
|
mods = trace_modules_for(:default)
|
165
164
|
mods.each { |mod| new_class.include(mod) }
|
165
|
+
new_class.include(DefaultTraceClass)
|
166
166
|
trace_mode(:default, new_class)
|
167
167
|
backtrace_class = Class.new(new_class)
|
168
168
|
backtrace_class.include(GraphQL::Backtrace::Trace)
|
@@ -205,24 +205,19 @@ module GraphQL
|
|
205
205
|
@own_trace_modes ||= {}
|
206
206
|
end
|
207
207
|
|
208
|
-
module DefaultTraceClass
|
209
|
-
end
|
210
|
-
|
211
|
-
private_constant :DefaultTraceClass
|
212
|
-
|
213
208
|
def build_trace_mode(mode)
|
214
209
|
case mode
|
215
210
|
when :default
|
216
211
|
# Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
|
217
|
-
base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
|
218
|
-
Class.new(base_class) do
|
212
|
+
base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: true)) || GraphQL::Tracing::Trace
|
213
|
+
const_set(:DefaultTrace, Class.new(base_class) do
|
219
214
|
include DefaultTraceClass
|
220
|
-
end
|
215
|
+
end)
|
221
216
|
when :default_backtrace
|
222
217
|
schema_base_class = trace_class_for(:default, build: true)
|
223
|
-
Class.new(schema_base_class) do
|
218
|
+
const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
|
224
219
|
include(GraphQL::Backtrace::Trace)
|
225
|
-
end
|
220
|
+
end)
|
226
221
|
else
|
227
222
|
# First, see if the superclass has a custom-defined class for this.
|
228
223
|
# Then, if it doesn't, use this class's default trace
|
@@ -322,6 +317,9 @@ module GraphQL
|
|
322
317
|
GraphQL::StaticValidation::Validator.new(schema: self)
|
323
318
|
end
|
324
319
|
|
320
|
+
# Add `plugin` to this schema
|
321
|
+
# @param plugin [#use] A Schema plugin
|
322
|
+
# @return void
|
325
323
|
def use(plugin, **kwargs)
|
326
324
|
if kwargs.any?
|
327
325
|
plugin.use(self, **kwargs)
|
@@ -339,8 +337,9 @@ module GraphQL
|
|
339
337
|
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
|
340
338
|
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
|
341
339
|
def types(context = GraphQL::Query::NullContext.instance)
|
342
|
-
if
|
343
|
-
|
340
|
+
if use_visibility_profile?
|
341
|
+
types = Visibility::Profile.from_context(context, self)
|
342
|
+
return types.all_types_h
|
344
343
|
end
|
345
344
|
all_types = non_introspection_types.merge(introspection_system.types)
|
346
345
|
visible_types = {}
|
@@ -367,17 +366,19 @@ module GraphQL
|
|
367
366
|
end
|
368
367
|
|
369
368
|
# @param type_name [String]
|
369
|
+
# @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
|
370
|
+
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
370
371
|
# @return [Module, nil] A type, or nil if there's no type called `type_name`
|
371
|
-
def get_type(type_name, context = GraphQL::Query::NullContext.instance)
|
372
|
-
if
|
373
|
-
return
|
372
|
+
def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
|
373
|
+
if use_visibility_profile
|
374
|
+
return Visibility::Profile.from_context(context, self).type(type_name)
|
374
375
|
end
|
375
376
|
local_entry = own_types[type_name]
|
376
377
|
type_defn = case local_entry
|
377
378
|
when nil
|
378
379
|
nil
|
379
380
|
when Array
|
380
|
-
if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::
|
381
|
+
if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)
|
381
382
|
local_entry
|
382
383
|
else
|
383
384
|
visible_t = nil
|
@@ -403,7 +404,7 @@ module GraphQL
|
|
403
404
|
|
404
405
|
type_defn ||
|
405
406
|
introspection_system.types[type_name] || # todo context-specific introspection?
|
406
|
-
(superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
|
407
|
+
(superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil)
|
407
408
|
end
|
408
409
|
|
409
410
|
# @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility?
|
@@ -430,12 +431,20 @@ module GraphQL
|
|
430
431
|
end
|
431
432
|
end
|
432
433
|
|
434
|
+
# Get or set the root `query { ... }` object for this schema.
|
435
|
+
#
|
436
|
+
# @example Using `Types::Query` as the entry-point
|
437
|
+
# query { Types::Query }
|
438
|
+
#
|
439
|
+
# @param new_query_object [Class<GraphQL::Schema::Object>] The root type to use for queries
|
440
|
+
# @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type.
|
441
|
+
# @return [Class<GraphQL::Schema::Object>, nil] The configured query root type, if there is one.
|
433
442
|
def query(new_query_object = nil, &lazy_load_block)
|
434
443
|
if new_query_object || block_given?
|
435
444
|
if @query_object
|
436
445
|
dup_defn = new_query_object || yield
|
437
446
|
raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
|
438
|
-
elsif
|
447
|
+
elsif use_visibility_profile?
|
439
448
|
@query_object = block_given? ? lazy_load_block : new_query_object
|
440
449
|
else
|
441
450
|
@query_object = new_query_object || lazy_load_block.call
|
@@ -449,12 +458,20 @@ module GraphQL
|
|
449
458
|
end
|
450
459
|
end
|
451
460
|
|
461
|
+
# Get or set the root `mutation { ... }` object for this schema.
|
462
|
+
#
|
463
|
+
# @example Using `Types::Mutation` as the entry-point
|
464
|
+
# mutation { Types::Mutation }
|
465
|
+
#
|
466
|
+
# @param new_mutation_object [Class<GraphQL::Schema::Object>] The root type to use for mutations
|
467
|
+
# @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type.
|
468
|
+
# @return [Class<GraphQL::Schema::Object>, nil] The configured mutation root type, if there is one.
|
452
469
|
def mutation(new_mutation_object = nil, &lazy_load_block)
|
453
470
|
if new_mutation_object || block_given?
|
454
471
|
if @mutation_object
|
455
472
|
dup_defn = new_mutation_object || yield
|
456
473
|
raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
|
457
|
-
elsif
|
474
|
+
elsif use_visibility_profile?
|
458
475
|
@mutation_object = block_given? ? lazy_load_block : new_mutation_object
|
459
476
|
else
|
460
477
|
@mutation_object = new_mutation_object || lazy_load_block.call
|
@@ -468,12 +485,20 @@ module GraphQL
|
|
468
485
|
end
|
469
486
|
end
|
470
487
|
|
488
|
+
# Get or set the root `subscription { ... }` object for this schema.
|
489
|
+
#
|
490
|
+
# @example Using `Types::Subscription` as the entry-point
|
491
|
+
# subscription { Types::Subscription }
|
492
|
+
#
|
493
|
+
# @param new_subscription_object [Class<GraphQL::Schema::Object>] The root type to use for subscriptions
|
494
|
+
# @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type.
|
495
|
+
# @return [Class<GraphQL::Schema::Object>, nil] The configured subscription root type, if there is one.
|
471
496
|
def subscription(new_subscription_object = nil, &lazy_load_block)
|
472
497
|
if new_subscription_object || block_given?
|
473
498
|
if @subscription_object
|
474
499
|
dup_defn = new_subscription_object || yield
|
475
500
|
raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
|
476
|
-
elsif
|
501
|
+
elsif use_visibility_profile?
|
477
502
|
@subscription_object = block_given? ? lazy_load_block : new_subscription_object
|
478
503
|
add_subscription_extension_if_necessary
|
479
504
|
else
|
@@ -491,8 +516,7 @@ module GraphQL
|
|
491
516
|
end
|
492
517
|
end
|
493
518
|
|
494
|
-
# @
|
495
|
-
# @return [GraphQL::ObjectType, nil]
|
519
|
+
# @api private
|
496
520
|
def root_type_for_operation(operation)
|
497
521
|
case operation
|
498
522
|
when "query"
|
@@ -506,14 +530,16 @@ module GraphQL
|
|
506
530
|
end
|
507
531
|
end
|
508
532
|
|
533
|
+
# @return [Array<Class>] The root types (query, mutation, subscription) defined for this schema
|
509
534
|
def root_types
|
510
|
-
if
|
535
|
+
if use_visibility_profile?
|
511
536
|
[query, mutation, subscription].compact
|
512
537
|
else
|
513
538
|
@root_types
|
514
539
|
end
|
515
540
|
end
|
516
541
|
|
542
|
+
# @api private
|
517
543
|
def warden_class
|
518
544
|
if defined?(@warden_class)
|
519
545
|
@warden_class
|
@@ -524,39 +550,46 @@ module GraphQL
|
|
524
550
|
end
|
525
551
|
end
|
526
552
|
|
553
|
+
# @api private
|
527
554
|
attr_writer :warden_class
|
528
555
|
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
556
|
+
# @api private
|
557
|
+
def visibility_profile_class
|
558
|
+
if defined?(@visibility_profile_class)
|
559
|
+
@visibility_profile_class
|
560
|
+
elsif superclass.respond_to?(:visibility_profile_class)
|
561
|
+
superclass.visibility_profile_class
|
534
562
|
else
|
535
|
-
GraphQL::Schema::
|
563
|
+
GraphQL::Schema::Visibility::Profile
|
536
564
|
end
|
537
565
|
end
|
538
566
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
567
|
+
# @api private
|
568
|
+
attr_writer :visibility_profile_class, :use_visibility_profile
|
569
|
+
# @api private
|
570
|
+
attr_accessor :visibility
|
571
|
+
# @api private
|
572
|
+
def use_visibility_profile?
|
573
|
+
if defined?(@use_visibility_profile)
|
574
|
+
@use_visibility_profile
|
575
|
+
elsif superclass.respond_to?(:use_visibility_profile?)
|
576
|
+
superclass.use_visibility_profile?
|
546
577
|
else
|
547
578
|
false
|
548
579
|
end
|
549
580
|
end
|
550
581
|
|
551
582
|
# @param type [Module] The type definition whose possible types you want to see
|
583
|
+
# @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime
|
584
|
+
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
552
585
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
553
586
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
554
|
-
def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
|
555
|
-
if
|
587
|
+
def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
|
588
|
+
if use_visibility_profile
|
556
589
|
if type
|
557
|
-
return
|
590
|
+
return Visibility::Profile.from_context(context, self).possible_types(type)
|
558
591
|
else
|
559
|
-
raise "Schema.possible_types is not implemented for `
|
592
|
+
raise "Schema.possible_types is not implemented for `use_visibility_profile?`"
|
560
593
|
end
|
561
594
|
end
|
562
595
|
if type
|
@@ -576,7 +609,7 @@ module GraphQL
|
|
576
609
|
introspection_system.possible_types[type] ||
|
577
610
|
(
|
578
611
|
superclass.respond_to?(:possible_types) ?
|
579
|
-
superclass.possible_types(type, context) :
|
612
|
+
superclass.possible_types(type, context, use_visibility_profile) :
|
580
613
|
EMPTY_ARRAY
|
581
614
|
)
|
582
615
|
end
|
@@ -779,6 +812,7 @@ module GraphQL
|
|
779
812
|
res[:errors]
|
780
813
|
end
|
781
814
|
|
815
|
+
# @param new_query_class [Class<GraphQL::Query>] A subclass to use when executing queries
|
782
816
|
def query_class(new_query_class = NOT_CONFIGURED)
|
783
817
|
if NOT_CONFIGURED.equal?(new_query_class)
|
784
818
|
@query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query)
|
@@ -789,13 +823,11 @@ module GraphQL
|
|
789
823
|
|
790
824
|
attr_writer :validate_max_errors
|
791
825
|
|
792
|
-
def validate_max_errors(new_validate_max_errors =
|
793
|
-
if new_validate_max_errors
|
794
|
-
@validate_max_errors
|
795
|
-
elsif defined?(@validate_max_errors)
|
796
|
-
@validate_max_errors
|
826
|
+
def validate_max_errors(new_validate_max_errors = NOT_CONFIGURED)
|
827
|
+
if NOT_CONFIGURED.equal?(new_validate_max_errors)
|
828
|
+
defined?(@validate_max_errors) ? @validate_max_errors : find_inherited_value(:validate_max_errors)
|
797
829
|
else
|
798
|
-
|
830
|
+
@validate_max_errors = new_validate_max_errors
|
799
831
|
end
|
800
832
|
end
|
801
833
|
|
@@ -934,7 +966,7 @@ module GraphQL
|
|
934
966
|
To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
|
935
967
|
ERR
|
936
968
|
end
|
937
|
-
add_type_and_traverse(new_orphan_types, root: false) unless
|
969
|
+
add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile?
|
938
970
|
own_orphan_types.concat(new_orphan_types.flatten)
|
939
971
|
end
|
940
972
|
|
@@ -966,6 +998,8 @@ module GraphQL
|
|
966
998
|
end
|
967
999
|
end
|
968
1000
|
|
1001
|
+
|
1002
|
+
# @param new_default_logger [#log] Something to use for logging messages
|
969
1003
|
def default_logger(new_default_logger = NOT_CONFIGURED)
|
970
1004
|
if NOT_CONFIGURED.equal?(new_default_logger)
|
971
1005
|
if defined?(@default_logger)
|
@@ -986,6 +1020,7 @@ module GraphQL
|
|
986
1020
|
end
|
987
1021
|
end
|
988
1022
|
|
1023
|
+
# @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
|
989
1024
|
def context_class(new_context_class = nil)
|
990
1025
|
if new_context_class
|
991
1026
|
@context_class = new_context_class
|
@@ -994,6 +1029,20 @@ module GraphQL
|
|
994
1029
|
end
|
995
1030
|
end
|
996
1031
|
|
1032
|
+
# Register a handler for errors raised during execution. The handlers can return a new value or raise a new error.
|
1033
|
+
#
|
1034
|
+
# @example Handling "not found" with a client-facing error
|
1035
|
+
# rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" }
|
1036
|
+
#
|
1037
|
+
# @param err_classes [Array<StandardError>] Classes which should be rescued by `handler_block`
|
1038
|
+
# @param handler_block The code to run when one of those errors is raised during execution
|
1039
|
+
# @yieldparam error [StandardError] An instance of one of the configured `err_classes`
|
1040
|
+
# @yieldparam object [Object] The current application object in the query when the error was raised
|
1041
|
+
# @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised
|
1042
|
+
# @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation
|
1043
|
+
# @yieldreturn [Object] Some object to use in the place where this error was raised
|
1044
|
+
# @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response
|
1045
|
+
# @raise [StandardError] In the handler, raise to crash the query with a developer-facing error
|
997
1046
|
def rescue_from(*err_classes, &handler_block)
|
998
1047
|
err_classes.each do |err_class|
|
999
1048
|
Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block)
|
@@ -1060,8 +1109,24 @@ module GraphQL
|
|
1060
1109
|
end
|
1061
1110
|
end
|
1062
1111
|
|
1063
|
-
|
1064
|
-
|
1112
|
+
# GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
|
1113
|
+
#
|
1114
|
+
# Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
|
1115
|
+
# But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument.loads} cases an object to be directly loaded from the database.
|
1116
|
+
#
|
1117
|
+
# @example Returning a GraphQL type based on the object's class name
|
1118
|
+
# class MySchema < GraphQL::Schema
|
1119
|
+
# def resolve_type(_abs_type, object, _context)
|
1120
|
+
# graphql_type_name = "Types::#{object.class.name}Type"
|
1121
|
+
# graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method
|
1122
|
+
# end
|
1123
|
+
# end
|
1124
|
+
# @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one
|
1125
|
+
# @param application_object [Object] The object returned from a field whose type must be determined
|
1126
|
+
# @param context [GraphQL::Query::Context] The query context for the currently-executing query
|
1127
|
+
# @return [Class<GraphQL::Schema::Object] The Object type definition to use for `obj`
|
1128
|
+
def resolve_type(abstract_type, application_object, context)
|
1129
|
+
raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(abstract_type, application_object, context) must be implemented to use Union types, Interface types, or `loads:` (tried to resolve: #{abstract_type.name})"
|
1065
1130
|
end
|
1066
1131
|
# rubocop:enable Lint/DuplicateMethods
|
1067
1132
|
|
@@ -1076,15 +1141,45 @@ module GraphQL
|
|
1076
1141
|
child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
|
1077
1142
|
end
|
1078
1143
|
child_class.singleton_class.prepend(ResolveTypeWithType)
|
1079
|
-
super
|
1080
|
-
end
|
1081
1144
|
|
1082
|
-
|
1083
|
-
|
1145
|
+
if use_visibility_profile?
|
1146
|
+
vis = self.visibility
|
1147
|
+
child_class.visibility = vis.dup_for(child_class)
|
1148
|
+
end
|
1149
|
+
super
|
1084
1150
|
end
|
1085
1151
|
|
1086
|
-
|
1087
|
-
|
1152
|
+
# Fetch an object based on an incoming ID and the current context. This method should return an object
|
1153
|
+
# from your application, or return `nil` if there is no object or the object shouldn't be available to this operation.
|
1154
|
+
#
|
1155
|
+
# @example Fetching an object with Rails's GlobalID
|
1156
|
+
# def self.object_from_id(object_id, _context)
|
1157
|
+
# GlobalID.find(global_id)
|
1158
|
+
# # TODO: use `context[:current_user]` to determine if this object is authorized.
|
1159
|
+
# end
|
1160
|
+
# @param object_id [String] The ID to fetch an object for. This may be client-provided (as in `node(id: ...)` or `loads:`) or previously stored by the schema (eg, by the `ObjectCache`)
|
1161
|
+
# @param context [GraphQL::Query::Context] The context for the currently-executing operation
|
1162
|
+
# @return [Object, nil] The application which `object_id` references, or `nil` if there is no object or the current operation shouldn't have access to the object
|
1163
|
+
# @see id_from_object which produces these IDs
|
1164
|
+
def object_from_id(object_id, context)
|
1165
|
+
raise GraphQL::RequiredImplementationMissingError, "#{self.name}.object_from_id(object_id, context) must be implemented to load by ID (tried to load from id `#{object_id}`)"
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
# Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
|
1169
|
+
#
|
1170
|
+
# {GlobalID}(https://github.com/rails/globalid) and {SQIDs}(https://sqids.org/ruby) can both be used to create IDs.
|
1171
|
+
#
|
1172
|
+
# @example Using Rails's GlobalID to generate IDs
|
1173
|
+
# def self.id_from_object(application_object, graphql_type, context)
|
1174
|
+
# application_object.to_gid_param
|
1175
|
+
# end
|
1176
|
+
#
|
1177
|
+
# @param application_object [Object] Some object encountered by GraphQL-Ruby while running a query
|
1178
|
+
# @param graphql_type [Class, Module] The type that GraphQL-Ruby is using for `application_object` during this query
|
1179
|
+
# @param context [GraphQL::Query::Context] The context for the operation that is currently running
|
1180
|
+
# @return [String] A stable identifier which can be passed to {.object_from_id} later to re-fetch `application_object`
|
1181
|
+
def id_from_object(application_object, graphql_type, context)
|
1182
|
+
raise GraphQL::RequiredImplementationMissingError, "#{self.name}.id_from_object(application_object, graphql_type, context) must be implemented to create global ids (tried to create an id for `#{application_object.inspect}`)"
|
1088
1183
|
end
|
1089
1184
|
|
1090
1185
|
def visible?(member, ctx)
|
@@ -1139,6 +1234,16 @@ module GraphQL
|
|
1139
1234
|
unauthorized_object(unauthorized_error)
|
1140
1235
|
end
|
1141
1236
|
|
1237
|
+
# Called at runtime when GraphQL-Ruby encounters a mismatch between the application behavior
|
1238
|
+
# and the GraphQL type system.
|
1239
|
+
#
|
1240
|
+
# The default implementation of this method is to follow the GraphQL specification,
|
1241
|
+
# but you can override this to report errors to your bug tracker or customize error handling.
|
1242
|
+
# @param type_error [GraphQL::Error] several specific error classes are passed here, see the default implementation for details
|
1243
|
+
# @param context [GraphQL::Query::Context] the context for the currently-running operation
|
1244
|
+
# @return [void]
|
1245
|
+
# @raise [GraphQL::ExecutionError] to return this error to the client
|
1246
|
+
# @raise [GraphQL::Error] to crash the query and raise a developer-facing error
|
1142
1247
|
def type_error(type_error, ctx)
|
1143
1248
|
case type_error
|
1144
1249
|
when GraphQL::InvalidNullError
|
@@ -1193,7 +1298,7 @@ module GraphQL
|
|
1193
1298
|
# @param new_directive [Class]
|
1194
1299
|
# @return void
|
1195
1300
|
def directive(new_directive)
|
1196
|
-
if
|
1301
|
+
if use_visibility_profile?
|
1197
1302
|
own_directives[new_directive.graphql_name] = new_directive
|
1198
1303
|
else
|
1199
1304
|
add_type_and_traverse(new_directive, root: false)
|
@@ -1234,6 +1339,7 @@ module GraphQL
|
|
1234
1339
|
# @param mode [Symbol] Trace module will only be used for this trade mode
|
1235
1340
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
1236
1341
|
# @return [void]
|
1342
|
+
# @see GraphQL::Tracing::Trace for available tracing methods
|
1237
1343
|
def trace_with(trace_mod, mode: :default, **options)
|
1238
1344
|
if mode.is_a?(Array)
|
1239
1345
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
@@ -1311,6 +1417,8 @@ module GraphQL
|
|
1311
1417
|
trace_class_for_mode.new(**trace_options)
|
1312
1418
|
end
|
1313
1419
|
|
1420
|
+
# @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on queries to this schema
|
1421
|
+
# @see GraphQL::Analysis the analysis system
|
1314
1422
|
def query_analyzer(new_analyzer)
|
1315
1423
|
own_query_analyzers << new_analyzer
|
1316
1424
|
end
|
@@ -1319,6 +1427,8 @@ module GraphQL
|
|
1319
1427
|
find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
|
1320
1428
|
end
|
1321
1429
|
|
1430
|
+
# @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
|
1431
|
+
# @see GraphQL::Analysis the analysis system
|
1322
1432
|
def multiplex_analyzer(new_analyzer)
|
1323
1433
|
own_multiplex_analyzers << new_analyzer
|
1324
1434
|
end
|
@@ -1402,6 +1512,11 @@ module GraphQL
|
|
1402
1512
|
end
|
1403
1513
|
end
|
1404
1514
|
|
1515
|
+
# Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response.
|
1516
|
+
# You could modify this method to report this error to your bug tracker.
|
1517
|
+
# @param query [GraphQL::Query]
|
1518
|
+
# @param err [SystemStackError]
|
1519
|
+
# @return [void]
|
1405
1520
|
def query_stack_error(query, err)
|
1406
1521
|
query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute."))
|
1407
1522
|
end
|
@@ -1460,11 +1575,34 @@ module GraphQL
|
|
1460
1575
|
end
|
1461
1576
|
end
|
1462
1577
|
|
1578
|
+
# Returns `DidYouMean` if it's defined.
|
1579
|
+
# Override this to return `nil` if you don't want to use `DidYouMean`
|
1580
|
+
def did_you_mean(new_dym = NOT_CONFIGURED)
|
1581
|
+
if NOT_CONFIGURED.equal?(new_dym)
|
1582
|
+
if defined?(@did_you_mean)
|
1583
|
+
@did_you_mean
|
1584
|
+
else
|
1585
|
+
find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil)
|
1586
|
+
end
|
1587
|
+
else
|
1588
|
+
@did_you_mean = new_dym
|
1589
|
+
end
|
1590
|
+
end
|
1591
|
+
|
1463
1592
|
private
|
1464
1593
|
|
1465
1594
|
def add_trace_options_for(mode, new_options)
|
1466
|
-
|
1467
|
-
|
1595
|
+
if mode == :default
|
1596
|
+
own_trace_modes.each do |mode_name, t_class|
|
1597
|
+
if t_class <= DefaultTraceClass
|
1598
|
+
t_opts = trace_options_for(mode_name)
|
1599
|
+
t_opts.merge!(new_options)
|
1600
|
+
end
|
1601
|
+
end
|
1602
|
+
else
|
1603
|
+
t_opts = trace_options_for(mode)
|
1604
|
+
t_opts.merge!(new_options)
|
1605
|
+
end
|
1468
1606
|
nil
|
1469
1607
|
end
|
1470
1608
|
|
@@ -1608,5 +1746,9 @@ module GraphQL
|
|
1608
1746
|
|
1609
1747
|
# Install these here so that subclasses will also install it.
|
1610
1748
|
self.connections = GraphQL::Pagination::Connections.new(schema: self)
|
1749
|
+
|
1750
|
+
# @api private
|
1751
|
+
module DefaultTraceClass
|
1752
|
+
end
|
1611
1753
|
end
|
1612
1754
|
end
|
@@ -10,8 +10,9 @@ module GraphQL
|
|
10
10
|
elsif parent_defn
|
11
11
|
kind_of_node = node_type(parent)
|
12
12
|
error_arg_name = parent_name(parent, parent_defn)
|
13
|
+
arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
|
13
14
|
add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new(
|
14
|
-
"#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'",
|
15
|
+
"#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{context.did_you_mean_suggestion(node.name, arg_names)}",
|
15
16
|
nodes: node,
|
16
17
|
name: error_arg_name,
|
17
18
|
type: kind_of_node,
|
@@ -10,8 +10,9 @@ module GraphQL
|
|
10
10
|
if !@types.directive_exists?(node.name)
|
11
11
|
@directives_are_defined_errors_by_name ||= {}
|
12
12
|
error = @directives_are_defined_errors_by_name[node.name] ||= begin
|
13
|
+
@directive_names ||= @types.directives.map(&:graphql_name)
|
13
14
|
err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
|
14
|
-
"Directive @#{node.name} is not defined",
|
15
|
+
"Directive @#{node.name} is not defined#{context.did_you_mean_suggestion(node.name, @directive_names)}",
|
15
16
|
nodes: [],
|
16
17
|
directive: node.name
|
17
18
|
)
|
@@ -19,6 +19,7 @@ module GraphQL
|
|
19
19
|
GraphQL::Schema::Directive::FRAGMENT_DEFINITION => "fragment definitions",
|
20
20
|
GraphQL::Schema::Directive::FRAGMENT_SPREAD => "fragment spreads",
|
21
21
|
GraphQL::Schema::Directive::INLINE_FRAGMENT => "inline fragments",
|
22
|
+
GraphQL::Schema::Directive::VARIABLE_DEFINITION => "variable definitions",
|
22
23
|
}
|
23
24
|
|
24
25
|
SIMPLE_LOCATIONS = {
|
@@ -26,6 +27,7 @@ module GraphQL
|
|
26
27
|
Nodes::InlineFragment => GraphQL::Schema::Directive::INLINE_FRAGMENT,
|
27
28
|
Nodes::FragmentSpread => GraphQL::Schema::Directive::FRAGMENT_SPREAD,
|
28
29
|
Nodes::FragmentDefinition => GraphQL::Schema::Directive::FRAGMENT_DEFINITION,
|
30
|
+
Nodes::VariableDefinition => GraphQL::Schema::Directive::VARIABLE_DEFINITION,
|
29
31
|
}
|
30
32
|
|
31
33
|
SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys
|
@@ -14,8 +14,9 @@ module GraphQL
|
|
14
14
|
node_name: parent_type.graphql_name
|
15
15
|
))
|
16
16
|
else
|
17
|
+
message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{context.did_you_mean_suggestion(node.name, context.types.fields(parent_type).map(&:graphql_name))}"
|
17
18
|
add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new(
|
18
|
-
|
19
|
+
message,
|
19
20
|
nodes: node,
|
20
21
|
field: node.name,
|
21
22
|
type: parent_type.graphql_name
|
@@ -212,6 +212,7 @@ module GraphQL
|
|
212
212
|
|
213
213
|
def find_conflict(response_key, field1, field2, mutually_exclusive: false)
|
214
214
|
return if @conflict_count >= context.max_errors
|
215
|
+
return if field1.definition.nil? || field2.definition.nil?
|
215
216
|
|
216
217
|
node1 = field1.node
|
217
218
|
node2 = field2.node
|
@@ -23,8 +23,18 @@ module GraphQL
|
|
23
23
|
type_name = fragment_node.type.name
|
24
24
|
type = @types.type(type_name)
|
25
25
|
if type.nil?
|
26
|
+
@all_possible_fragment_type_names ||= begin
|
27
|
+
names = []
|
28
|
+
context.types.all_types.each do |type|
|
29
|
+
if type.kind.fields?
|
30
|
+
names << type.graphql_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
names
|
34
|
+
end
|
35
|
+
|
26
36
|
add_error(GraphQL::StaticValidation::FragmentTypesExistError.new(
|
27
|
-
"No such type #{type_name}, so it can't be a fragment condition",
|
37
|
+
"No such type #{type_name}, so it can't be a fragment condition#{context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)}",
|
28
38
|
nodes: fragment_node,
|
29
39
|
type: type_name
|
30
40
|
))
|
@@ -7,8 +7,17 @@ module GraphQL
|
|
7
7
|
type = context.query.types.type(type_name)
|
8
8
|
|
9
9
|
if type.nil?
|
10
|
+
@all_possible_input_type_names ||= begin
|
11
|
+
names = []
|
12
|
+
context.types.all_types.each { |(t)|
|
13
|
+
if t.kind.input?
|
14
|
+
names << t.graphql_name
|
15
|
+
end
|
16
|
+
}
|
17
|
+
names
|
18
|
+
end
|
10
19
|
add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
|
11
|
-
"#{type_name} isn't a defined input type (on $#{node.name})",
|
20
|
+
"#{type_name} isn't a defined input type (on $#{node.name})#{context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)}",
|
12
21
|
nodes: node,
|
13
22
|
name: node.name,
|
14
23
|
type: type_name
|
@@ -48,6 +48,21 @@ module GraphQL
|
|
48
48
|
def schema_directives
|
49
49
|
@schema_directives ||= schema.directives
|
50
50
|
end
|
51
|
+
|
52
|
+
def did_you_mean_suggestion(name, options)
|
53
|
+
if did_you_mean = schema.did_you_mean
|
54
|
+
suggestions = did_you_mean::SpellChecker.new(dictionary: options).correct(name)
|
55
|
+
case suggestions.size
|
56
|
+
when 0
|
57
|
+
""
|
58
|
+
when 1
|
59
|
+
" (Did you mean `#{suggestions.first}`?)"
|
60
|
+
else
|
61
|
+
last_sugg = suggestions.pop
|
62
|
+
" (Did you mean #{suggestions.map {|s| "`#{s}`"}.join(", ")} or `#{last_sugg}`?)"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
51
66
|
end
|
52
67
|
end
|
53
68
|
end
|
@@ -166,7 +166,8 @@ module GraphQL
|
|
166
166
|
#
|
167
167
|
def setup_stream(channel, initial_event)
|
168
168
|
topic = initial_event.topic
|
169
|
-
|
169
|
+
event_stream = stream_event_name(initial_event)
|
170
|
+
channel.stream_from(event_stream, coder: @action_cable_coder) do |message|
|
170
171
|
events_by_fingerprint = @events[topic]
|
171
172
|
object = nil
|
172
173
|
events_by_fingerprint.each do |_fingerprint, events|
|