graphql 2.4.3 → 2.5.3
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/graphql/analysis/analyzer.rb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +87 -7
- data/lib/graphql/analysis/visitor.rb +38 -41
- data/lib/graphql/analysis.rb +15 -12
- data/lib/graphql/autoload.rb +38 -0
- data/lib/graphql/backtrace/table.rb +118 -55
- data/lib/graphql/backtrace.rb +1 -19
- data/lib/graphql/current.rb +7 -2
- data/lib/graphql/dashboard/detailed_traces.rb +47 -0
- data/lib/graphql/dashboard/installable.rb +22 -0
- data/lib/graphql/dashboard/limiters.rb +93 -0
- data/lib/graphql/dashboard/operation_store.rb +199 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
- data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
- data/lib/graphql/dashboard/statics/charts.min.css +1 -0
- data/lib/graphql/dashboard/statics/dashboard.css +30 -0
- data/lib/graphql/dashboard/statics/dashboard.js +143 -0
- data/lib/graphql/dashboard/statics/header-icon.png +0 -0
- data/lib/graphql/dashboard/statics/icon.png +0 -0
- data/lib/graphql/dashboard/subscriptions.rb +96 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
- data/lib/graphql/dashboard.rb +158 -0
- data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
- data/lib/graphql/dataloader/active_record_source.rb +26 -0
- data/lib/graphql/dataloader/async_dataloader.rb +21 -9
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/source.rb +3 -3
- data/lib/graphql/dataloader.rb +43 -14
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/execution/interpreter/resolve.rb +3 -3
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
- data/lib/graphql/execution/interpreter/runtime.rb +96 -52
- data/lib/graphql/execution/interpreter.rb +16 -7
- data/lib/graphql/execution/multiplex.rb +6 -5
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +19 -16
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/document_from_schema_definition.rb +8 -7
- data/lib/graphql/language/lexer.rb +11 -4
- data/lib/graphql/language/nodes.rb +3 -0
- data/lib/graphql/language/parser.rb +15 -8
- data/lib/graphql/language/printer.rb +8 -8
- data/lib/graphql/language/static_visitor.rb +37 -33
- data/lib/graphql/language/visitor.rb +59 -55
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +7 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +22 -32
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/schema/addition.rb +1 -1
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +7 -8
- data/lib/graphql/schema/build_from_definition.rb +99 -53
- data/lib/graphql/schema/directive/flagged.rb +3 -1
- data/lib/graphql/schema/directive.rb +2 -2
- data/lib/graphql/schema/enum.rb +36 -1
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +27 -13
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +3 -1
- data/lib/graphql/schema/input_object.rb +77 -40
- data/lib/graphql/schema/interface.rb +3 -2
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +25 -17
- data/lib/graphql/schema/member/has_dataloader.rb +62 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +4 -4
- data/lib/graphql/schema/member/has_fields.rb +19 -1
- data/lib/graphql/schema/member/has_interfaces.rb +5 -5
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/member/scoped.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/object.rb +25 -8
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +12 -10
- data/lib/graphql/schema/subscription.rb +52 -6
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/required_validator.rb +23 -6
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/visibility/migration.rb +1 -0
- data/lib/graphql/schema/visibility/profile.rb +98 -244
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +178 -38
- data/lib/graphql/schema/warden.rb +18 -5
- data/lib/graphql/schema.rb +266 -54
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
- data/lib/graphql/static_validation/validation_context.rb +1 -0
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
- data/lib/graphql/subscriptions/event.rb +12 -1
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/testing/helpers.rb +7 -4
- data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
- data/lib/graphql/tracing/appoptics_trace.rb +9 -1
- data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
- data/lib/graphql/tracing/appsignal_trace.rb +32 -55
- data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
- data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
- data/lib/graphql/tracing/data_dog_trace.rb +46 -158
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
- data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
- data/lib/graphql/tracing/detailed_trace.rb +93 -0
- data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
- data/lib/graphql/tracing/legacy_trace.rb +4 -61
- data/lib/graphql/tracing/monitor_trace.rb +283 -0
- data/lib/graphql/tracing/new_relic_trace.rb +47 -54
- data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
- data/lib/graphql/tracing/notifications_trace.rb +182 -34
- data/lib/graphql/tracing/notifications_tracing.rb +2 -0
- data/lib/graphql/tracing/null_trace.rb +9 -0
- data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
- data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
- data/lib/graphql/tracing/perfetto_trace.rb +734 -0
- data/lib/graphql/tracing/platform_trace.rb +5 -0
- data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
- data/lib/graphql/tracing/prometheus_trace.rb +72 -68
- data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
- data/lib/graphql/tracing/scout_trace.rb +32 -55
- data/lib/graphql/tracing/scout_tracing.rb +2 -0
- data/lib/graphql/tracing/sentry_trace.rb +62 -94
- data/lib/graphql/tracing/statsd_trace.rb +33 -41
- data/lib/graphql/tracing/statsd_tracing.rb +2 -0
- data/lib/graphql/tracing/trace.rb +111 -1
- data/lib/graphql/tracing.rb +31 -30
- data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
- data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +55 -47
- metadata +146 -11
- data/lib/graphql/backtrace/inspect_result.rb +0 -38
- data/lib/graphql/backtrace/trace.rb +0 -93
- data/lib/graphql/backtrace/tracer.rb +0 -80
- data/lib/graphql/schema/null_mask.rb +0 -11
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
data/lib/graphql/schema.rb
CHANGED
@@ -7,7 +7,6 @@ require "graphql/schema/find_inherited_value"
|
|
7
7
|
require "graphql/schema/finder"
|
8
8
|
require "graphql/schema/introspection_system"
|
9
9
|
require "graphql/schema/late_bound_type"
|
10
|
-
require "graphql/schema/null_mask"
|
11
10
|
require "graphql/schema/timeout"
|
12
11
|
require "graphql/schema/type_expression"
|
13
12
|
require "graphql/schema/unique_within_type"
|
@@ -61,7 +60,7 @@ module GraphQL
|
|
61
60
|
# Any undiscoverable types may be provided with the `types` configuration.
|
62
61
|
#
|
63
62
|
# Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
|
64
|
-
# (These configurations can be overridden by specific calls to {Schema
|
63
|
+
# (These configurations can be overridden by specific calls to {Schema.execute})
|
65
64
|
#
|
66
65
|
# @example defining a schema
|
67
66
|
# class MySchema < GraphQL::Schema
|
@@ -73,6 +72,9 @@ module GraphQL
|
|
73
72
|
class Schema
|
74
73
|
extend GraphQL::Schema::Member::HasAstNode
|
75
74
|
extend GraphQL::Schema::FindInheritedValue
|
75
|
+
extend Autoload
|
76
|
+
|
77
|
+
autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
|
76
78
|
|
77
79
|
class DuplicateNamesError < GraphQL::Error
|
78
80
|
attr_reader :duplicated_name
|
@@ -109,7 +111,7 @@ module GraphQL
|
|
109
111
|
# @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
|
110
112
|
# @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
|
111
113
|
# @return [Class] the schema described by `document`
|
112
|
-
def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
|
114
|
+
def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {})
|
113
115
|
# If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
|
114
116
|
if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
|
115
117
|
GraphQL::Schema::BuildFromDefinition.from_definition_path(
|
@@ -118,6 +120,7 @@ module GraphQL
|
|
118
120
|
default_resolve: default_resolve,
|
119
121
|
parser: parser,
|
120
122
|
using: using,
|
123
|
+
base_types: base_types,
|
121
124
|
)
|
122
125
|
else
|
123
126
|
GraphQL::Schema::BuildFromDefinition.from_definition(
|
@@ -126,6 +129,7 @@ module GraphQL
|
|
126
129
|
default_resolve: default_resolve,
|
127
130
|
parser: parser,
|
128
131
|
using: using,
|
132
|
+
base_types: base_types,
|
129
133
|
)
|
130
134
|
end
|
131
135
|
end
|
@@ -164,9 +168,6 @@ module GraphQL
|
|
164
168
|
mods.each { |mod| new_class.include(mod) }
|
165
169
|
new_class.include(DefaultTraceClass)
|
166
170
|
trace_mode(:default, new_class)
|
167
|
-
backtrace_class = Class.new(new_class)
|
168
|
-
backtrace_class.include(GraphQL::Backtrace::Trace)
|
169
|
-
trace_mode(:default_backtrace, backtrace_class)
|
170
171
|
end
|
171
172
|
trace_class_for(:default, build: true)
|
172
173
|
end
|
@@ -213,11 +214,6 @@ module GraphQL
|
|
213
214
|
const_set(:DefaultTrace, Class.new(base_class) do
|
214
215
|
include DefaultTraceClass
|
215
216
|
end)
|
216
|
-
when :default_backtrace
|
217
|
-
schema_base_class = trace_class_for(:default, build: true)
|
218
|
-
const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
|
219
|
-
include(GraphQL::Backtrace::Trace)
|
220
|
-
end)
|
221
217
|
else
|
222
218
|
# First, see if the superclass has a custom-defined class for this.
|
223
219
|
# Then, if it doesn't, use this class's default trace
|
@@ -233,7 +229,7 @@ module GraphQL
|
|
233
229
|
add_trace_options_for(mode, default_options)
|
234
230
|
|
235
231
|
Class.new(base_class) do
|
236
|
-
mods.
|
232
|
+
!mods.empty? && include(*mods)
|
237
233
|
end
|
238
234
|
end
|
239
235
|
end
|
@@ -253,7 +249,7 @@ module GraphQL
|
|
253
249
|
|
254
250
|
|
255
251
|
# Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
|
256
|
-
# @see
|
252
|
+
# @see #as_json Return a Hash representation of the schema
|
257
253
|
# @return [String]
|
258
254
|
def to_json(**args)
|
259
255
|
JSON.pretty_generate(as_json(**args))
|
@@ -261,8 +257,6 @@ module GraphQL
|
|
261
257
|
|
262
258
|
# Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
|
263
259
|
# @param context [Hash]
|
264
|
-
# @param only [<#call(member, ctx)>]
|
265
|
-
# @param except [<#call(member, ctx)>]
|
266
260
|
# @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response
|
267
261
|
# @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response
|
268
262
|
# @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives
|
@@ -321,7 +315,7 @@ module GraphQL
|
|
321
315
|
# @param plugin [#use] A Schema plugin
|
322
316
|
# @return void
|
323
317
|
def use(plugin, **kwargs)
|
324
|
-
if kwargs.
|
318
|
+
if !kwargs.empty?
|
325
319
|
plugin.use(self, **kwargs)
|
326
320
|
else
|
327
321
|
plugin.use(self)
|
@@ -446,7 +440,12 @@ module GraphQL
|
|
446
440
|
raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
|
447
441
|
elsif use_visibility_profile?
|
448
442
|
if block_given?
|
449
|
-
|
443
|
+
if visibility.preload?
|
444
|
+
@query_object = lazy_load_block.call
|
445
|
+
self.visibility.query_configured(@query_object)
|
446
|
+
else
|
447
|
+
@query_object = lazy_load_block
|
448
|
+
end
|
450
449
|
else
|
451
450
|
@query_object = new_query_object
|
452
451
|
self.visibility.query_configured(@query_object)
|
@@ -480,7 +479,12 @@ module GraphQL
|
|
480
479
|
raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
|
481
480
|
elsif use_visibility_profile?
|
482
481
|
if block_given?
|
483
|
-
|
482
|
+
if visibility.preload?
|
483
|
+
@mutation_object = lazy_load_block.call
|
484
|
+
self.visibility.mutation_configured(@mutation_object)
|
485
|
+
else
|
486
|
+
@mutation_object = lazy_load_block
|
487
|
+
end
|
484
488
|
else
|
485
489
|
@mutation_object = new_mutation_object
|
486
490
|
self.visibility.mutation_configured(@mutation_object)
|
@@ -514,7 +518,12 @@ module GraphQL
|
|
514
518
|
raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
|
515
519
|
elsif use_visibility_profile?
|
516
520
|
if block_given?
|
517
|
-
|
521
|
+
if visibility.preload?
|
522
|
+
@subscription_object = lazy_load_block.call
|
523
|
+
visibility.subscription_configured(@subscription_object)
|
524
|
+
else
|
525
|
+
@subscription_object = lazy_load_block
|
526
|
+
end
|
518
527
|
else
|
519
528
|
@subscription_object = new_subscription_object
|
520
529
|
self.visibility.subscription_configured(@subscription_object)
|
@@ -676,7 +685,7 @@ module GraphQL
|
|
676
685
|
# and generally speaking, we won't inherit any values.
|
677
686
|
# So optimize the most common case -- don't create a duplicate Hash.
|
678
687
|
inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
|
679
|
-
if inherited_value.
|
688
|
+
if !inherited_value.empty?
|
680
689
|
inherited_value.merge(own_references_to)
|
681
690
|
else
|
682
691
|
own_references_to
|
@@ -812,13 +821,13 @@ module GraphQL
|
|
812
821
|
|
813
822
|
attr_writer :validate_timeout
|
814
823
|
|
815
|
-
def validate_timeout(new_validate_timeout =
|
816
|
-
if new_validate_timeout
|
824
|
+
def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
|
825
|
+
if !NOT_CONFIGURED.equal?(new_validate_timeout)
|
817
826
|
@validate_timeout = new_validate_timeout
|
818
827
|
elsif defined?(@validate_timeout)
|
819
828
|
@validate_timeout
|
820
829
|
else
|
821
|
-
find_inherited_value(:validate_timeout)
|
830
|
+
find_inherited_value(:validate_timeout) || 3
|
822
831
|
end
|
823
832
|
end
|
824
833
|
|
@@ -962,7 +971,7 @@ module GraphQL
|
|
962
971
|
# @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
|
963
972
|
# @return [Array<Module>] Type definitions added to this schema
|
964
973
|
def extra_types(*new_extra_types)
|
965
|
-
if new_extra_types.
|
974
|
+
if !new_extra_types.empty?
|
966
975
|
new_extra_types = new_extra_types.flatten
|
967
976
|
@own_extra_types ||= []
|
968
977
|
@own_extra_types.concat(new_extra_types)
|
@@ -987,10 +996,10 @@ module GraphQL
|
|
987
996
|
# @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
|
988
997
|
# @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
|
989
998
|
def orphan_types(*new_orphan_types)
|
990
|
-
if new_orphan_types.
|
999
|
+
if !new_orphan_types.empty?
|
991
1000
|
new_orphan_types = new_orphan_types.flatten
|
992
1001
|
non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
|
993
|
-
if non_object_types.
|
1002
|
+
if !non_object_types.empty?
|
994
1003
|
raise ArgumentError, <<~ERR
|
995
1004
|
Only object type classes should be added as `orphan_types(...)`.
|
996
1005
|
|
@@ -1007,7 +1016,7 @@ module GraphQL
|
|
1007
1016
|
|
1008
1017
|
inherited_ot = find_inherited_value(:orphan_types, nil)
|
1009
1018
|
if inherited_ot
|
1010
|
-
if own_orphan_types.
|
1019
|
+
if !own_orphan_types.empty?
|
1011
1020
|
inherited_ot + own_orphan_types
|
1012
1021
|
else
|
1013
1022
|
inherited_ot
|
@@ -1055,6 +1064,18 @@ module GraphQL
|
|
1055
1064
|
end
|
1056
1065
|
end
|
1057
1066
|
|
1067
|
+
# @param context [GraphQL::Query::Context, nil]
|
1068
|
+
# @return [Logger] A logger to use for this context configuration, falling back to {.default_logger}
|
1069
|
+
def logger_for(context)
|
1070
|
+
if context && context[:logger] == false
|
1071
|
+
Logger.new(IO::NULL)
|
1072
|
+
elsif context && (l = context[:logger])
|
1073
|
+
l
|
1074
|
+
else
|
1075
|
+
default_logger
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
|
1058
1079
|
# @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
|
1059
1080
|
def context_class(new_context_class = nil)
|
1060
1081
|
if new_context_class
|
@@ -1100,6 +1121,9 @@ module GraphQL
|
|
1100
1121
|
}
|
1101
1122
|
end
|
1102
1123
|
|
1124
|
+
# @api private
|
1125
|
+
attr_accessor :using_backtrace
|
1126
|
+
|
1103
1127
|
# @api private
|
1104
1128
|
def handle_or_reraise(context, err)
|
1105
1129
|
handler = Execution::Errors.find_handler_for(self, err.class)
|
@@ -1113,6 +1137,10 @@ module GraphQL
|
|
1113
1137
|
end
|
1114
1138
|
handler[:handler].call(err, obj, args, context, field)
|
1115
1139
|
else
|
1140
|
+
if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
|
1141
|
+
err = GraphQL::Backtrace::TracedError.new(err, context)
|
1142
|
+
end
|
1143
|
+
|
1116
1144
|
raise err
|
1117
1145
|
end
|
1118
1146
|
end
|
@@ -1147,7 +1175,7 @@ module GraphQL
|
|
1147
1175
|
# GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
|
1148
1176
|
#
|
1149
1177
|
# Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
|
1150
|
-
# But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument
|
1178
|
+
# 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.
|
1151
1179
|
#
|
1152
1180
|
# @example Returning a GraphQL type based on the object's class name
|
1153
1181
|
# class MySchema < GraphQL::Schema
|
@@ -1202,7 +1230,7 @@ module GraphQL
|
|
1202
1230
|
|
1203
1231
|
# Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
|
1204
1232
|
#
|
1205
|
-
#
|
1233
|
+
# [GlobalID](https://github.com/rails/globalid) and [SQIDs](https://sqids.org/ruby) can both be used to create IDs.
|
1206
1234
|
#
|
1207
1235
|
# @example Using Rails's GlobalID to generate IDs
|
1208
1236
|
# def self.id_from_object(application_object, graphql_type, context)
|
@@ -1279,10 +1307,13 @@ module GraphQL
|
|
1279
1307
|
# @return [void]
|
1280
1308
|
# @raise [GraphQL::ExecutionError] to return this error to the client
|
1281
1309
|
# @raise [GraphQL::Error] to crash the query and raise a developer-facing error
|
1282
|
-
def type_error(type_error,
|
1310
|
+
def type_error(type_error, context)
|
1283
1311
|
case type_error
|
1284
1312
|
when GraphQL::InvalidNullError
|
1285
|
-
|
1313
|
+
execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
|
1314
|
+
execution_error.path = context[:current_path]
|
1315
|
+
|
1316
|
+
context.errors << execution_error
|
1286
1317
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
1287
1318
|
raise type_error
|
1288
1319
|
when GraphQL::IntegerDecodingError
|
@@ -1290,7 +1321,7 @@ module GraphQL
|
|
1290
1321
|
end
|
1291
1322
|
end
|
1292
1323
|
|
1293
|
-
# A function to call when {
|
1324
|
+
# A function to call when {.execute} receives an invalid query string
|
1294
1325
|
#
|
1295
1326
|
# The default is to add the error to `context.errors`
|
1296
1327
|
# @param parse_err [GraphQL::ParseError] The error encountered during parsing
|
@@ -1317,12 +1348,12 @@ module GraphQL
|
|
1317
1348
|
# Add several directives at once
|
1318
1349
|
# @param new_directives [Class]
|
1319
1350
|
def directives(*new_directives)
|
1320
|
-
if new_directives.
|
1351
|
+
if !new_directives.empty?
|
1321
1352
|
new_directives.flatten.each { |d| directive(d) }
|
1322
1353
|
end
|
1323
1354
|
|
1324
1355
|
inherited_dirs = find_inherited_value(:directives, default_directives)
|
1325
|
-
if own_directives.
|
1356
|
+
if !own_directives.empty?
|
1326
1357
|
inherited_dirs.merge(own_directives)
|
1327
1358
|
else
|
1328
1359
|
inherited_dirs
|
@@ -1350,6 +1381,16 @@ module GraphQL
|
|
1350
1381
|
}.freeze
|
1351
1382
|
end
|
1352
1383
|
|
1384
|
+
# @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
|
1385
|
+
attr_accessor :detailed_trace
|
1386
|
+
|
1387
|
+
# @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
|
1388
|
+
# @return [Boolean] When `true`, save a detailed trace for this query.
|
1389
|
+
# @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
|
1390
|
+
def detailed_trace?(query)
|
1391
|
+
raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
|
1392
|
+
end
|
1393
|
+
|
1353
1394
|
def tracer(new_tracer, silence_deprecation_warning: false)
|
1354
1395
|
if !silence_deprecation_warning
|
1355
1396
|
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
@@ -1367,14 +1408,22 @@ module GraphQL
|
|
1367
1408
|
find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
|
1368
1409
|
end
|
1369
1410
|
|
1370
|
-
# Mix `trace_mod` into this schema's `Trace` class so that its methods
|
1371
|
-
#
|
1411
|
+
# Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
|
1412
|
+
#
|
1413
|
+
# You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
|
1414
|
+
# it will only run for queries with a matching `context[:trace_mode]`.
|
1415
|
+
#
|
1416
|
+
# Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
|
1417
|
+
#
|
1418
|
+
# @example Adding a trace in a special mode
|
1419
|
+
# # only runs when `query.context[:trace_mode]` is `:special`
|
1420
|
+
# trace_with SpecialTrace, mode: :special
|
1372
1421
|
#
|
1373
1422
|
# @param trace_mod [Module] A module that implements tracing methods
|
1374
1423
|
# @param mode [Symbol] Trace module will only be used for this trade mode
|
1375
1424
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
1376
1425
|
# @return [void]
|
1377
|
-
# @see GraphQL::Tracing::Trace for available tracing methods
|
1426
|
+
# @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
|
1378
1427
|
def trace_with(trace_mod, mode: :default, **options)
|
1379
1428
|
if mode.is_a?(Array)
|
1380
1429
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
@@ -1424,29 +1473,36 @@ module GraphQL
|
|
1424
1473
|
#
|
1425
1474
|
# If no `mode:` is given, then {default_trace_mode} will be used.
|
1426
1475
|
#
|
1476
|
+
# If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
|
1477
|
+
# DetailedTrace's mode will override the passed-in `mode`.
|
1478
|
+
#
|
1427
1479
|
# @param mode [Symbol] Trace modules for this trade mode will be included
|
1428
1480
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
1429
1481
|
# @return [Tracing::Trace]
|
1430
1482
|
def new_trace(mode: nil, **options)
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
|
1441
|
-
options_trace_mode = :default
|
1442
|
-
:default_backtrace
|
1483
|
+
should_sample = if detailed_trace
|
1484
|
+
if (query = options[:query])
|
1485
|
+
detailed_trace?(query)
|
1486
|
+
elsif (multiplex = options[:multiplex])
|
1487
|
+
if multiplex.queries.length == 1
|
1488
|
+
detailed_trace?(multiplex.queries.first)
|
1489
|
+
else
|
1490
|
+
detailed_trace?(multiplex)
|
1491
|
+
end
|
1443
1492
|
end
|
1444
1493
|
else
|
1445
|
-
|
1494
|
+
false
|
1446
1495
|
end
|
1447
1496
|
|
1448
|
-
|
1449
|
-
|
1497
|
+
if should_sample
|
1498
|
+
mode = detailed_trace.trace_mode
|
1499
|
+
else
|
1500
|
+
target = options[:query] || options[:multiplex]
|
1501
|
+
mode ||= target && target.context[:trace_mode]
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
trace_mode = mode || default_trace_mode
|
1505
|
+
base_trace_options = trace_options_for(trace_mode)
|
1450
1506
|
trace_options = base_trace_options.merge(options)
|
1451
1507
|
trace_class_for_mode = trace_class_for(trace_mode, build: true)
|
1452
1508
|
trace_class_for_mode.new(**trace_options)
|
@@ -1521,7 +1577,8 @@ module GraphQL
|
|
1521
1577
|
# @see {Query#initialize} for query keyword arguments
|
1522
1578
|
# @see {Execution::Multiplex#run_all} for multiplex keyword arguments
|
1523
1579
|
# @param queries [Array<Hash>] Keyword arguments for each query
|
1524
|
-
# @
|
1580
|
+
# @option kwargs [Hash] :context ({}) Multiplex-level context
|
1581
|
+
# @option kwargs [nil, Integer] :max_complexity (nil)
|
1525
1582
|
# @return [Array<GraphQL::Query::Result>] One result for each query in the input
|
1526
1583
|
def multiplex(queries, **kwargs)
|
1527
1584
|
GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
|
@@ -1586,7 +1643,7 @@ module GraphQL
|
|
1586
1643
|
end
|
1587
1644
|
end
|
1588
1645
|
|
1589
|
-
# @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {
|
1646
|
+
# @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {.lazy_resolve}.
|
1590
1647
|
def lazy_method_name(obj)
|
1591
1648
|
lazy_methods.get(obj)
|
1592
1649
|
end
|
@@ -1624,6 +1681,158 @@ module GraphQL
|
|
1624
1681
|
end
|
1625
1682
|
end
|
1626
1683
|
|
1684
|
+
|
1685
|
+
# This setting controls how GraphQL-Ruby handles empty selections on Union types.
|
1686
|
+
#
|
1687
|
+
# To opt into future, spec-compliant behavior where these selections are rejected, set this to `false`.
|
1688
|
+
#
|
1689
|
+
# If you need to support previous, non-spec behavior which allowed selecting union fields
|
1690
|
+
# but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
|
1691
|
+
#
|
1692
|
+
# If this is `true`, then {.legacy_invalid_empty_selections_on_union} will be called with {Query} objects
|
1693
|
+
# with that kind of selections. You must implement that method
|
1694
|
+
# @param new_value [Boolean]
|
1695
|
+
# @return [true, false, nil]
|
1696
|
+
def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED)
|
1697
|
+
if NOT_CONFIGURED.equal?(new_value)
|
1698
|
+
@allow_legacy_invalid_empty_selections_on_union
|
1699
|
+
else
|
1700
|
+
@allow_legacy_invalid_empty_selections_on_union = new_value
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
|
1704
|
+
# This method is called during validation when a previously-allowed, but non-spec
|
1705
|
+
# query is encountered where a union field has no child selections on it.
|
1706
|
+
#
|
1707
|
+
# You should implement this method to log the violation so that you can contact clients
|
1708
|
+
# and notify them about changing their queries. Then return a suitable value to
|
1709
|
+
# tell GraphQL-Ruby how to continue.
|
1710
|
+
# @param query [GraphQL::Query]
|
1711
|
+
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
1712
|
+
# @return [String] A validation error to return for this query
|
1713
|
+
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
1714
|
+
def legacy_invalid_empty_selections_on_union(query)
|
1715
|
+
raise "Implement `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
|
1716
|
+
end
|
1717
|
+
|
1718
|
+
# This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
|
1719
|
+
# don't match.
|
1720
|
+
#
|
1721
|
+
# When set to `false`, GraphQL-Ruby will reject those queries with a validation error (as per the GraphQL spec).
|
1722
|
+
#
|
1723
|
+
# When set to `true`, GraphQL-Ruby will call {.legacy_invalid_return_type_conflicts} when the scenario is encountered.
|
1724
|
+
#
|
1725
|
+
# @param new_value [Boolean] `true` permits the legacy behavior, `false` rejects it.
|
1726
|
+
# @return [true, false, nil]
|
1727
|
+
def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED)
|
1728
|
+
if NOT_CONFIGURED.equal?(new_value)
|
1729
|
+
@allow_legacy_invalid_return_type_conflicts
|
1730
|
+
else
|
1731
|
+
@allow_legacy_invalid_return_type_conflicts = new_value
|
1732
|
+
end
|
1733
|
+
end
|
1734
|
+
|
1735
|
+
# This method is called when the query contains fields which don't contain matching scalar types.
|
1736
|
+
# This was previously allowed by GraphQL-Ruby but it's a violation of the GraphQL spec.
|
1737
|
+
#
|
1738
|
+
# You should implement this method to log the violation so that you observe usage of these fields.
|
1739
|
+
# Fixing this scenario might mean adding new fields, and telling clients to use those fields.
|
1740
|
+
# (Changing the field return type would be a breaking change, but if it works for your client use cases,
|
1741
|
+
# that might work, too.)
|
1742
|
+
#
|
1743
|
+
# @param query [GraphQL::Query]
|
1744
|
+
# @param type1 [Module] A GraphQL type definition
|
1745
|
+
# @param type2 [Module] A GraphQL type definition
|
1746
|
+
# @param node1 [GraphQL::Language::Nodes::Field] This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting.
|
1747
|
+
# @param node2 [GraphQL::Language::Nodes::Field] The other node recognized as conflicting.
|
1748
|
+
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
1749
|
+
# @return [String] A validation error to return for this query
|
1750
|
+
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
1751
|
+
def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2)
|
1752
|
+
raise "Implement #{self}.legacy_invalid_return_type_conflicts to handle this invalid selection"
|
1753
|
+
end
|
1754
|
+
|
1755
|
+
# The legacy complexity implementation included several bugs:
|
1756
|
+
#
|
1757
|
+
# - In some cases, it used the lexically _last_ field to determine a cost, instead of calculating the maximum among selections
|
1758
|
+
# - In some cases, it called field complexity hooks repeatedly (when it should have only called them once)
|
1759
|
+
#
|
1760
|
+
# The future implementation may produce higher total complexity scores, so it's not active by default yet. You can opt into
|
1761
|
+
# the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with {.complexity_cost_calculation_mode_for}.
|
1762
|
+
#
|
1763
|
+
# The legacy mode is currently maintained alongside the future one, but it will be removed in a future GraphQL-Ruby version.
|
1764
|
+
#
|
1765
|
+
# If you choose `:compare`, you must also implement {.legacy_complexity_cost_calculation_mismatch} to handle the input somehow.
|
1766
|
+
#
|
1767
|
+
# @example Opting into the future calculation mode
|
1768
|
+
# complexity_cost_calculation_mode(:future)
|
1769
|
+
#
|
1770
|
+
# @example Choosing the legacy mode (which will work until that mode is removed...)
|
1771
|
+
# complexity_cost_calculation_mode(:legacy)
|
1772
|
+
#
|
1773
|
+
# @example Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match:
|
1774
|
+
# complexity_cost_calculation_mode(:compare)
|
1775
|
+
def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED)
|
1776
|
+
if NOT_CONFIGURED.equal?(new_mode)
|
1777
|
+
@complexity_cost_calculation_mode
|
1778
|
+
else
|
1779
|
+
@complexity_cost_calculation_mode = new_mode
|
1780
|
+
end
|
1781
|
+
end
|
1782
|
+
|
1783
|
+
# Implement this method to produce a per-query complexity cost calculation mode. (Technically, it's per-multiplex.)
|
1784
|
+
#
|
1785
|
+
# This is a way to check the compatibility of queries coming to your API without adding overhead of running `:compare`
|
1786
|
+
# for every query. You could sample traffic, turn it off/on with feature flags, or anything else.
|
1787
|
+
#
|
1788
|
+
# @example Sampling traffic
|
1789
|
+
# def self.complexity_cost_calculation_mode_for(_context)
|
1790
|
+
# if rand < 0.1 # 10% of the time
|
1791
|
+
# :compare
|
1792
|
+
# else
|
1793
|
+
# :legacy
|
1794
|
+
# end
|
1795
|
+
# end
|
1796
|
+
#
|
1797
|
+
# @example Using a feature flag to manage future mode
|
1798
|
+
# def complexity_cost_calculation_mode_for(context)
|
1799
|
+
# current_user = context[:current_user]
|
1800
|
+
# if Flipper.enabled?(:future_complexity_cost, current_user)
|
1801
|
+
# :future
|
1802
|
+
# elsif rand < 0.5 # 50%
|
1803
|
+
# :compare
|
1804
|
+
# else
|
1805
|
+
# :legacy
|
1806
|
+
# end
|
1807
|
+
# end
|
1808
|
+
#
|
1809
|
+
# @param multiplex_context [Hash] The context for the currently-running {Execution::Multiplex} (which contains one or more queries)
|
1810
|
+
# @return [:future] Use the new calculation algorithm -- may be higher than `:legacy`
|
1811
|
+
# @return [:legacy] Use the legacy calculation algorithm, warts and all
|
1812
|
+
# @return [:compare] Run both algorithms and call {.legacy_complexity_cost_calculation_mismatch} if they don't match
|
1813
|
+
def complexity_cost_calculation_mode_for(multiplex_context)
|
1814
|
+
complexity_cost_calculation_mode
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
# Implement this method in your schema to handle mismatches when `:compare` is used.
|
1818
|
+
#
|
1819
|
+
# @example Logging the mismatch
|
1820
|
+
# def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost)
|
1821
|
+
# client_id = multiplex.context[:api_client].id
|
1822
|
+
# operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ")
|
1823
|
+
# Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names })
|
1824
|
+
# legacy_cost
|
1825
|
+
# end
|
1826
|
+
# @see Query::Context#add_error Adding an error to the response to notify the client
|
1827
|
+
# @see Query::Context#response_extensions Adding key-value pairs to the response `"extensions" => { ... }`
|
1828
|
+
# @param multiplex [GraphQL::Execution::Multiplex]
|
1829
|
+
# @param future_complexity_cost [Integer]
|
1830
|
+
# @param legacy_complexity_cost [Integer]
|
1831
|
+
# @return [Integer] the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`)
|
1832
|
+
def legacy_complexity_cost_calculation_mismatch(multiplex, future_complexity_cost, legacy_complexity_cost)
|
1833
|
+
raise "Implement #{self}.legacy_complexity_cost(multiplex, future_complexity_cost, legacy_complexity_cost) to handle this mismatch (#{future_complexity_cost} vs. #{legacy_complexity_cost}) and return a value to use"
|
1834
|
+
end
|
1835
|
+
|
1627
1836
|
private
|
1628
1837
|
|
1629
1838
|
def add_trace_options_for(mode, new_options)
|
@@ -1787,3 +1996,6 @@ module GraphQL
|
|
1787
1996
|
end
|
1788
1997
|
end
|
1789
1998
|
end
|
1999
|
+
|
2000
|
+
require "graphql/schema/loader"
|
2001
|
+
require "graphql/schema/printer"
|
@@ -34,7 +34,7 @@ module GraphQL
|
|
34
34
|
GraphQL::StaticValidation::VariableUsagesAreAllowed,
|
35
35
|
GraphQL::StaticValidation::MutationRootExists,
|
36
36
|
GraphQL::StaticValidation::QueryRootExists,
|
37
|
-
GraphQL::StaticValidation::
|
37
|
+
GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
|
38
38
|
GraphQL::StaticValidation::InputObjectNamesAreUnique,
|
39
39
|
GraphQL::StaticValidation::OneOfInputObjectsAreValid,
|
40
40
|
]
|
@@ -16,7 +16,7 @@ module GraphQL
|
|
16
16
|
|
17
17
|
def validate_arguments(node)
|
18
18
|
argument_defns = node.arguments
|
19
|
-
if argument_defns.
|
19
|
+
if !argument_defns.empty?
|
20
20
|
args_by_name = Hash.new { |h, k| h[k] = [] }
|
21
21
|
argument_defns.each { |a| args_by_name[a.name] << a }
|
22
22
|
args_by_name.each do |name, defns|
|
@@ -25,22 +25,56 @@ module GraphQL
|
|
25
25
|
def validate_field_selections(ast_node, resolved_type)
|
26
26
|
msg = if resolved_type.nil?
|
27
27
|
nil
|
28
|
-
elsif
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
elsif resolved_type.kind.leaf?
|
29
|
+
if !ast_node.selections.empty?
|
30
|
+
selection_strs = ast_node.selections.map do |n|
|
31
|
+
case n
|
32
|
+
when GraphQL::Language::Nodes::InlineFragment
|
33
|
+
"\"... on #{n.type.name} { ... }\""
|
34
|
+
when GraphQL::Language::Nodes::Field
|
35
|
+
"\"#{n.name}\""
|
36
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
37
|
+
"\"#{n.name}\""
|
38
|
+
else
|
39
|
+
raise "Invariant: unexpected selection node: #{n}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
"Selections can't be made on #{resolved_type.kind.name.sub("_", " ").downcase}s (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{selection_strs.join(", ")}])"
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
elsif ast_node.selections.empty?
|
47
|
+
return_validation_error = true
|
48
|
+
legacy_invalid_empty_selection_result = nil
|
49
|
+
if !resolved_type.kind.fields?
|
50
|
+
case @schema.allow_legacy_invalid_empty_selections_on_union
|
51
|
+
when true
|
52
|
+
legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union(@context.query)
|
53
|
+
case legacy_invalid_empty_selection_result
|
54
|
+
when :return_validation_error
|
55
|
+
# keep `return_validation_error = true`
|
56
|
+
when String
|
57
|
+
return_validation_error = false
|
58
|
+
# the string is returned below
|
59
|
+
when nil
|
60
|
+
# No error:
|
61
|
+
return_validation_error = false
|
62
|
+
legacy_invalid_empty_selection_result = nil
|
63
|
+
else
|
64
|
+
raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
|
65
|
+
end
|
66
|
+
when false
|
67
|
+
# pass -- error below
|
37
68
|
else
|
38
|
-
|
69
|
+
return_validation_error = false
|
70
|
+
@context.query.logger.warn("Unions require selections but #{ast_node.alias || ast_node.name} (#{resolved_type.graphql_name}) doesn't have any. This will fail with a validation error on a future GraphQL-Ruby version. More info: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_empty_selections_on_union-class_method")
|
39
71
|
end
|
40
72
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
73
|
+
if return_validation_error
|
74
|
+
"Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)"
|
75
|
+
else
|
76
|
+
legacy_invalid_empty_selection_result
|
77
|
+
end
|
44
78
|
else
|
45
79
|
nil
|
46
80
|
end
|