graphql 2.4.5 → 2.5.21
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/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/graphql/analysis/analyzer.rb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +87 -7
- data/lib/graphql/analysis/visitor.rb +37 -40
- data/lib/graphql/analysis.rb +12 -9
- data/lib/graphql/autoload.rb +1 -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/application_controller.rb +41 -0
- data/lib/graphql/dashboard/detailed_traces.rb +47 -0
- data/lib/graphql/dashboard/installable.rb +22 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -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/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +97 -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 +24 -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 +96 -0
- data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
- data/lib/graphql/dataloader/active_record_source.rb +47 -0
- data/lib/graphql/dataloader/async_dataloader.rb +38 -15
- data/lib/graphql/dataloader/null_dataloader.rb +55 -10
- data/lib/graphql/dataloader/source.rb +18 -6
- data/lib/graphql/dataloader.rb +110 -26
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/dig.rb +2 -1
- data/lib/graphql/execution/interpreter/resolve.rb +10 -16
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +58 -5
- data/lib/graphql/execution/interpreter/runtime.rb +229 -93
- data/lib/graphql/execution/interpreter.rb +15 -24
- data/lib/graphql/execution/multiplex.rb +7 -6
- data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
- data/lib/graphql/execution/next/load_argument_step.rb +60 -0
- data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
- data/lib/graphql/execution/next/runner.rb +389 -0
- data/lib/graphql/execution/next/selections_step.rb +37 -0
- data/lib/graphql/execution/next.rb +69 -0
- data/lib/graphql/execution.rb +1 -0
- data/lib/graphql/execution_error.rb +13 -10
- data/lib/graphql/introspection/directive_location_enum.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_name_error.rb +1 -1
- data/lib/graphql/invalid_null_error.rb +25 -16
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +16 -5
- data/lib/graphql/language/nodes.rb +8 -1
- data/lib/graphql/language/parser.rb +16 -8
- data/lib/graphql/language/static_visitor.rb +37 -33
- data/lib/graphql/language/visitor.rb +59 -55
- data/lib/graphql/language.rb +21 -12
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +32 -0
- data/lib/graphql/query/context.rb +6 -10
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +179 -0
- data/lib/graphql/query.rb +64 -64
- data/lib/graphql/railtie.rb +1 -1
- data/lib/graphql/schema/addition.rb +3 -1
- data/lib/graphql/schema/always_visible.rb +1 -0
- data/lib/graphql/schema/argument.rb +24 -8
- data/lib/graphql/schema/build_from_definition.rb +113 -54
- data/lib/graphql/schema/directive/flagged.rb +2 -0
- data/lib/graphql/schema/directive.rb +52 -2
- data/lib/graphql/schema/enum.rb +36 -1
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field/connection_extension.rb +15 -35
- data/lib/graphql/schema/field/scope_extension.rb +22 -13
- data/lib/graphql/schema/field.rb +101 -51
- data/lib/graphql/schema/field_extension.rb +33 -0
- data/lib/graphql/schema/input_object.rb +45 -38
- data/lib/graphql/schema/interface.rb +2 -1
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +56 -19
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +79 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +81 -5
- data/lib/graphql/schema/member/has_interfaces.rb +3 -3
- data/lib/graphql/schema/member/scoped.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +17 -3
- data/lib/graphql/schema/member.rb +6 -0
- data/lib/graphql/schema/object.rb +18 -8
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/resolver.rb +52 -6
- data/lib/graphql/schema/scalar.rb +1 -6
- data/lib/graphql/schema/subscription.rb +50 -4
- data/lib/graphql/schema/timeout.rb +19 -2
- data/lib/graphql/schema/validator/required_validator.rb +71 -14
- data/lib/graphql/schema/visibility/migration.rb +3 -2
- data/lib/graphql/schema/visibility/profile.rb +115 -23
- data/lib/graphql/schema/visibility.rb +49 -32
- data/lib/graphql/schema/warden.rb +23 -2
- data/lib/graphql/schema.rb +333 -68
- data/lib/graphql/static_validation/all_rules.rb +2 -2
- 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/not_single_subscription_error.rb +25 -0
- 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 +6 -2
- data/lib/graphql/static_validation/validator.rb +6 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- 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 +17 -11
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- 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/active_record_backend.rb +74 -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 +156 -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 +184 -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 +864 -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 +64 -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/type_kinds.rb +1 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +9 -7
- data/lib/graphql/types/relay/edge_behaviors.rb +5 -4
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +12 -31
- metadata +174 -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,7 @@ 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/
|
|
10
|
+
require "graphql/schema/ractor_shareable"
|
|
11
11
|
require "graphql/schema/timeout"
|
|
12
12
|
require "graphql/schema/type_expression"
|
|
13
13
|
require "graphql/schema/unique_within_type"
|
|
@@ -47,8 +47,6 @@ require "graphql/schema/relay_classic_mutation"
|
|
|
47
47
|
require "graphql/schema/subscription"
|
|
48
48
|
require "graphql/schema/visibility"
|
|
49
49
|
|
|
50
|
-
GraphQL.ensure_eager_load!
|
|
51
|
-
|
|
52
50
|
module GraphQL
|
|
53
51
|
# A GraphQL schema which may be queried with {GraphQL::Query}.
|
|
54
52
|
#
|
|
@@ -63,7 +61,7 @@ module GraphQL
|
|
|
63
61
|
# Any undiscoverable types may be provided with the `types` configuration.
|
|
64
62
|
#
|
|
65
63
|
# Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
|
|
66
|
-
# (These configurations can be overridden by specific calls to {Schema
|
|
64
|
+
# (These configurations can be overridden by specific calls to {Schema.execute})
|
|
67
65
|
#
|
|
68
66
|
# @example defining a schema
|
|
69
67
|
# class MySchema < GraphQL::Schema
|
|
@@ -75,6 +73,9 @@ module GraphQL
|
|
|
75
73
|
class Schema
|
|
76
74
|
extend GraphQL::Schema::Member::HasAstNode
|
|
77
75
|
extend GraphQL::Schema::FindInheritedValue
|
|
76
|
+
extend Autoload
|
|
77
|
+
|
|
78
|
+
autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
|
|
78
79
|
|
|
79
80
|
class DuplicateNamesError < GraphQL::Error
|
|
80
81
|
attr_reader :duplicated_name
|
|
@@ -111,7 +112,7 @@ module GraphQL
|
|
|
111
112
|
# @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
|
|
112
113
|
# @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
|
|
113
114
|
# @return [Class] the schema described by `document`
|
|
114
|
-
def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
|
|
115
|
+
def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {})
|
|
115
116
|
# If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
|
|
116
117
|
if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
|
|
117
118
|
GraphQL::Schema::BuildFromDefinition.from_definition_path(
|
|
@@ -120,6 +121,7 @@ module GraphQL
|
|
|
120
121
|
default_resolve: default_resolve,
|
|
121
122
|
parser: parser,
|
|
122
123
|
using: using,
|
|
124
|
+
base_types: base_types,
|
|
123
125
|
)
|
|
124
126
|
else
|
|
125
127
|
GraphQL::Schema::BuildFromDefinition.from_definition(
|
|
@@ -128,6 +130,7 @@ module GraphQL
|
|
|
128
130
|
default_resolve: default_resolve,
|
|
129
131
|
parser: parser,
|
|
130
132
|
using: using,
|
|
133
|
+
base_types: base_types,
|
|
131
134
|
)
|
|
132
135
|
end
|
|
133
136
|
end
|
|
@@ -146,10 +149,12 @@ module GraphQL
|
|
|
146
149
|
end
|
|
147
150
|
|
|
148
151
|
# @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
|
|
149
|
-
def default_trace_mode(new_mode =
|
|
150
|
-
if new_mode
|
|
152
|
+
def default_trace_mode(new_mode = NOT_CONFIGURED)
|
|
153
|
+
if !NOT_CONFIGURED.equal?(new_mode)
|
|
151
154
|
@default_trace_mode = new_mode
|
|
152
|
-
elsif defined?(@default_trace_mode)
|
|
155
|
+
elsif defined?(@default_trace_mode) &&
|
|
156
|
+
!@default_trace_mode.nil? # This `nil?` check seems necessary because of
|
|
157
|
+
# Ractors silently initializing @default_trace_mode somehow
|
|
153
158
|
@default_trace_mode
|
|
154
159
|
elsif superclass.respond_to?(:default_trace_mode)
|
|
155
160
|
superclass.default_trace_mode
|
|
@@ -166,9 +171,6 @@ module GraphQL
|
|
|
166
171
|
mods.each { |mod| new_class.include(mod) }
|
|
167
172
|
new_class.include(DefaultTraceClass)
|
|
168
173
|
trace_mode(:default, new_class)
|
|
169
|
-
backtrace_class = Class.new(new_class)
|
|
170
|
-
backtrace_class.include(GraphQL::Backtrace::Trace)
|
|
171
|
-
trace_mode(:default_backtrace, backtrace_class)
|
|
172
174
|
end
|
|
173
175
|
trace_class_for(:default, build: true)
|
|
174
176
|
end
|
|
@@ -215,11 +217,6 @@ module GraphQL
|
|
|
215
217
|
const_set(:DefaultTrace, Class.new(base_class) do
|
|
216
218
|
include DefaultTraceClass
|
|
217
219
|
end)
|
|
218
|
-
when :default_backtrace
|
|
219
|
-
schema_base_class = trace_class_for(:default, build: true)
|
|
220
|
-
const_set(:DefaultTraceBacktrace, Class.new(schema_base_class) do
|
|
221
|
-
include(GraphQL::Backtrace::Trace)
|
|
222
|
-
end)
|
|
223
220
|
else
|
|
224
221
|
# First, see if the superclass has a custom-defined class for this.
|
|
225
222
|
# Then, if it doesn't, use this class's default trace
|
|
@@ -255,7 +252,7 @@ module GraphQL
|
|
|
255
252
|
|
|
256
253
|
|
|
257
254
|
# Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
|
|
258
|
-
# @see
|
|
255
|
+
# @see #as_json Return a Hash representation of the schema
|
|
259
256
|
# @return [String]
|
|
260
257
|
def to_json(**args)
|
|
261
258
|
JSON.pretty_generate(as_json(**args))
|
|
@@ -263,8 +260,6 @@ module GraphQL
|
|
|
263
260
|
|
|
264
261
|
# Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
|
|
265
262
|
# @param context [Hash]
|
|
266
|
-
# @param only [<#call(member, ctx)>]
|
|
267
|
-
# @param except [<#call(member, ctx)>]
|
|
268
263
|
# @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response
|
|
269
264
|
# @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response
|
|
270
265
|
# @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives
|
|
@@ -335,10 +330,16 @@ module GraphQL
|
|
|
335
330
|
find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
|
|
336
331
|
end
|
|
337
332
|
|
|
333
|
+
attr_writer :null_context
|
|
334
|
+
|
|
335
|
+
def null_context
|
|
336
|
+
@null_context || GraphQL::Query::NullContext.instance
|
|
337
|
+
end
|
|
338
|
+
|
|
338
339
|
# Build a map of `{ name => type }` and return it
|
|
339
340
|
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
|
|
340
341
|
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
|
|
341
|
-
def types(context =
|
|
342
|
+
def types(context = null_context)
|
|
342
343
|
if use_visibility_profile?
|
|
343
344
|
types = Visibility::Profile.from_context(context, self)
|
|
344
345
|
return types.all_types_h
|
|
@@ -371,9 +372,10 @@ module GraphQL
|
|
|
371
372
|
# @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
|
|
372
373
|
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
|
373
374
|
# @return [Module, nil] A type, or nil if there's no type called `type_name`
|
|
374
|
-
def get_type(type_name, context =
|
|
375
|
+
def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
375
376
|
if use_visibility_profile
|
|
376
|
-
|
|
377
|
+
profile = Visibility::Profile.from_context(context, self)
|
|
378
|
+
return profile.type(type_name)
|
|
377
379
|
end
|
|
378
380
|
local_entry = own_types[type_name]
|
|
379
381
|
type_defn = case local_entry
|
|
@@ -621,7 +623,7 @@ module GraphQL
|
|
|
621
623
|
# @param use_visibility_profile Private, for migration to {Schema::Visibility}
|
|
622
624
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
|
623
625
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
|
624
|
-
def possible_types(type = nil, context =
|
|
626
|
+
def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
625
627
|
if use_visibility_profile
|
|
626
628
|
if type
|
|
627
629
|
return Visibility::Profile.from_context(context, self).possible_types(type)
|
|
@@ -705,7 +707,21 @@ module GraphQL
|
|
|
705
707
|
GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
|
|
706
708
|
end
|
|
707
709
|
|
|
708
|
-
def get_field(type_or_name, field_name, context =
|
|
710
|
+
def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
|
|
711
|
+
if use_visibility_profile
|
|
712
|
+
profile = Visibility::Profile.from_context(context, self)
|
|
713
|
+
parent_type = case type_or_name
|
|
714
|
+
when String
|
|
715
|
+
profile.type(type_or_name)
|
|
716
|
+
when Module
|
|
717
|
+
type_or_name
|
|
718
|
+
when LateBoundType
|
|
719
|
+
profile.type(type_or_name.name)
|
|
720
|
+
else
|
|
721
|
+
raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
|
|
722
|
+
end
|
|
723
|
+
return profile.field(parent_type, field_name)
|
|
724
|
+
end
|
|
709
725
|
parent_type = case type_or_name
|
|
710
726
|
when LateBoundType
|
|
711
727
|
get_type(type_or_name.name, context)
|
|
@@ -728,7 +744,7 @@ module GraphQL
|
|
|
728
744
|
end
|
|
729
745
|
end
|
|
730
746
|
|
|
731
|
-
def get_fields(type, context =
|
|
747
|
+
def get_fields(type, context = null_context)
|
|
732
748
|
type.fields(context)
|
|
733
749
|
end
|
|
734
750
|
|
|
@@ -829,13 +845,13 @@ module GraphQL
|
|
|
829
845
|
|
|
830
846
|
attr_writer :validate_timeout
|
|
831
847
|
|
|
832
|
-
def validate_timeout(new_validate_timeout =
|
|
833
|
-
if new_validate_timeout
|
|
848
|
+
def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
|
|
849
|
+
if !NOT_CONFIGURED.equal?(new_validate_timeout)
|
|
834
850
|
@validate_timeout = new_validate_timeout
|
|
835
851
|
elsif defined?(@validate_timeout)
|
|
836
852
|
@validate_timeout
|
|
837
853
|
else
|
|
838
|
-
find_inherited_value(:validate_timeout)
|
|
854
|
+
find_inherited_value(:validate_timeout) || 3
|
|
839
855
|
end
|
|
840
856
|
end
|
|
841
857
|
|
|
@@ -1072,6 +1088,18 @@ module GraphQL
|
|
|
1072
1088
|
end
|
|
1073
1089
|
end
|
|
1074
1090
|
|
|
1091
|
+
# @param context [GraphQL::Query::Context, nil]
|
|
1092
|
+
# @return [Logger] A logger to use for this context configuration, falling back to {.default_logger}
|
|
1093
|
+
def logger_for(context)
|
|
1094
|
+
if context && context[:logger] == false
|
|
1095
|
+
Logger.new(IO::NULL)
|
|
1096
|
+
elsif context && (l = context[:logger])
|
|
1097
|
+
l
|
|
1098
|
+
else
|
|
1099
|
+
default_logger
|
|
1100
|
+
end
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1075
1103
|
# @param new_context_class [Class<GraphQL::Query::Context>] A subclass to use when executing queries
|
|
1076
1104
|
def context_class(new_context_class = nil)
|
|
1077
1105
|
if new_context_class
|
|
@@ -1101,22 +1129,26 @@ module GraphQL
|
|
|
1101
1129
|
end
|
|
1102
1130
|
end
|
|
1103
1131
|
|
|
1104
|
-
NEW_HANDLER_HASH = ->(h, k) {
|
|
1105
|
-
h[k] = {
|
|
1106
|
-
class: k,
|
|
1107
|
-
handler: nil,
|
|
1108
|
-
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
1132
|
def error_handlers
|
|
1113
|
-
@error_handlers ||=
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1133
|
+
@error_handlers ||= begin
|
|
1134
|
+
new_handler_hash = ->(h, k) {
|
|
1135
|
+
h[k] = {
|
|
1136
|
+
class: k,
|
|
1137
|
+
handler: nil,
|
|
1138
|
+
subclass_handlers: Hash.new(&new_handler_hash),
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
{
|
|
1142
|
+
class: nil,
|
|
1143
|
+
handler: nil,
|
|
1144
|
+
subclass_handlers: Hash.new(&new_handler_hash),
|
|
1145
|
+
}
|
|
1146
|
+
end
|
|
1118
1147
|
end
|
|
1119
1148
|
|
|
1149
|
+
# @api private
|
|
1150
|
+
attr_accessor :using_backtrace
|
|
1151
|
+
|
|
1120
1152
|
# @api private
|
|
1121
1153
|
def handle_or_reraise(context, err)
|
|
1122
1154
|
handler = Execution::Errors.find_handler_for(self, err.class)
|
|
@@ -1130,6 +1162,10 @@ module GraphQL
|
|
|
1130
1162
|
end
|
|
1131
1163
|
handler[:handler].call(err, obj, args, context, field)
|
|
1132
1164
|
else
|
|
1165
|
+
if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
|
|
1166
|
+
err = GraphQL::Backtrace::TracedError.new(err, context)
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1133
1169
|
raise err
|
|
1134
1170
|
end
|
|
1135
1171
|
end
|
|
@@ -1164,7 +1200,7 @@ module GraphQL
|
|
|
1164
1200
|
# GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object.
|
|
1165
1201
|
#
|
|
1166
1202
|
# Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}.
|
|
1167
|
-
# But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument
|
|
1203
|
+
# 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.
|
|
1168
1204
|
#
|
|
1169
1205
|
# @example Returning a GraphQL type based on the object's class name
|
|
1170
1206
|
# class MySchema < GraphQL::Schema
|
|
@@ -1178,7 +1214,7 @@ module GraphQL
|
|
|
1178
1214
|
# @param context [GraphQL::Query::Context] The query context for the currently-executing query
|
|
1179
1215
|
# @return [Class<GraphQL::Schema::Object] The Object type definition to use for `obj`
|
|
1180
1216
|
def resolve_type(abstract_type, application_object, context)
|
|
1181
|
-
raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(abstract_type, application_object, context) must be implemented to use Union types, Interface types, or `
|
|
1217
|
+
raise GraphQL::RequiredImplementationMissingError, "#{self.name}.resolve_type(abstract_type, application_object, context) must be implemented to use Union types, Interface types, `loads:`, or `run_partials` (tried to resolve: #{abstract_type.name})"
|
|
1182
1218
|
end
|
|
1183
1219
|
# rubocop:enable Lint/DuplicateMethods
|
|
1184
1220
|
|
|
@@ -1198,6 +1234,7 @@ module GraphQL
|
|
|
1198
1234
|
vis = self.visibility
|
|
1199
1235
|
child_class.visibility = vis.dup_for(child_class)
|
|
1200
1236
|
end
|
|
1237
|
+
child_class.null_context = Query::NullContext.new(schema: child_class)
|
|
1201
1238
|
super
|
|
1202
1239
|
end
|
|
1203
1240
|
|
|
@@ -1219,7 +1256,7 @@ module GraphQL
|
|
|
1219
1256
|
|
|
1220
1257
|
# Return a stable ID string for `object` so that it can be refetched later, using {.object_from_id}.
|
|
1221
1258
|
#
|
|
1222
|
-
#
|
|
1259
|
+
# [GlobalID](https://github.com/rails/globalid) and [SQIDs](https://sqids.org/ruby) can both be used to create IDs.
|
|
1223
1260
|
#
|
|
1224
1261
|
# @example Using Rails's GlobalID to generate IDs
|
|
1225
1262
|
# def self.id_from_object(application_object, graphql_type, context)
|
|
@@ -1296,10 +1333,14 @@ module GraphQL
|
|
|
1296
1333
|
# @return [void]
|
|
1297
1334
|
# @raise [GraphQL::ExecutionError] to return this error to the client
|
|
1298
1335
|
# @raise [GraphQL::Error] to crash the query and raise a developer-facing error
|
|
1299
|
-
def type_error(type_error,
|
|
1336
|
+
def type_error(type_error, context)
|
|
1300
1337
|
case type_error
|
|
1301
1338
|
when GraphQL::InvalidNullError
|
|
1302
|
-
|
|
1339
|
+
execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
|
|
1340
|
+
execution_error.path = type_error.path || context[:current_path]
|
|
1341
|
+
|
|
1342
|
+
context.errors << execution_error
|
|
1343
|
+
execution_error
|
|
1303
1344
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1304
1345
|
raise type_error
|
|
1305
1346
|
when GraphQL::IntegerDecodingError
|
|
@@ -1307,7 +1348,7 @@ module GraphQL
|
|
|
1307
1348
|
end
|
|
1308
1349
|
end
|
|
1309
1350
|
|
|
1310
|
-
# A function to call when {
|
|
1351
|
+
# A function to call when {.execute} receives an invalid query string
|
|
1311
1352
|
#
|
|
1312
1353
|
# The default is to add the error to `context.errors`
|
|
1313
1354
|
# @param parse_err [GraphQL::ParseError] The error encountered during parsing
|
|
@@ -1321,6 +1362,24 @@ module GraphQL
|
|
|
1321
1362
|
lazy_methods.set(lazy_class, value_method)
|
|
1322
1363
|
end
|
|
1323
1364
|
|
|
1365
|
+
def uses_raw_value?
|
|
1366
|
+
!!@uses_raw_value
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
def uses_raw_value(new_val)
|
|
1370
|
+
@uses_raw_value = new_val
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def resolves_lazies?
|
|
1374
|
+
lazy_method_count = 0
|
|
1375
|
+
lazy_methods.each do |k, v|
|
|
1376
|
+
if !v.nil?
|
|
1377
|
+
lazy_method_count += 1
|
|
1378
|
+
end
|
|
1379
|
+
end
|
|
1380
|
+
lazy_method_count > 2
|
|
1381
|
+
end
|
|
1382
|
+
|
|
1324
1383
|
def instrument(instrument_step, instrumenter, options = {})
|
|
1325
1384
|
warn <<~WARN
|
|
1326
1385
|
Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
|
|
@@ -1367,6 +1426,16 @@ module GraphQL
|
|
|
1367
1426
|
}.freeze
|
|
1368
1427
|
end
|
|
1369
1428
|
|
|
1429
|
+
# @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema
|
|
1430
|
+
attr_accessor :detailed_trace
|
|
1431
|
+
|
|
1432
|
+
# @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex})
|
|
1433
|
+
# @return [Boolean] When `true`, save a detailed trace for this query.
|
|
1434
|
+
# @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true
|
|
1435
|
+
def detailed_trace?(query)
|
|
1436
|
+
raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition."
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1370
1439
|
def tracer(new_tracer, silence_deprecation_warning: false)
|
|
1371
1440
|
if !silence_deprecation_warning
|
|
1372
1441
|
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
|
@@ -1384,14 +1453,22 @@ module GraphQL
|
|
|
1384
1453
|
find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
|
|
1385
1454
|
end
|
|
1386
1455
|
|
|
1387
|
-
# Mix `trace_mod` into this schema's `Trace` class so that its methods
|
|
1388
|
-
#
|
|
1456
|
+
# Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
|
|
1457
|
+
#
|
|
1458
|
+
# You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`,
|
|
1459
|
+
# it will only run for queries with a matching `context[:trace_mode]`.
|
|
1460
|
+
#
|
|
1461
|
+
# Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration).
|
|
1462
|
+
#
|
|
1463
|
+
# @example Adding a trace in a special mode
|
|
1464
|
+
# # only runs when `query.context[:trace_mode]` is `:special`
|
|
1465
|
+
# trace_with SpecialTrace, mode: :special
|
|
1389
1466
|
#
|
|
1390
1467
|
# @param trace_mod [Module] A module that implements tracing methods
|
|
1391
1468
|
# @param mode [Symbol] Trace module will only be used for this trade mode
|
|
1392
1469
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1393
1470
|
# @return [void]
|
|
1394
|
-
# @see GraphQL::Tracing::Trace for available tracing methods
|
|
1471
|
+
# @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
|
|
1395
1472
|
def trace_with(trace_mod, mode: :default, **options)
|
|
1396
1473
|
if mode.is_a?(Array)
|
|
1397
1474
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
|
@@ -1441,29 +1518,36 @@ module GraphQL
|
|
|
1441
1518
|
#
|
|
1442
1519
|
# If no `mode:` is given, then {default_trace_mode} will be used.
|
|
1443
1520
|
#
|
|
1521
|
+
# If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then
|
|
1522
|
+
# DetailedTrace's mode will override the passed-in `mode`.
|
|
1523
|
+
#
|
|
1444
1524
|
# @param mode [Symbol] Trace modules for this trade mode will be included
|
|
1445
1525
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1446
1526
|
# @return [Tracing::Trace]
|
|
1447
1527
|
def new_trace(mode: nil, **options)
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
|
|
1458
|
-
options_trace_mode = :default
|
|
1459
|
-
:default_backtrace
|
|
1528
|
+
should_sample = if detailed_trace
|
|
1529
|
+
if (query = options[:query])
|
|
1530
|
+
detailed_trace?(query)
|
|
1531
|
+
elsif (multiplex = options[:multiplex])
|
|
1532
|
+
if multiplex.queries.length == 1
|
|
1533
|
+
detailed_trace?(multiplex.queries.first)
|
|
1534
|
+
else
|
|
1535
|
+
detailed_trace?(multiplex)
|
|
1536
|
+
end
|
|
1460
1537
|
end
|
|
1461
1538
|
else
|
|
1462
|
-
|
|
1539
|
+
false
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
if should_sample
|
|
1543
|
+
mode = detailed_trace.trace_mode
|
|
1544
|
+
else
|
|
1545
|
+
target = options[:query] || options[:multiplex]
|
|
1546
|
+
mode ||= target && target.context[:trace_mode]
|
|
1463
1547
|
end
|
|
1464
1548
|
|
|
1465
|
-
|
|
1466
|
-
base_trace_options = trace_options_for(
|
|
1549
|
+
trace_mode = mode || default_trace_mode
|
|
1550
|
+
base_trace_options = trace_options_for(trace_mode)
|
|
1467
1551
|
trace_options = base_trace_options.merge(options)
|
|
1468
1552
|
trace_class_for_mode = trace_class_for(trace_mode, build: true)
|
|
1469
1553
|
trace_class_for_mode.new(**trace_options)
|
|
@@ -1538,7 +1622,8 @@ module GraphQL
|
|
|
1538
1622
|
# @see {Query#initialize} for query keyword arguments
|
|
1539
1623
|
# @see {Execution::Multiplex#run_all} for multiplex keyword arguments
|
|
1540
1624
|
# @param queries [Array<Hash>] Keyword arguments for each query
|
|
1541
|
-
# @
|
|
1625
|
+
# @option kwargs [Hash] :context ({}) Multiplex-level context
|
|
1626
|
+
# @option kwargs [nil, Integer] :max_complexity (nil)
|
|
1542
1627
|
# @return [Array<GraphQL::Query::Result>] One result for each query in the input
|
|
1543
1628
|
def multiplex(queries, **kwargs)
|
|
1544
1629
|
GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs)
|
|
@@ -1603,7 +1688,7 @@ module GraphQL
|
|
|
1603
1688
|
end
|
|
1604
1689
|
end
|
|
1605
1690
|
|
|
1606
|
-
# @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {
|
|
1691
|
+
# @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {.lazy_resolve}.
|
|
1607
1692
|
def lazy_method_name(obj)
|
|
1608
1693
|
lazy_methods.get(obj)
|
|
1609
1694
|
end
|
|
@@ -1641,6 +1726,187 @@ module GraphQL
|
|
|
1641
1726
|
end
|
|
1642
1727
|
end
|
|
1643
1728
|
|
|
1729
|
+
|
|
1730
|
+
# This setting controls how GraphQL-Ruby handles empty selections on Union types.
|
|
1731
|
+
#
|
|
1732
|
+
# To opt into future, spec-compliant behavior where these selections are rejected, set this to `false`.
|
|
1733
|
+
#
|
|
1734
|
+
# If you need to support previous, non-spec behavior which allowed selecting union fields
|
|
1735
|
+
# but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
|
|
1736
|
+
#
|
|
1737
|
+
# If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
|
|
1738
|
+
# with that kind of selections. You must implement that method
|
|
1739
|
+
# @param new_value [Boolean]
|
|
1740
|
+
# @return [true, false, nil]
|
|
1741
|
+
def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED)
|
|
1742
|
+
if NOT_CONFIGURED.equal?(new_value)
|
|
1743
|
+
if defined?(@allow_legacy_invalid_empty_selections_on_union)
|
|
1744
|
+
@allow_legacy_invalid_empty_selections_on_union
|
|
1745
|
+
else
|
|
1746
|
+
find_inherited_value(:allow_legacy_invalid_empty_selections_on_union)
|
|
1747
|
+
end
|
|
1748
|
+
else
|
|
1749
|
+
@allow_legacy_invalid_empty_selections_on_union = new_value
|
|
1750
|
+
end
|
|
1751
|
+
end
|
|
1752
|
+
|
|
1753
|
+
# This method is called during validation when a previously-allowed, but non-spec
|
|
1754
|
+
# query is encountered where a union field has no child selections on it.
|
|
1755
|
+
#
|
|
1756
|
+
# If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
|
|
1757
|
+
#
|
|
1758
|
+
# You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
|
|
1759
|
+
# to log the violation so that you can contact clients and notify them about changing their queries.
|
|
1760
|
+
# Then return a suitable value to tell GraphQL-Ruby how to continue.
|
|
1761
|
+
# @param query [GraphQL::Query]
|
|
1762
|
+
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
|
1763
|
+
# @return [String] A validation error to return for this query
|
|
1764
|
+
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
|
1765
|
+
def legacy_invalid_empty_selections_on_union(query)
|
|
1766
|
+
raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
|
|
1767
|
+
end
|
|
1768
|
+
|
|
1769
|
+
# This method is called during validation when a previously-allowed, but non-spec
|
|
1770
|
+
# query is encountered where a union field has no child selections on it.
|
|
1771
|
+
#
|
|
1772
|
+
# You should implement this method to log the violation so that you can contact clients
|
|
1773
|
+
# and notify them about changing their queries. Then return a suitable value to
|
|
1774
|
+
# tell GraphQL-Ruby how to continue.
|
|
1775
|
+
# @param query [GraphQL::Query]
|
|
1776
|
+
# @param type [Module] A GraphQL type definition
|
|
1777
|
+
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
|
1778
|
+
# @return [String] A validation error to return for this query
|
|
1779
|
+
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
|
1780
|
+
def legacy_invalid_empty_selections_on_union_with_type(query, type)
|
|
1781
|
+
legacy_invalid_empty_selections_on_union(query)
|
|
1782
|
+
end
|
|
1783
|
+
|
|
1784
|
+
# This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
|
|
1785
|
+
# don't match.
|
|
1786
|
+
#
|
|
1787
|
+
# When set to `false`, GraphQL-Ruby will reject those queries with a validation error (as per the GraphQL spec).
|
|
1788
|
+
#
|
|
1789
|
+
# When set to `true`, GraphQL-Ruby will call {.legacy_invalid_return_type_conflicts} when the scenario is encountered.
|
|
1790
|
+
#
|
|
1791
|
+
# @param new_value [Boolean] `true` permits the legacy behavior, `false` rejects it.
|
|
1792
|
+
# @return [true, false, nil]
|
|
1793
|
+
def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED)
|
|
1794
|
+
if NOT_CONFIGURED.equal?(new_value)
|
|
1795
|
+
if defined?(@allow_legacy_invalid_return_type_conflicts)
|
|
1796
|
+
@allow_legacy_invalid_return_type_conflicts
|
|
1797
|
+
else
|
|
1798
|
+
find_inherited_value(:allow_legacy_invalid_return_type_conflicts)
|
|
1799
|
+
end
|
|
1800
|
+
else
|
|
1801
|
+
@allow_legacy_invalid_return_type_conflicts = new_value
|
|
1802
|
+
end
|
|
1803
|
+
end
|
|
1804
|
+
|
|
1805
|
+
# This method is called when the query contains fields which don't contain matching scalar types.
|
|
1806
|
+
# This was previously allowed by GraphQL-Ruby but it's a violation of the GraphQL spec.
|
|
1807
|
+
#
|
|
1808
|
+
# You should implement this method to log the violation so that you observe usage of these fields.
|
|
1809
|
+
# Fixing this scenario might mean adding new fields, and telling clients to use those fields.
|
|
1810
|
+
# (Changing the field return type would be a breaking change, but if it works for your client use cases,
|
|
1811
|
+
# that might work, too.)
|
|
1812
|
+
#
|
|
1813
|
+
# @param query [GraphQL::Query]
|
|
1814
|
+
# @param type1 [Module] A GraphQL type definition
|
|
1815
|
+
# @param type2 [Module] A GraphQL type definition
|
|
1816
|
+
# @param node1 [GraphQL::Language::Nodes::Field] This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting.
|
|
1817
|
+
# @param node2 [GraphQL::Language::Nodes::Field] The other node recognized as conflicting.
|
|
1818
|
+
# @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
|
|
1819
|
+
# @return [String] A validation error to return for this query
|
|
1820
|
+
# @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
|
|
1821
|
+
def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2)
|
|
1822
|
+
raise "Implement #{self}.legacy_invalid_return_type_conflicts to handle this invalid selection"
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
# The legacy complexity implementation included several bugs:
|
|
1826
|
+
#
|
|
1827
|
+
# - In some cases, it used the lexically _last_ field to determine a cost, instead of calculating the maximum among selections
|
|
1828
|
+
# - In some cases, it called field complexity hooks repeatedly (when it should have only called them once)
|
|
1829
|
+
#
|
|
1830
|
+
# The future implementation may produce higher total complexity scores, so it's not active by default yet. You can opt into
|
|
1831
|
+
# the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with {.complexity_cost_calculation_mode_for}.
|
|
1832
|
+
#
|
|
1833
|
+
# The legacy mode is currently maintained alongside the future one, but it will be removed in a future GraphQL-Ruby version.
|
|
1834
|
+
#
|
|
1835
|
+
# If you choose `:compare`, you must also implement {.legacy_complexity_cost_calculation_mismatch} to handle the input somehow.
|
|
1836
|
+
#
|
|
1837
|
+
# @example Opting into the future calculation mode
|
|
1838
|
+
# complexity_cost_calculation_mode(:future)
|
|
1839
|
+
#
|
|
1840
|
+
# @example Choosing the legacy mode (which will work until that mode is removed...)
|
|
1841
|
+
# complexity_cost_calculation_mode(:legacy)
|
|
1842
|
+
#
|
|
1843
|
+
# @example Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match:
|
|
1844
|
+
# complexity_cost_calculation_mode(:compare)
|
|
1845
|
+
def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED)
|
|
1846
|
+
if NOT_CONFIGURED.equal?(new_mode)
|
|
1847
|
+
if defined?(@complexity_cost_calculation_mode)
|
|
1848
|
+
@complexity_cost_calculation_mode
|
|
1849
|
+
else
|
|
1850
|
+
find_inherited_value(:complexity_cost_calculation_mode)
|
|
1851
|
+
end
|
|
1852
|
+
else
|
|
1853
|
+
@complexity_cost_calculation_mode = new_mode
|
|
1854
|
+
end
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
# Implement this method to produce a per-query complexity cost calculation mode. (Technically, it's per-multiplex.)
|
|
1858
|
+
#
|
|
1859
|
+
# This is a way to check the compatibility of queries coming to your API without adding overhead of running `:compare`
|
|
1860
|
+
# for every query. You could sample traffic, turn it off/on with feature flags, or anything else.
|
|
1861
|
+
#
|
|
1862
|
+
# @example Sampling traffic
|
|
1863
|
+
# def self.complexity_cost_calculation_mode_for(_context)
|
|
1864
|
+
# if rand < 0.1 # 10% of the time
|
|
1865
|
+
# :compare
|
|
1866
|
+
# else
|
|
1867
|
+
# :legacy
|
|
1868
|
+
# end
|
|
1869
|
+
# end
|
|
1870
|
+
#
|
|
1871
|
+
# @example Using a feature flag to manage future mode
|
|
1872
|
+
# def complexity_cost_calculation_mode_for(context)
|
|
1873
|
+
# current_user = context[:current_user]
|
|
1874
|
+
# if Flipper.enabled?(:future_complexity_cost, current_user)
|
|
1875
|
+
# :future
|
|
1876
|
+
# elsif rand < 0.5 # 50%
|
|
1877
|
+
# :compare
|
|
1878
|
+
# else
|
|
1879
|
+
# :legacy
|
|
1880
|
+
# end
|
|
1881
|
+
# end
|
|
1882
|
+
#
|
|
1883
|
+
# @param multiplex_context [Hash] The context for the currently-running {Execution::Multiplex} (which contains one or more queries)
|
|
1884
|
+
# @return [:future] Use the new calculation algorithm -- may be higher than `:legacy`
|
|
1885
|
+
# @return [:legacy] Use the legacy calculation algorithm, warts and all
|
|
1886
|
+
# @return [:compare] Run both algorithms and call {.legacy_complexity_cost_calculation_mismatch} if they don't match
|
|
1887
|
+
def complexity_cost_calculation_mode_for(multiplex_context)
|
|
1888
|
+
complexity_cost_calculation_mode
|
|
1889
|
+
end
|
|
1890
|
+
|
|
1891
|
+
# Implement this method in your schema to handle mismatches when `:compare` is used.
|
|
1892
|
+
#
|
|
1893
|
+
# @example Logging the mismatch
|
|
1894
|
+
# def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost)
|
|
1895
|
+
# client_id = multiplex.context[:api_client].id
|
|
1896
|
+
# operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ")
|
|
1897
|
+
# Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names })
|
|
1898
|
+
# legacy_cost
|
|
1899
|
+
# end
|
|
1900
|
+
# @see Query::Context#add_error Adding an error to the response to notify the client
|
|
1901
|
+
# @see Query::Context#response_extensions Adding key-value pairs to the response `"extensions" => { ... }`
|
|
1902
|
+
# @param multiplex [GraphQL::Execution::Multiplex]
|
|
1903
|
+
# @param future_complexity_cost [Integer]
|
|
1904
|
+
# @param legacy_complexity_cost [Integer]
|
|
1905
|
+
# @return [Integer] the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`)
|
|
1906
|
+
def legacy_complexity_cost_calculation_mismatch(multiplex, future_complexity_cost, legacy_complexity_cost)
|
|
1907
|
+
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"
|
|
1908
|
+
end
|
|
1909
|
+
|
|
1644
1910
|
private
|
|
1645
1911
|
|
|
1646
1912
|
def add_trace_options_for(mode, new_options)
|
|
@@ -1805,6 +2071,5 @@ module GraphQL
|
|
|
1805
2071
|
end
|
|
1806
2072
|
end
|
|
1807
2073
|
|
|
1808
|
-
require "graphql/schema/built_in_types"
|
|
1809
2074
|
require "graphql/schema/loader"
|
|
1810
2075
|
require "graphql/schema/printer"
|
|
@@ -34,9 +34,9 @@ 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
|
+
].freeze
|
|
41
41
|
end
|
|
42
42
|
end
|