graphql 2.4.5 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/analyzer.rb +2 -1
- 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 +5 -0
- 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 +17 -5
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/source.rb +2 -2
- data/lib/graphql/dataloader.rb +37 -5
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
- data/lib/graphql/execution/interpreter/runtime.rb +71 -34
- data/lib/graphql/execution/interpreter.rb +12 -3
- 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/lexer.rb +7 -3
- data/lib/graphql/language/nodes.rb +3 -0
- data/lib/graphql/language/parser.rb +15 -8
- data/lib/graphql/language/static_visitor.rb +37 -33
- data/lib/graphql/language/visitor.rb +59 -55
- data/lib/graphql/query.rb +7 -13
- data/lib/graphql/railtie.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.rb +1 -1
- data/lib/graphql/schema/enum.rb +36 -1
- data/lib/graphql/schema/enum_value.rb +1 -1
- data/lib/graphql/schema/field.rb +2 -2
- data/lib/graphql/schema/input_object.rb +16 -16
- data/lib/graphql/schema/interface.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +13 -5
- data/lib/graphql/schema/member/has_dataloader.rb +60 -0
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +1 -1
- data/lib/graphql/schema/member/has_interfaces.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 +17 -8
- data/lib/graphql/schema/resolver.rb +6 -5
- data/lib/graphql/schema/subscription.rb +50 -4
- data/lib/graphql/schema/validator/required_validator.rb +23 -6
- data/lib/graphql/schema/visibility/migration.rb +1 -0
- data/lib/graphql/schema/visibility/profile.rb +11 -5
- data/lib/graphql/schema/visibility.rb +14 -9
- data/lib/graphql/schema/warden.rb +14 -1
- data/lib/graphql/schema.rb +63 -35
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- 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/validator.rb +6 -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/testing/helpers.rb +5 -2
- 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 +285 -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 +63 -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 +1 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +4 -29
- metadata +144 -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
|
@@ -13,6 +13,10 @@ module GraphQL
|
|
|
13
13
|
# @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
|
|
14
14
|
# @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
|
|
15
15
|
def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails) ? Rails.env.production? : nil), migration_errors: false)
|
|
16
|
+
profiles&.each { |name, ctx|
|
|
17
|
+
ctx[:visibility_profile] = name
|
|
18
|
+
ctx.freeze
|
|
19
|
+
}
|
|
16
20
|
schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
|
|
17
21
|
if preload
|
|
18
22
|
schema.visibility.preload
|
|
@@ -81,8 +85,7 @@ module GraphQL
|
|
|
81
85
|
types_to_visit.compact!
|
|
82
86
|
ensure_all_loaded(types_to_visit)
|
|
83
87
|
@profiles.each do |profile_name, example_ctx|
|
|
84
|
-
|
|
85
|
-
prof = profile_for(example_ctx, profile_name)
|
|
88
|
+
prof = profile_for(example_ctx)
|
|
86
89
|
prof.all_types # force loading
|
|
87
90
|
end
|
|
88
91
|
end
|
|
@@ -145,7 +148,7 @@ module GraphQL
|
|
|
145
148
|
|
|
146
149
|
attr_reader :cached_profiles
|
|
147
150
|
|
|
148
|
-
def profile_for(context, visibility_profile)
|
|
151
|
+
def profile_for(context, visibility_profile = context[:visibility_profile])
|
|
149
152
|
if !@profiles.empty?
|
|
150
153
|
if visibility_profile.nil?
|
|
151
154
|
if @dynamic
|
|
@@ -160,7 +163,8 @@ module GraphQL
|
|
|
160
163
|
elsif !@profiles.include?(visibility_profile)
|
|
161
164
|
raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition."
|
|
162
165
|
else
|
|
163
|
-
@
|
|
166
|
+
profile_ctx = @profiles[visibility_profile]
|
|
167
|
+
@cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema)
|
|
164
168
|
end
|
|
165
169
|
elsif context.is_a?(Query::NullContext)
|
|
166
170
|
top_level_profile
|
|
@@ -222,7 +226,9 @@ module GraphQL
|
|
|
222
226
|
elsif member.respond_to?(:interface_type_memberships)
|
|
223
227
|
member.interface_type_memberships.each do |itm|
|
|
224
228
|
@all_references[itm.abstract_type] << member
|
|
225
|
-
|
|
229
|
+
# `itm.object_type` may not actually be `member` if this implementation
|
|
230
|
+
# is inherited from a superclass
|
|
231
|
+
@interface_type_memberships[itm.abstract_type] << [itm, member]
|
|
226
232
|
end
|
|
227
233
|
elsif member < GraphQL::Schema::Union
|
|
228
234
|
@unions_for_references << member
|
|
@@ -271,13 +277,12 @@ module GraphQL
|
|
|
271
277
|
|
|
272
278
|
# TODO: somehow don't iterate over all these,
|
|
273
279
|
# only the ones that may have been modified
|
|
274
|
-
@interface_type_memberships.each do |int_type,
|
|
280
|
+
@interface_type_memberships.each do |int_type, type_membership_pairs|
|
|
275
281
|
referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
|
|
276
282
|
if !referers.empty?
|
|
277
|
-
|
|
278
|
-
implementor_type = type_membership.object_type
|
|
283
|
+
type_membership_pairs.each do |(type_membership, impl_type)|
|
|
279
284
|
# Add new items only:
|
|
280
|
-
@all_references[
|
|
285
|
+
@all_references[impl_type] |= referers
|
|
281
286
|
end
|
|
282
287
|
end
|
|
283
288
|
end
|
|
@@ -72,6 +72,7 @@ module GraphQL
|
|
|
72
72
|
def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
|
|
73
73
|
def arguments(owner, ctx); owner.arguments(ctx); end
|
|
74
74
|
def loadable?(type, ctx); type.visible?(ctx); end
|
|
75
|
+
def loadable_possible_types(type, ctx); type.possible_types(ctx); end
|
|
75
76
|
def visibility_profile
|
|
76
77
|
@visibility_profile ||= Warden::VisibilityProfile.new(self)
|
|
77
78
|
end
|
|
@@ -106,6 +107,7 @@ module GraphQL
|
|
|
106
107
|
def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
|
|
107
108
|
def reachable_type?(type_name); true; end
|
|
108
109
|
def loadable?(type, _ctx); true; end
|
|
110
|
+
def loadable_possible_types(union_type, _ctx); union_type.possible_types; end
|
|
109
111
|
def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
|
|
110
112
|
def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
|
|
111
113
|
def interfaces(obj_type); obj_type.interfaces; end
|
|
@@ -180,6 +182,10 @@ module GraphQL
|
|
|
180
182
|
@warden.loadable?(t, ctx)
|
|
181
183
|
end
|
|
182
184
|
|
|
185
|
+
def loadable_possible_types(t, ctx)
|
|
186
|
+
@warden.loadable_possible_types(t, ctx)
|
|
187
|
+
end
|
|
188
|
+
|
|
183
189
|
def reachable_type?(type_name)
|
|
184
190
|
!!@warden.reachable_type?(type_name)
|
|
185
191
|
end
|
|
@@ -204,7 +210,7 @@ module GraphQL
|
|
|
204
210
|
@visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
|
|
205
211
|
@visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
|
|
206
212
|
@visible_and_reachable_type = @unions = @unfiltered_interfaces =
|
|
207
|
-
@reachable_type_set = @visibility_profile =
|
|
213
|
+
@reachable_type_set = @visibility_profile = @loadable_possible_types =
|
|
208
214
|
nil
|
|
209
215
|
@skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
|
|
210
216
|
end
|
|
@@ -229,6 +235,13 @@ module GraphQL
|
|
|
229
235
|
!reachable_type_set.include?(type) && visible_type?(type)
|
|
230
236
|
end
|
|
231
237
|
|
|
238
|
+
def loadable_possible_types(union_type, _ctx)
|
|
239
|
+
@loadable_possible_types ||= read_through do |t|
|
|
240
|
+
t.possible_types # unfiltered
|
|
241
|
+
end
|
|
242
|
+
@loadable_possible_types[union_type]
|
|
243
|
+
end
|
|
244
|
+
|
|
232
245
|
# @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
|
|
233
246
|
def get_type(type_name)
|
|
234
247
|
@visible_types ||= read_through do |name|
|
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"
|
|
@@ -47,8 +46,6 @@ require "graphql/schema/relay_classic_mutation"
|
|
|
47
46
|
require "graphql/schema/subscription"
|
|
48
47
|
require "graphql/schema/visibility"
|
|
49
48
|
|
|
50
|
-
GraphQL.ensure_eager_load!
|
|
51
|
-
|
|
52
49
|
module GraphQL
|
|
53
50
|
# A GraphQL schema which may be queried with {GraphQL::Query}.
|
|
54
51
|
#
|
|
@@ -75,6 +72,9 @@ module GraphQL
|
|
|
75
72
|
class Schema
|
|
76
73
|
extend GraphQL::Schema::Member::HasAstNode
|
|
77
74
|
extend GraphQL::Schema::FindInheritedValue
|
|
75
|
+
extend Autoload
|
|
76
|
+
|
|
77
|
+
autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types"
|
|
78
78
|
|
|
79
79
|
class DuplicateNamesError < GraphQL::Error
|
|
80
80
|
attr_reader :duplicated_name
|
|
@@ -111,7 +111,7 @@ module GraphQL
|
|
|
111
111
|
# @param parser [Object] An object for handling definition string parsing (must respond to `parse`)
|
|
112
112
|
# @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
|
|
113
113
|
# @return [Class] the schema described by `document`
|
|
114
|
-
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: {})
|
|
115
115
|
# If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
|
|
116
116
|
if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
|
|
117
117
|
GraphQL::Schema::BuildFromDefinition.from_definition_path(
|
|
@@ -120,6 +120,7 @@ module GraphQL
|
|
|
120
120
|
default_resolve: default_resolve,
|
|
121
121
|
parser: parser,
|
|
122
122
|
using: using,
|
|
123
|
+
base_types: base_types,
|
|
123
124
|
)
|
|
124
125
|
else
|
|
125
126
|
GraphQL::Schema::BuildFromDefinition.from_definition(
|
|
@@ -128,6 +129,7 @@ module GraphQL
|
|
|
128
129
|
default_resolve: default_resolve,
|
|
129
130
|
parser: parser,
|
|
130
131
|
using: using,
|
|
132
|
+
base_types: base_types,
|
|
131
133
|
)
|
|
132
134
|
end
|
|
133
135
|
end
|
|
@@ -166,9 +168,6 @@ module GraphQL
|
|
|
166
168
|
mods.each { |mod| new_class.include(mod) }
|
|
167
169
|
new_class.include(DefaultTraceClass)
|
|
168
170
|
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
171
|
end
|
|
173
172
|
trace_class_for(:default, build: true)
|
|
174
173
|
end
|
|
@@ -215,11 +214,6 @@ module GraphQL
|
|
|
215
214
|
const_set(:DefaultTrace, Class.new(base_class) do
|
|
216
215
|
include DefaultTraceClass
|
|
217
216
|
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
217
|
else
|
|
224
218
|
# First, see if the superclass has a custom-defined class for this.
|
|
225
219
|
# Then, if it doesn't, use this class's default trace
|
|
@@ -829,13 +823,13 @@ module GraphQL
|
|
|
829
823
|
|
|
830
824
|
attr_writer :validate_timeout
|
|
831
825
|
|
|
832
|
-
def validate_timeout(new_validate_timeout =
|
|
833
|
-
if new_validate_timeout
|
|
826
|
+
def validate_timeout(new_validate_timeout = NOT_CONFIGURED)
|
|
827
|
+
if !NOT_CONFIGURED.equal?(new_validate_timeout)
|
|
834
828
|
@validate_timeout = new_validate_timeout
|
|
835
829
|
elsif defined?(@validate_timeout)
|
|
836
830
|
@validate_timeout
|
|
837
831
|
else
|
|
838
|
-
find_inherited_value(:validate_timeout)
|
|
832
|
+
find_inherited_value(:validate_timeout) || 3
|
|
839
833
|
end
|
|
840
834
|
end
|
|
841
835
|
|
|
@@ -1117,6 +1111,9 @@ module GraphQL
|
|
|
1117
1111
|
}
|
|
1118
1112
|
end
|
|
1119
1113
|
|
|
1114
|
+
# @api private
|
|
1115
|
+
attr_accessor :using_backtrace
|
|
1116
|
+
|
|
1120
1117
|
# @api private
|
|
1121
1118
|
def handle_or_reraise(context, err)
|
|
1122
1119
|
handler = Execution::Errors.find_handler_for(self, err.class)
|
|
@@ -1130,6 +1127,10 @@ module GraphQL
|
|
|
1130
1127
|
end
|
|
1131
1128
|
handler[:handler].call(err, obj, args, context, field)
|
|
1132
1129
|
else
|
|
1130
|
+
if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError)
|
|
1131
|
+
err = GraphQL::Backtrace::TracedError.new(err, context)
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1133
1134
|
raise err
|
|
1134
1135
|
end
|
|
1135
1136
|
end
|
|
@@ -1299,7 +1300,10 @@ module GraphQL
|
|
|
1299
1300
|
def type_error(type_error, ctx)
|
|
1300
1301
|
case type_error
|
|
1301
1302
|
when GraphQL::InvalidNullError
|
|
1302
|
-
|
|
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
|
|
1303
1307
|
when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
|
|
1304
1308
|
raise type_error
|
|
1305
1309
|
when GraphQL::IntegerDecodingError
|
|
@@ -1367,6 +1371,16 @@ module GraphQL
|
|
|
1367
1371
|
}.freeze
|
|
1368
1372
|
end
|
|
1369
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
|
+
|
|
1370
1384
|
def tracer(new_tracer, silence_deprecation_warning: false)
|
|
1371
1385
|
if !silence_deprecation_warning
|
|
1372
1386
|
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
|
@@ -1384,14 +1398,22 @@ module GraphQL
|
|
|
1384
1398
|
find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
|
|
1385
1399
|
end
|
|
1386
1400
|
|
|
1387
|
-
# Mix `trace_mod` into this schema's `Trace` class so that its methods
|
|
1388
|
-
#
|
|
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
|
|
1389
1411
|
#
|
|
1390
1412
|
# @param trace_mod [Module] A module that implements tracing methods
|
|
1391
1413
|
# @param mode [Symbol] Trace module will only be used for this trade mode
|
|
1392
1414
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1393
1415
|
# @return [void]
|
|
1394
|
-
# @see GraphQL::Tracing::Trace for available tracing methods
|
|
1416
|
+
# @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods
|
|
1395
1417
|
def trace_with(trace_mod, mode: :default, **options)
|
|
1396
1418
|
if mode.is_a?(Array)
|
|
1397
1419
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
|
@@ -1441,29 +1463,36 @@ module GraphQL
|
|
|
1441
1463
|
#
|
|
1442
1464
|
# If no `mode:` is given, then {default_trace_mode} will be used.
|
|
1443
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
|
+
#
|
|
1444
1469
|
# @param mode [Symbol] Trace modules for this trade mode will be included
|
|
1445
1470
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
|
1446
1471
|
# @return [Tracing::Trace]
|
|
1447
1472
|
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
|
|
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
|
|
1460
1482
|
end
|
|
1461
1483
|
else
|
|
1462
|
-
|
|
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]
|
|
1463
1492
|
end
|
|
1464
1493
|
|
|
1465
|
-
|
|
1466
|
-
base_trace_options = trace_options_for(
|
|
1494
|
+
trace_mode = mode || default_trace_mode
|
|
1495
|
+
base_trace_options = trace_options_for(trace_mode)
|
|
1467
1496
|
trace_options = base_trace_options.merge(options)
|
|
1468
1497
|
trace_class_for_mode = trace_class_for(trace_mode, build: true)
|
|
1469
1498
|
trace_class_for_mode.new(**trace_options)
|
|
@@ -1805,6 +1834,5 @@ module GraphQL
|
|
|
1805
1834
|
end
|
|
1806
1835
|
end
|
|
1807
1836
|
|
|
1808
|
-
require "graphql/schema/built_in_types"
|
|
1809
1837
|
require "graphql/schema/loader"
|
|
1810
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
|
]
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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.
|
|
@@ -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,18 +43,21 @@ 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)
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "graphql/tracing/notifications_trace"
|
|
4
4
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
module Tracing
|
|
7
|
-
# This implementation forwards events to ActiveSupport::Notifications
|
|
8
|
-
#
|
|
7
|
+
# This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
|
|
8
|
+
#
|
|
9
|
+
# @example Sending execution events to ActiveSupport::Notifications
|
|
10
|
+
# class MySchema < GraphQL::Schema
|
|
11
|
+
# trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# @example Subscribing to GraphQL events with ActiveSupport::Notifications
|
|
15
|
+
# ActiveSupport::Notifications.subscribe(/graphql/) do |event|
|
|
16
|
+
# pp event.name
|
|
17
|
+
# pp event.payload
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
9
20
|
module ActiveSupportNotificationsTrace
|
|
10
21
|
include NotificationsTrace
|
|
11
22
|
def initialize(engine: ActiveSupport::Notifications, **rest)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "graphql/tracing/platform_trace"
|
|
4
|
+
|
|
3
5
|
module GraphQL
|
|
4
6
|
module Tracing
|
|
5
7
|
|
|
@@ -20,14 +22,18 @@ module GraphQL
|
|
|
20
22
|
# These GraphQL events will show up as 'graphql.execute' spans
|
|
21
23
|
EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
|
|
22
24
|
|
|
25
|
+
|
|
23
26
|
# During auto-instrumentation this version of AppOpticsTracing is compared
|
|
24
27
|
# with the version provided in the appoptics_apm gem, so that the newer
|
|
25
28
|
# version of the class can be used
|
|
26
29
|
|
|
30
|
+
|
|
27
31
|
def self.version
|
|
28
32
|
Gem::Version.new('1.0.0')
|
|
29
33
|
end
|
|
30
34
|
|
|
35
|
+
# rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time
|
|
36
|
+
|
|
31
37
|
[
|
|
32
38
|
'lex',
|
|
33
39
|
'parse',
|
|
@@ -55,6 +61,8 @@ module GraphQL
|
|
|
55
61
|
RUBY
|
|
56
62
|
end
|
|
57
63
|
|
|
64
|
+
# rubocop:enable Development/NoEvalCop
|
|
65
|
+
|
|
58
66
|
def execute_field(query:, field:, ast_node:, arguments:, object:)
|
|
59
67
|
return_type = field.type.unwrap
|
|
60
68
|
trace_field = if return_type.kind.scalar? || return_type.kind.enum?
|
|
@@ -81,7 +89,7 @@ module GraphQL
|
|
|
81
89
|
end
|
|
82
90
|
end
|
|
83
91
|
|
|
84
|
-
def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
|
|
92
|
+
def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) # rubocop:disable Development/TraceCallsSuperCop
|
|
85
93
|
execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
|
|
86
94
|
end
|
|
87
95
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "graphql/tracing/platform_tracing"
|
|
4
|
+
|
|
3
5
|
module GraphQL
|
|
4
6
|
module Tracing
|
|
5
7
|
|
|
@@ -20,6 +22,11 @@ module GraphQL
|
|
|
20
22
|
# These GraphQL events will show up as 'graphql.execute' spans
|
|
21
23
|
EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
|
|
22
24
|
|
|
25
|
+
def initialize(...)
|
|
26
|
+
warn "GraphQL::Tracing::AppOptics tracing is deprecated; update to SolarWindsAPM instead, which uses OpenTelemetry."
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
23
30
|
# During auto-instrumentation this version of AppOpticsTracing is compared
|
|
24
31
|
# with the version provided in the appoptics_apm gem, so that the newer
|
|
25
32
|
# version of the class can be used
|