graphql 2.4.3 → 2.5.1
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/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 +6 -1
- 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/execution/interpreter/resolve.rb +3 -3
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
- data/lib/graphql/execution/interpreter/runtime.rb +78 -41
- data/lib/graphql/execution/interpreter.rb +16 -7
- data/lib/graphql/execution/multiplex.rb +1 -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 +5 -15
- 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 +6 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +19 -23
- data/lib/graphql/railtie.rb +7 -0
- data/lib/graphql/schema/addition.rb +1 -1
- 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 +1 -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 +12 -12
- 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 +76 -39
- data/lib/graphql/schema/interface.rb +3 -2
- 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 +60 -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 +11 -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 +69 -237
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +169 -28
- data/lib/graphql/schema/warden.rb +18 -5
- data/lib/graphql/schema.rb +93 -44
- 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 +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- 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"
|
|
@@ -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
|
|
@@ -321,7 +317,7 @@ module GraphQL
|
|
|
321
317
|
# @param plugin [#use] A Schema plugin
|
|
322
318
|
# @return void
|
|
323
319
|
def use(plugin, **kwargs)
|
|
324
|
-
if kwargs.
|
|
320
|
+
if !kwargs.empty?
|
|
325
321
|
plugin.use(self, **kwargs)
|
|
326
322
|
else
|
|
327
323
|
plugin.use(self)
|
|
@@ -446,7 +442,12 @@ module GraphQL
|
|
|
446
442
|
raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
|
|
447
443
|
elsif use_visibility_profile?
|
|
448
444
|
if block_given?
|
|
449
|
-
|
|
445
|
+
if visibility.preload?
|
|
446
|
+
@query_object = lazy_load_block.call
|
|
447
|
+
self.visibility.query_configured(@query_object)
|
|
448
|
+
else
|
|
449
|
+
@query_object = lazy_load_block
|
|
450
|
+
end
|
|
450
451
|
else
|
|
451
452
|
@query_object = new_query_object
|
|
452
453
|
self.visibility.query_configured(@query_object)
|
|
@@ -480,7 +481,12 @@ module GraphQL
|
|
|
480
481
|
raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
|
|
481
482
|
elsif use_visibility_profile?
|
|
482
483
|
if block_given?
|
|
483
|
-
|
|
484
|
+
if visibility.preload?
|
|
485
|
+
@mutation_object = lazy_load_block.call
|
|
486
|
+
self.visibility.mutation_configured(@mutation_object)
|
|
487
|
+
else
|
|
488
|
+
@mutation_object = lazy_load_block
|
|
489
|
+
end
|
|
484
490
|
else
|
|
485
491
|
@mutation_object = new_mutation_object
|
|
486
492
|
self.visibility.mutation_configured(@mutation_object)
|
|
@@ -514,7 +520,12 @@ module GraphQL
|
|
|
514
520
|
raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
|
|
515
521
|
elsif use_visibility_profile?
|
|
516
522
|
if block_given?
|
|
517
|
-
|
|
523
|
+
if visibility.preload?
|
|
524
|
+
@subscription_object = lazy_load_block.call
|
|
525
|
+
visibility.subscription_configured(@subscription_object)
|
|
526
|
+
else
|
|
527
|
+
@subscription_object = lazy_load_block
|
|
528
|
+
end
|
|
518
529
|
else
|
|
519
530
|
@subscription_object = new_subscription_object
|
|
520
531
|
self.visibility.subscription_configured(@subscription_object)
|
|
@@ -676,7 +687,7 @@ module GraphQL
|
|
|
676
687
|
# and generally speaking, we won't inherit any values.
|
|
677
688
|
# So optimize the most common case -- don't create a duplicate Hash.
|
|
678
689
|
inherited_value = find_inherited_value(:references_to, EMPTY_HASH)
|
|
679
|
-
if inherited_value.
|
|
690
|
+
if !inherited_value.empty?
|
|
680
691
|
inherited_value.merge(own_references_to)
|
|
681
692
|
else
|
|
682
693
|
own_references_to
|
|
@@ -812,13 +823,13 @@ module GraphQL
|
|
|
812
823
|
|
|
813
824
|
attr_writer :validate_timeout
|
|
814
825
|
|
|
815
|
-
def validate_timeout(new_validate_timeout =
|
|
816
|
-
if new_validate_timeout
|
|
826
|
+
def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
|
|
827
|
+
if !NOT_CONFIGURED.equal?(new_validate_timeout)
|
|
817
828
|
@validate_timeout = new_validate_timeout
|
|
818
829
|
elsif defined?(@validate_timeout)
|
|
819
830
|
@validate_timeout
|
|
820
831
|
else
|
|
821
|
-
find_inherited_value(:validate_timeout)
|
|
832
|
+
find_inherited_value(:validate_timeout) || 3
|
|
822
833
|
end
|
|
823
834
|
end
|
|
824
835
|
|
|
@@ -962,7 +973,7 @@ module GraphQL
|
|
|
962
973
|
# @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema
|
|
963
974
|
# @return [Array<Module>] Type definitions added to this schema
|
|
964
975
|
def extra_types(*new_extra_types)
|
|
965
|
-
if new_extra_types.
|
|
976
|
+
if !new_extra_types.empty?
|
|
966
977
|
new_extra_types = new_extra_types.flatten
|
|
967
978
|
@own_extra_types ||= []
|
|
968
979
|
@own_extra_types.concat(new_extra_types)
|
|
@@ -987,10 +998,10 @@ module GraphQL
|
|
|
987
998
|
# @param new_orphan_types [Array<Class<GraphQL::Schema::Object>>] Object types to register as implementations of interfaces in the schema.
|
|
988
999
|
# @return [Array<Class<GraphQL::Schema::Object>>] All previously-registered orphan types for this schema
|
|
989
1000
|
def orphan_types(*new_orphan_types)
|
|
990
|
-
if new_orphan_types.
|
|
1001
|
+
if !new_orphan_types.empty?
|
|
991
1002
|
new_orphan_types = new_orphan_types.flatten
|
|
992
1003
|
non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
|
|
993
|
-
if non_object_types.
|
|
1004
|
+
if !non_object_types.empty?
|
|
994
1005
|
raise ArgumentError, <<~ERR
|
|
995
1006
|
Only object type classes should be added as `orphan_types(...)`.
|
|
996
1007
|
|
|
@@ -1007,7 +1018,7 @@ module GraphQL
|
|
|
1007
1018
|
|
|
1008
1019
|
inherited_ot = find_inherited_value(:orphan_types, nil)
|
|
1009
1020
|
if inherited_ot
|
|
1010
|
-
if own_orphan_types.
|
|
1021
|
+
if !own_orphan_types.empty?
|
|
1011
1022
|
inherited_ot + own_orphan_types
|
|
1012
1023
|
else
|
|
1013
1024
|
inherited_ot
|
|
@@ -1100,6 +1111,9 @@ module GraphQL
|
|
|
1100
1111
|
}
|
|
1101
1112
|
end
|
|
1102
1113
|
|
|
1114
|
+
# @api private
|
|
1115
|
+
attr_accessor :using_backtrace
|
|
1116
|
+
|
|
1103
1117
|
# @api private
|
|
1104
1118
|
def handle_or_reraise(context, err)
|
|
1105
1119
|
handler = Execution::Errors.find_handler_for(self, err.class)
|
|
@@ -1113,6 +1127,10 @@ module GraphQL
|
|
|
1113
1127
|
end
|
|
1114
1128
|
handler[:handler].call(err, obj, args, context, field)
|
|
1115
1129
|
else
|
|
1130
|
+
if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
|
|
1131
|
+
err = GraphQL::Backtrace::TracedError.new(err, context)
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1116
1134
|
raise err
|
|
1117
1135
|
end
|
|
1118
1136
|
end
|
|
@@ -1282,7 +1300,10 @@ module GraphQL
|
|
|
1282
1300
|
def type_error(type_error, ctx)
|
|
1283
1301
|
case type_error
|
|
1284
1302
|
when GraphQL::InvalidNullError
|
|
1285
|
-
|
|
1303
|
+
execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
|
|
1304
|
+
execution_error.path = ctx[:current_path]
|
|
1305
|
+
|
|
1306
|
+
ctx.errors << execution_error
|
|
1286
1307
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1287
1308
|
raise type_error
|
|
1288
1309
|
when GraphQL::IntegerDecodingError
|
|
@@ -1317,12 +1338,12 @@ module GraphQL
|
|
|
1317
1338
|
# Add several directives at once
|
|
1318
1339
|
# @param new_directives [Class]
|
|
1319
1340
|
def directives(*new_directives)
|
|
1320
|
-
if new_directives.
|
|
1341
|
+
if !new_directives.empty?
|
|
1321
1342
|
new_directives.flatten.each { |d| directive(d) }
|
|
1322
1343
|
end
|
|
1323
1344
|
|
|
1324
1345
|
inherited_dirs = find_inherited_value(:directives, default_directives)
|
|
1325
|
-
if own_directives.
|
|
1346
|
+
if !own_directives.empty?
|
|
1326
1347
|
inherited_dirs.merge(own_directives)
|
|
1327
1348
|
else
|
|
1328
1349
|
inherited_dirs
|
|
@@ -1350,6 +1371,16 @@ module GraphQL
|
|
|
1350
1371
|
}.freeze
|
|
1351
1372
|
end
|
|
1352
1373
|
|
|
1374
|
+
# @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
|
|
1375
|
+
attr_accessor :detailed_trace
|
|
1376
|
+
|
|
1377
|
+
# @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
|
|
1378
|
+
# @return [Boolean] When `true`, save a detailed trace for this query.
|
|
1379
|
+
# @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
|
|
1380
|
+
def detailed_trace?(query)
|
|
1381
|
+
raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1353
1384
|
def tracer(new_tracer, silence_deprecation_warning: false)
|
|
1354
1385
|
if !silence_deprecation_warning
|
|
1355
1386
|
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
|
@@ -1367,14 +1398,22 @@ module GraphQL
|
|
|
1367
1398
|
find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
|
|
1368
1399
|
end
|
|
1369
1400
|
|
|
1370
|
-
# Mix `trace_mod` into this schema's `Trace` class so that its methods
|
|
1371
|
-
#
|
|
1401
|
+
# Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
|
|
1402
|
+
#
|
|
1403
|
+
# You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
|
|
1404
|
+
# it will only run for queries with a matching `context[:trace_mode]`.
|
|
1405
|
+
#
|
|
1406
|
+
# Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
|
|
1407
|
+
#
|
|
1408
|
+
# @example Adding a trace in a special mode
|
|
1409
|
+
# # only runs when `query.context[:trace_mode]` is `:special`
|
|
1410
|
+
# trace_with SpecialTrace, mode: :special
|
|
1372
1411
|
#
|
|
1373
1412
|
# @param trace_mod [Module] A module that implements tracing methods
|
|
1374
1413
|
# @param mode [Symbol] Trace module will only be used for this trade mode
|
|
1375
1414
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1376
1415
|
# @return [void]
|
|
1377
|
-
# @see GraphQL::Tracing::Trace for available tracing methods
|
|
1416
|
+
# @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
|
|
1378
1417
|
def trace_with(trace_mod, mode: :default, **options)
|
|
1379
1418
|
if mode.is_a?(Array)
|
|
1380
1419
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
|
@@ -1424,29 +1463,36 @@ module GraphQL
|
|
|
1424
1463
|
#
|
|
1425
1464
|
# If no `mode:` is given, then {default_trace_mode} will be used.
|
|
1426
1465
|
#
|
|
1466
|
+
# If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
|
|
1467
|
+
# DetailedTrace's mode will override the passed-in `mode`.
|
|
1468
|
+
#
|
|
1427
1469
|
# @param mode [Symbol] Trace modules for this trade mode will be included
|
|
1428
1470
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1429
1471
|
# @return [Tracing::Trace]
|
|
1430
1472
|
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
|
|
1473
|
+
should_sample = if detailed_trace
|
|
1474
|
+
if (query = options[:query])
|
|
1475
|
+
detailed_trace?(query)
|
|
1476
|
+
elsif (multiplex = options[:multiplex])
|
|
1477
|
+
if multiplex.queries.length == 1
|
|
1478
|
+
detailed_trace?(multiplex.queries.first)
|
|
1479
|
+
else
|
|
1480
|
+
detailed_trace?(multiplex)
|
|
1481
|
+
end
|
|
1443
1482
|
end
|
|
1444
1483
|
else
|
|
1445
|
-
|
|
1484
|
+
false
|
|
1485
|
+
end
|
|
1486
|
+
|
|
1487
|
+
if should_sample
|
|
1488
|
+
mode = detailed_trace.trace_mode
|
|
1489
|
+
else
|
|
1490
|
+
target = options[:query] || options[:multiplex]
|
|
1491
|
+
mode ||= target && target.context[:trace_mode]
|
|
1446
1492
|
end
|
|
1447
1493
|
|
|
1448
|
-
|
|
1449
|
-
base_trace_options = trace_options_for(
|
|
1494
|
+
trace_mode = mode || default_trace_mode
|
|
1495
|
+
base_trace_options = trace_options_for(trace_mode)
|
|
1450
1496
|
trace_options = base_trace_options.merge(options)
|
|
1451
1497
|
trace_class_for_mode = trace_class_for(trace_mode, build: true)
|
|
1452
1498
|
trace_class_for_mode.new(**trace_options)
|
|
@@ -1787,3 +1833,6 @@ module GraphQL
|
|
|
1787
1833
|
end
|
|
1788
1834
|
end
|
|
1789
1835
|
end
|
|
1836
|
+
|
|
1837
|
+
require "graphql/schema/loader"
|
|
1838
|
+
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,7 +25,7 @@ module GraphQL
|
|
|
25
25
|
def validate_field_selections(ast_node, resolved_type)
|
|
26
26
|
msg = if resolved_type.nil?
|
|
27
27
|
nil
|
|
28
|
-
elsif ast_node.selections.
|
|
28
|
+
elsif !ast_node.selections.empty? && resolved_type.kind.leaf?
|
|
29
29
|
selection_strs = ast_node.selections.map do |n|
|
|
30
30
|
case n
|
|
31
31
|
when GraphQL::Language::Nodes::InlineFragment
|
|
@@ -345,7 +345,7 @@ module GraphQL
|
|
|
345
345
|
fields << Field.new(node, definition, owner_type, parents)
|
|
346
346
|
when GraphQL::Language::Nodes::InlineFragment
|
|
347
347
|
fragment_type = node.type ? @types.type(node.type.name) : owner_type
|
|
348
|
-
find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type:
|
|
348
|
+
find_fields_and_fragments(node.selections, parents: [*parents, fragment_type], owner_type: fragment_type, fields: fields, fragment_spreads: fragment_spreads) if fragment_type
|
|
349
349
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
350
350
|
fragment_spreads << FragmentSpread.new(node.name, parents)
|
|
351
351
|
end
|
|
@@ -32,7 +32,7 @@ module GraphQL
|
|
|
32
32
|
|
|
33
33
|
def on_document(node, parent)
|
|
34
34
|
super
|
|
35
|
-
if
|
|
35
|
+
if !@schema_definition_nodes.empty?
|
|
36
36
|
add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes))
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module StaticValidation
|
|
4
|
+
class NotSingleSubscriptionError < StaticValidation::Error
|
|
5
|
+
def initialize(message, path: nil, nodes: [])
|
|
6
|
+
super(message, path: path, nodes: nodes)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# A hash representation of this Message
|
|
10
|
+
def to_h
|
|
11
|
+
extensions = {
|
|
12
|
+
"code" => code,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
super.merge({
|
|
16
|
+
"extensions" => extensions
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def code
|
|
21
|
+
"notSingleSubscription"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -24,7 +24,7 @@ module GraphQL
|
|
|
24
24
|
.map!(&:name)
|
|
25
25
|
|
|
26
26
|
missing_names = required_argument_names - present_argument_names
|
|
27
|
-
if missing_names.
|
|
27
|
+
if !missing_names.empty?
|
|
28
28
|
add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new(
|
|
29
29
|
"#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}",
|
|
30
30
|
nodes: ast_node,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module StaticValidation
|
|
4
|
+
module SubscriptionRootExistsAndSingleSubscriptionSelection
|
|
5
|
+
def on_operation_definition(node, parent)
|
|
6
|
+
if node.operation_type == "subscription"
|
|
7
|
+
if context.types.subscription_root.nil?
|
|
8
|
+
add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new(
|
|
9
|
+
'Schema is not configured for subscriptions',
|
|
10
|
+
nodes: node
|
|
11
|
+
))
|
|
12
|
+
elsif node.selections.size != 1
|
|
13
|
+
add_error(GraphQL::StaticValidation::NotSingleSubscriptionError.new(
|
|
14
|
+
'A subscription operation may only have one selection',
|
|
15
|
+
nodes: node,
|
|
16
|
+
))
|
|
17
|
+
else
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -4,7 +4,7 @@ module GraphQL
|
|
|
4
4
|
module VariableNamesAreUnique
|
|
5
5
|
def on_operation_definition(node, parent)
|
|
6
6
|
var_defns = node.variables
|
|
7
|
-
if var_defns.
|
|
7
|
+
if !var_defns.empty?
|
|
8
8
|
vars_by_name = Hash.new { |h, k| h[k] = [] }
|
|
9
9
|
var_defns.each { |v| vars_by_name[v.name] << v }
|
|
10
10
|
vars_by_name.each do |name, defns|
|
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
|
21
21
|
end
|
|
22
22
|
node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
|
|
23
23
|
|
|
24
|
-
if node_values.
|
|
24
|
+
if !node_values.empty?
|
|
25
25
|
argument_owner = case parent
|
|
26
26
|
when GraphQL::Language::Nodes::Field
|
|
27
27
|
context.field_definition
|
|
@@ -29,6 +29,7 @@ module GraphQL
|
|
|
29
29
|
@visitor = visitor_class.new(document, self)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# TODO stop using def_delegators because of Array allocations
|
|
32
33
|
def_delegators :@visitor,
|
|
33
34
|
:path, :type_definition, :field_definition, :argument_definition,
|
|
34
35
|
:parent_type_definition, :directive_definition, :object_types, :dependencies
|
|
@@ -27,6 +27,8 @@ module GraphQL
|
|
|
27
27
|
# @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit.
|
|
28
28
|
# @return [Array<Hash>]
|
|
29
29
|
def validate(query, validate: true, timeout: nil, max_errors: nil)
|
|
30
|
+
errors = nil
|
|
31
|
+
query.current_trace.begin_validate(query, validate)
|
|
30
32
|
query.current_trace.validate(validate: validate, query: query) do
|
|
31
33
|
begin_t = Time.now
|
|
32
34
|
errors = if validate == false
|
|
@@ -58,10 +60,13 @@ module GraphQL
|
|
|
58
60
|
}
|
|
59
61
|
end
|
|
60
62
|
rescue GraphQL::ExecutionError => e
|
|
63
|
+
errors = [e]
|
|
61
64
|
{
|
|
62
65
|
remaining_timeout: nil,
|
|
63
|
-
errors:
|
|
66
|
+
errors: errors,
|
|
64
67
|
}
|
|
68
|
+
ensure
|
|
69
|
+
query.current_trace.end_validate(query, validate, errors)
|
|
65
70
|
end
|
|
66
71
|
|
|
67
72
|
# Invoked when static validation times out.
|
|
@@ -171,7 +171,7 @@ module GraphQL
|
|
|
171
171
|
events_by_fingerprint = @events[topic]
|
|
172
172
|
object = nil
|
|
173
173
|
events_by_fingerprint.each do |_fingerprint, events|
|
|
174
|
-
if events.
|
|
174
|
+
if !events.empty? && events.first == initial_event
|
|
175
175
|
# The fingerprint has told us that this response should be shared by all subscribers,
|
|
176
176
|
# so just run it once, then deliver the result to every subscriber
|
|
177
177
|
first_event = events.first
|
|
@@ -20,12 +20,22 @@ module GraphQL
|
|
|
20
20
|
def after_resolve(value:, context:, object:, arguments:, **rest)
|
|
21
21
|
if value.is_a?(GraphQL::ExecutionError)
|
|
22
22
|
value
|
|
23
|
+
elsif @field.resolver&.method_defined?(:subscription_written?) &&
|
|
24
|
+
(subscription_namespace = context.namespace(:subscriptions)) &&
|
|
25
|
+
(subscriptions_by_path = subscription_namespace[:subscriptions])
|
|
26
|
+
(subscription_instance = subscriptions_by_path[context.current_path])
|
|
27
|
+
# If it was already written, don't append this event to be written later
|
|
28
|
+
if !subscription_instance.subscription_written?
|
|
29
|
+
events = context.namespace(:subscriptions)[:events]
|
|
30
|
+
events << subscription_instance.event
|
|
31
|
+
end
|
|
32
|
+
value
|
|
23
33
|
elsif (events = context.namespace(:subscriptions)[:events])
|
|
24
34
|
# This is the first execution, so gather an Event
|
|
25
35
|
# for the backend to register:
|
|
26
36
|
event = Subscriptions::Event.new(
|
|
27
37
|
name: field.name,
|
|
28
|
-
arguments:
|
|
38
|
+
arguments: arguments,
|
|
29
39
|
context: context,
|
|
30
40
|
field: field,
|
|
31
41
|
)
|
|
@@ -33,7 +43,7 @@ module GraphQL
|
|
|
33
43
|
value
|
|
34
44
|
elsif context.query.subscription_topic == Subscriptions::Event.serialize(
|
|
35
45
|
field.name,
|
|
36
|
-
|
|
46
|
+
arguments,
|
|
37
47
|
field,
|
|
38
48
|
scope: (field.subscription_scope ? context[field.subscription_scope] : nil),
|
|
39
49
|
)
|
|
@@ -45,14 +55,6 @@ module GraphQL
|
|
|
45
55
|
context.skip
|
|
46
56
|
end
|
|
47
57
|
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def arguments_without_field_extras(arguments:)
|
|
52
|
-
arguments.dup.tap do |event_args|
|
|
53
|
-
field.extras.each { |k| event_args.delete(k) }
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
60
|
end
|
|
@@ -20,7 +20,7 @@ module GraphQL
|
|
|
20
20
|
|
|
21
21
|
def initialize(name:, arguments:, field: nil, context: nil, scope: nil)
|
|
22
22
|
@name = name
|
|
23
|
-
@arguments = arguments
|
|
23
|
+
@arguments = self.class.arguments_without_field_extras(arguments: arguments, field: field)
|
|
24
24
|
@context = context
|
|
25
25
|
field ||= context.field
|
|
26
26
|
scope_key = field.subscription_scope
|
|
@@ -39,6 +39,7 @@ module GraphQL
|
|
|
39
39
|
# @return [String] an identifier for this unit of subscription
|
|
40
40
|
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
|
|
41
41
|
subscription = field.resolver || GraphQL::Schema::Subscription
|
|
42
|
+
arguments = arguments_without_field_extras(field: field, arguments: arguments)
|
|
42
43
|
normalized_args = stringify_args(field, arguments.to_h, context)
|
|
43
44
|
subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
|
|
44
45
|
end
|
|
@@ -60,6 +61,16 @@ module GraphQL
|
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
class << self
|
|
64
|
+
def arguments_without_field_extras(arguments:, field:)
|
|
65
|
+
if !field.extras.empty?
|
|
66
|
+
arguments = arguments.dup
|
|
67
|
+
field.extras.each do |extra_key|
|
|
68
|
+
arguments.delete(extra_key)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
arguments
|
|
72
|
+
end
|
|
73
|
+
|
|
63
74
|
private
|
|
64
75
|
|
|
65
76
|
# This method does not support cyclic references in the Hash,
|
|
@@ -146,7 +146,7 @@ module GraphQL
|
|
|
146
146
|
elsif obj.is_a?(Date) || obj.is_a?(Time)
|
|
147
147
|
# DateTime extends Date; for TimeWithZone, call `.utc` first.
|
|
148
148
|
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
|
149
|
-
elsif obj.is_a?(OpenStruct)
|
|
149
|
+
elsif defined?(OpenStruct) && obj.is_a?(OpenStruct)
|
|
150
150
|
{ OPEN_STRUCT_KEY => dump_value(obj.to_h) }
|
|
151
151
|
elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
|
|
152
152
|
dump_value(obj.to_a)
|
|
@@ -43,22 +43,25 @@ module GraphQL
|
|
|
43
43
|
type_name, *field_names = field_path.split(".")
|
|
44
44
|
dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context)
|
|
45
45
|
query_context = dummy_query.context
|
|
46
|
+
dataloader = query_context.dataloader
|
|
46
47
|
object_type = dummy_query.types.type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
47
48
|
if object_type
|
|
48
49
|
graphql_result = object
|
|
49
50
|
field_names.each do |field_name|
|
|
50
51
|
inner_object = graphql_result
|
|
51
|
-
|
|
52
|
+
dataloader.run_isolated {
|
|
53
|
+
graphql_result = object_type.wrap(inner_object, query_context)
|
|
54
|
+
}
|
|
52
55
|
if graphql_result.nil?
|
|
53
56
|
return nil
|
|
54
57
|
end
|
|
55
58
|
visible_field = dummy_query.types.field(object_type, field_name) # rubocop:disable Development/ContextIsPassedCop
|
|
56
59
|
if visible_field
|
|
57
|
-
|
|
60
|
+
dataloader.run_isolated {
|
|
58
61
|
query_context[:current_field] = visible_field
|
|
59
62
|
field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
|
|
60
63
|
field_args = schema.sync_lazy(field_args)
|
|
61
|
-
if visible_field.extras.
|
|
64
|
+
if !visible_field.extras.empty?
|
|
62
65
|
extra_args = {}
|
|
63
66
|
visible_field.extras.each do |extra|
|
|
64
67
|
extra_args[extra] = case extra
|
|
@@ -92,7 +95,7 @@ module GraphQL
|
|
|
92
95
|
end
|
|
93
96
|
graphql_result
|
|
94
97
|
else
|
|
95
|
-
unfiltered_type =
|
|
98
|
+
unfiltered_type = schema.use_visibility_profile? ? schema.visibility.get_type(type_name) : schema.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
96
99
|
if unfiltered_type
|
|
97
100
|
raise TypeNotVisibleError.new(type_name: type_name)
|
|
98
101
|
else
|