graphql 2.0.28 → 2.2.11
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/install/templates/base_mutation.erb +2 -0
- data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
- data/lib/generators/graphql/install_generator.rb +3 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_connection.erb +2 -0
- data/lib/generators/graphql/templates/base_edge.erb +2 -0
- data/lib/generators/graphql/templates/base_enum.erb +2 -0
- data/lib/generators/graphql/templates/base_field.erb +2 -0
- data/lib/generators/graphql/templates/base_input_object.erb +2 -0
- data/lib/generators/graphql/templates/base_interface.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -0
- data/lib/generators/graphql/templates/base_resolver.erb +6 -0
- data/lib/generators/graphql/templates/base_scalar.erb +2 -0
- data/lib/generators/graphql/templates/base_union.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
- data/lib/generators/graphql/templates/loader.erb +2 -0
- data/lib/generators/graphql/templates/mutation.erb +2 -0
- data/lib/generators/graphql/templates/node_type.erb +2 -0
- data/lib/generators/graphql/templates/query_type.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +2 -0
- data/lib/graphql/analysis/ast/analyzer.rb +7 -0
- data/lib/graphql/analysis/ast/field_usage.rb +32 -7
- data/lib/graphql/analysis/ast/query_complexity.rb +80 -128
- data/lib/graphql/analysis/ast/query_depth.rb +7 -2
- data/lib/graphql/analysis/ast/visitor.rb +2 -2
- data/lib/graphql/analysis/ast.rb +21 -11
- data/lib/graphql/backtrace/trace.rb +12 -15
- data/lib/graphql/coercion_error.rb +1 -9
- data/lib/graphql/dataloader/async_dataloader.rb +85 -0
- data/lib/graphql/dataloader/request.rb +5 -0
- data/lib/graphql/dataloader/source.rb +11 -3
- data/lib/graphql/dataloader.rb +109 -142
- data/lib/graphql/duration_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
- data/lib/graphql/execution/interpreter/runtime.rb +79 -248
- data/lib/graphql/execution/interpreter.rb +91 -157
- data/lib/graphql/execution/lookahead.rb +88 -21
- data/lib/graphql/introspection/dynamic_fields.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +11 -5
- data/lib/graphql/introspection/schema_type.rb +3 -1
- data/lib/graphql/language/block_string.rb +34 -18
- data/lib/graphql/language/definition_slice.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +37 -37
- data/lib/graphql/language/lexer.rb +271 -177
- data/lib/graphql/language/nodes.rb +75 -57
- data/lib/graphql/language/parser.rb +707 -1986
- data/lib/graphql/language/printer.rb +303 -146
- data/lib/graphql/language/sanitized_printer.rb +20 -22
- data/lib/graphql/language/static_visitor.rb +167 -0
- data/lib/graphql/language/visitor.rb +20 -81
- data/lib/graphql/language.rb +1 -0
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/array_connection.rb +3 -3
- data/lib/graphql/pagination/connection.rb +28 -1
- data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
- data/lib/graphql/pagination/relation_connection.rb +3 -3
- data/lib/graphql/query/context/scoped_context.rb +101 -0
- data/lib/graphql/query/context.rb +36 -98
- data/lib/graphql/query/null_context.rb +4 -11
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +13 -22
- data/lib/graphql/railtie.rb +9 -6
- data/lib/graphql/rake_task.rb +3 -12
- data/lib/graphql/schema/argument.rb +6 -1
- data/lib/graphql/schema/base_64_encoder.rb +3 -5
- data/lib/graphql/schema/build_from_definition.rb +0 -11
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/directive/specified_by.rb +14 -0
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +3 -3
- data/lib/graphql/schema/field/connection_extension.rb +1 -15
- data/lib/graphql/schema/field/scope_extension.rb +8 -1
- data/lib/graphql/schema/field.rb +39 -35
- data/lib/graphql/schema/has_single_input_argument.rb +156 -0
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/schema/interface.rb +15 -11
- data/lib/graphql/schema/introspection_system.rb +2 -0
- data/lib/graphql/schema/loader.rb +0 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +61 -38
- data/lib/graphql/schema/member/has_fields.rb +8 -5
- data/lib/graphql/schema/member/has_interfaces.rb +23 -9
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +8 -7
- data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
- data/lib/graphql/schema/resolver.rb +16 -8
- data/lib/graphql/schema/scalar.rb +3 -3
- data/lib/graphql/schema/subscription.rb +11 -4
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/unique_within_type.rb +1 -1
- data/lib/graphql/schema/warden.rb +96 -94
- data/lib/graphql/schema.rb +252 -78
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/literal_validator.rb +2 -3
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +2 -2
- data/lib/graphql/static_validation/validation_context.rb +5 -5
- data/lib/graphql/static_validation/validator.rb +3 -0
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
- data/lib/graphql/subscriptions/event.rb +8 -2
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +14 -12
- data/lib/graphql/testing/helpers.rb +129 -0
- data/lib/graphql/testing.rb +2 -0
- data/lib/graphql/tracing/appoptics_trace.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
- data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
- data/lib/graphql/tracing/platform_tracing.rb +2 -0
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
- data/lib/graphql/tracing/sentry_trace.rb +112 -0
- data/lib/graphql/tracing/trace.rb +1 -0
- data/lib/graphql/tracing.rb +3 -1
- data/lib/graphql/types/iso_8601_duration.rb +77 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +6 -5
- data/readme.md +12 -2
- metadata +46 -38
- data/lib/graphql/deprecation.rb +0 -9
- data/lib/graphql/filter.rb +0 -59
- data/lib/graphql/language/parser.y +0 -560
- data/lib/graphql/schema/base_64_bp.rb +0 -26
- data/lib/graphql/static_validation/type_stack.rb +0 -216
- data/lib/graphql/subscriptions/instrumentation.rb +0 -28
|
@@ -8,20 +8,20 @@ module GraphQL
|
|
|
8
8
|
# It provides access to the schema & fragments which validators may read from.
|
|
9
9
|
#
|
|
10
10
|
# It holds a list of errors which each validator may add to.
|
|
11
|
-
#
|
|
12
|
-
# It also provides limited access to the {TypeStack} instance,
|
|
13
|
-
# which tracks state as you climb in and out of different fields.
|
|
14
11
|
class ValidationContext
|
|
15
12
|
extend Forwardable
|
|
16
13
|
|
|
17
14
|
attr_reader :query, :errors, :visitor,
|
|
18
15
|
:on_dependency_resolve_handlers,
|
|
19
|
-
:max_errors
|
|
16
|
+
:max_errors, :warden, :schema
|
|
17
|
+
|
|
20
18
|
|
|
21
|
-
def_delegators :@query, :
|
|
19
|
+
def_delegators :@query, :document, :fragments, :operations
|
|
22
20
|
|
|
23
21
|
def initialize(query, visitor_class, max_errors)
|
|
24
22
|
@query = query
|
|
23
|
+
@warden = query.warden
|
|
24
|
+
@schema = query.schema
|
|
25
25
|
@literal_validator = LiteralValidator.new(context: query.context)
|
|
26
26
|
@errors = []
|
|
27
27
|
@max_errors = max_errors || Float::INFINITY
|
|
@@ -28,6 +28,7 @@ module GraphQL
|
|
|
28
28
|
# @return [Array<Hash>]
|
|
29
29
|
def validate(query, validate: true, timeout: nil, max_errors: nil)
|
|
30
30
|
query.current_trace.validate(validate: validate, query: query) do
|
|
31
|
+
begin_t = Time.now
|
|
31
32
|
errors = if validate == false
|
|
32
33
|
[]
|
|
33
34
|
else
|
|
@@ -52,11 +53,13 @@ module GraphQL
|
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
{
|
|
56
|
+
remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil,
|
|
55
57
|
errors: errors,
|
|
56
58
|
}
|
|
57
59
|
end
|
|
58
60
|
rescue GraphQL::ExecutionError => e
|
|
59
61
|
{
|
|
62
|
+
remaining_timeout: nil,
|
|
60
63
|
errors: [e],
|
|
61
64
|
}
|
|
62
65
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "graphql/static_validation/error"
|
|
3
3
|
require "graphql/static_validation/definition_dependencies"
|
|
4
|
-
require "graphql/static_validation/type_stack"
|
|
5
4
|
require "graphql/static_validation/validator"
|
|
6
5
|
require "graphql/static_validation/validation_context"
|
|
7
6
|
require "graphql/static_validation/validation_timeout_error"
|
|
@@ -35,7 +35,7 @@ module GraphQL
|
|
|
35
35
|
# }
|
|
36
36
|
#
|
|
37
37
|
# result = MySchema.execute(
|
|
38
|
-
# query
|
|
38
|
+
# query,
|
|
39
39
|
# context: context,
|
|
40
40
|
# variables: variables,
|
|
41
41
|
# operation_name: operation_name
|
|
@@ -124,7 +124,8 @@ module GraphQL
|
|
|
124
124
|
# This subscription was re-evaluated.
|
|
125
125
|
# Send it to the specific stream where this client was waiting.
|
|
126
126
|
def deliver(subscription_id, result)
|
|
127
|
-
|
|
127
|
+
has_more = !result.context.namespace(:subscriptions)[:final_update]
|
|
128
|
+
payload = { result: result.to_h, more: has_more }
|
|
128
129
|
@action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
|
|
129
130
|
end
|
|
130
131
|
|
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# @return [String] an identifier for this unit of subscription
|
|
40
|
-
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
|
|
40
|
+
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
|
|
41
41
|
subscription = field.resolver || GraphQL::Schema::Subscription
|
|
42
42
|
normalized_args = stringify_args(field, arguments.to_h, context)
|
|
43
43
|
subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
|
|
@@ -126,7 +126,13 @@ module GraphQL
|
|
|
126
126
|
when GraphQL::Schema::InputObject
|
|
127
127
|
stringify_args(arg_owner, args.to_h, context)
|
|
128
128
|
else
|
|
129
|
-
|
|
129
|
+
if arg_owner.is_a?(Class) && arg_owner < GraphQL::Schema::Enum
|
|
130
|
+
# `prepare:` may have made the value something other than
|
|
131
|
+
# a defined value of this enum -- use _that_ in this case.
|
|
132
|
+
arg_owner.coerce_isolated_input(args) || args
|
|
133
|
+
else
|
|
134
|
+
args
|
|
135
|
+
end
|
|
130
136
|
end
|
|
131
137
|
end
|
|
132
138
|
|
|
@@ -148,6 +148,8 @@ module GraphQL
|
|
|
148
148
|
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
|
149
149
|
elsif obj.is_a?(OpenStruct)
|
|
150
150
|
{ OPEN_STRUCT_KEY => dump_value(obj.to_h) }
|
|
151
|
+
elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
|
|
152
|
+
dump_value(obj.to_a)
|
|
151
153
|
else
|
|
152
154
|
obj
|
|
153
155
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
require "securerandom"
|
|
3
3
|
require "graphql/subscriptions/broadcast_analyzer"
|
|
4
4
|
require "graphql/subscriptions/event"
|
|
5
|
-
require "graphql/subscriptions/instrumentation"
|
|
6
5
|
require "graphql/subscriptions/serialize"
|
|
7
6
|
require "graphql/subscriptions/action_cable_subscriptions"
|
|
8
7
|
require "graphql/subscriptions/default_subscription_resolve_extension"
|
|
@@ -30,8 +29,6 @@ module GraphQL
|
|
|
30
29
|
raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}"
|
|
31
30
|
end
|
|
32
31
|
|
|
33
|
-
instrumentation = Subscriptions::Instrumentation.new(schema: schema)
|
|
34
|
-
defn.instrument(:query, instrumentation)
|
|
35
32
|
options[:schema] = schema
|
|
36
33
|
schema.subscriptions = self.new(**options)
|
|
37
34
|
schema.add_subscription_extension_if_necessary
|
|
@@ -62,7 +59,7 @@ module GraphQL
|
|
|
62
59
|
# @return [void]
|
|
63
60
|
def trigger(event_name, args, object, scope: nil, context: {})
|
|
64
61
|
# Make something as context-like as possible, even though there isn't a current query:
|
|
65
|
-
dummy_query =
|
|
62
|
+
dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context)
|
|
66
63
|
context = dummy_query.context
|
|
67
64
|
event_name = event_name.to_s
|
|
68
65
|
|
|
@@ -83,7 +80,7 @@ module GraphQL
|
|
|
83
80
|
|
|
84
81
|
# Normalize symbol-keyed args to strings, try camelizing them
|
|
85
82
|
# Should this accept a real context somehow?
|
|
86
|
-
normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
|
|
83
|
+
normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance)
|
|
87
84
|
|
|
88
85
|
event = Subscriptions::Event.new(
|
|
89
86
|
name: normalized_event_name,
|
|
@@ -125,10 +122,10 @@ module GraphQL
|
|
|
125
122
|
variables: variables,
|
|
126
123
|
root_value: object,
|
|
127
124
|
}
|
|
128
|
-
|
|
125
|
+
|
|
129
126
|
# merge event's and query's context together
|
|
130
127
|
context.merge!(event.context) unless event.context.nil? || context.nil?
|
|
131
|
-
|
|
128
|
+
|
|
132
129
|
execute_options[:validate] = validate_update?(**execute_options)
|
|
133
130
|
result = @schema.execute(**execute_options)
|
|
134
131
|
subscriptions_context = result.context.namespace(:subscriptions)
|
|
@@ -136,11 +133,9 @@ module GraphQL
|
|
|
136
133
|
result = nil
|
|
137
134
|
end
|
|
138
135
|
|
|
139
|
-
unsubscribed
|
|
140
|
-
|
|
141
|
-
if unsubscribed
|
|
136
|
+
if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update]
|
|
142
137
|
# `unsubscribe` was called, clean up on our side
|
|
143
|
-
#
|
|
138
|
+
# The transport should also send `{more: false}` to client
|
|
144
139
|
delete_subscription(subscription_id)
|
|
145
140
|
result = nil
|
|
146
141
|
end
|
|
@@ -164,7 +159,14 @@ module GraphQL
|
|
|
164
159
|
res = execute_update(subscription_id, event, object)
|
|
165
160
|
if !res.nil?
|
|
166
161
|
deliver(subscription_id, res)
|
|
162
|
+
|
|
163
|
+
if res.context.namespace(:subscriptions)[:unsubscribed]
|
|
164
|
+
# `unsubscribe` was called, clean up on our side
|
|
165
|
+
# The transport should also send `{more: false}` to client
|
|
166
|
+
delete_subscription(subscription_id)
|
|
167
|
+
end
|
|
167
168
|
end
|
|
169
|
+
|
|
168
170
|
end
|
|
169
171
|
|
|
170
172
|
# Event `event` occurred on `object`,
|
|
@@ -229,7 +231,7 @@ module GraphQL
|
|
|
229
231
|
|
|
230
232
|
# @return [Boolean] if true, then a query like this one would be broadcasted
|
|
231
233
|
def broadcastable?(query_str, **query_options)
|
|
232
|
-
query =
|
|
234
|
+
query = @schema.query_class.new(@schema, query_str, **query_options)
|
|
233
235
|
if !query.valid?
|
|
234
236
|
raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
|
|
235
237
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Testing
|
|
4
|
+
module Helpers
|
|
5
|
+
# @param schema_class [Class<GraphQL::Schema>]
|
|
6
|
+
# @return [Module] A helpers module which always uses the given schema
|
|
7
|
+
def self.for(schema_class)
|
|
8
|
+
SchemaHelpers.for(schema_class)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Error < GraphQL::Error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class TypeNotVisibleError < Error
|
|
15
|
+
def initialize(type_name:)
|
|
16
|
+
message = "`#{type_name}` should be `visible?` this field resolution and `context`, but it was not"
|
|
17
|
+
super(message)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class FieldNotVisibleError < Error
|
|
22
|
+
def initialize(type_name:, field_name:)
|
|
23
|
+
message = "`#{type_name}.#{field_name}` should be `visible?` for this resolution, but it was not"
|
|
24
|
+
super(message)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class TypeNotDefinedError < Error
|
|
29
|
+
def initialize(type_name:)
|
|
30
|
+
message = "No type named `#{type_name}` is defined; choose another type name or define this type."
|
|
31
|
+
super(message)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class FieldNotDefinedError < Error
|
|
36
|
+
def initialize(type_name:, field_name:)
|
|
37
|
+
message = "`#{type_name}` has no field named `#{field_name}`; pick another name or define this field."
|
|
38
|
+
super(message)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def run_graphql_field(schema, field_path, object, arguments: {}, context: {})
|
|
43
|
+
type_name, *field_names = field_path.split(".")
|
|
44
|
+
dummy_query = GraphQL::Query.new(schema, context: context)
|
|
45
|
+
query_context = dummy_query.context
|
|
46
|
+
object_type = dummy_query.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
47
|
+
if object_type
|
|
48
|
+
graphql_result = object
|
|
49
|
+
field_names.each do |field_name|
|
|
50
|
+
inner_object = graphql_result
|
|
51
|
+
graphql_result = object_type.wrap(inner_object, query_context)
|
|
52
|
+
if graphql_result.nil?
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
visible_field = dummy_query.get_field(object_type, field_name)
|
|
56
|
+
if visible_field
|
|
57
|
+
dummy_query.context.dataloader.run_isolated {
|
|
58
|
+
field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context)
|
|
59
|
+
field_args = schema.sync_lazy(field_args)
|
|
60
|
+
graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context)
|
|
61
|
+
graphql_result = schema.sync_lazy(graphql_result)
|
|
62
|
+
}
|
|
63
|
+
object_type = visible_field.type.unwrap
|
|
64
|
+
elsif object_type.all_field_definitions.any? { |f| f.graphql_name == field_name }
|
|
65
|
+
raise FieldNotVisibleError.new(field_name: field_name, type_name: type_name)
|
|
66
|
+
else
|
|
67
|
+
raise FieldNotDefinedError.new(type_name: type_name, field_name: field_name)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
graphql_result
|
|
71
|
+
elsif schema.has_defined_type?(type_name)
|
|
72
|
+
raise TypeNotVisibleError.new(type_name: type_name)
|
|
73
|
+
else
|
|
74
|
+
raise TypeNotDefinedError.new(type_name: type_name)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def with_resolution_context(schema, type:, object:, context:{})
|
|
79
|
+
resolution_context = ResolutionAssertionContext.new(
|
|
80
|
+
self,
|
|
81
|
+
schema: schema,
|
|
82
|
+
type_name: type,
|
|
83
|
+
object: object,
|
|
84
|
+
context: context
|
|
85
|
+
)
|
|
86
|
+
yield(resolution_context)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class ResolutionAssertionContext
|
|
90
|
+
def initialize(test, type_name:, object:, schema:, context:)
|
|
91
|
+
@test = test
|
|
92
|
+
@type_name = type_name
|
|
93
|
+
@object = object
|
|
94
|
+
@schema = schema
|
|
95
|
+
@context = context
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_graphql_field(field_name, arguments: {})
|
|
100
|
+
if @schema
|
|
101
|
+
@test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
|
|
102
|
+
else
|
|
103
|
+
@test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
module SchemaHelpers
|
|
109
|
+
include Helpers
|
|
110
|
+
|
|
111
|
+
def run_graphql_field(field_path, object, arguments: {}, context: {})
|
|
112
|
+
super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def with_resolution_context(*args, **kwargs, &block)
|
|
116
|
+
# schema will be added later
|
|
117
|
+
super(nil, *args, **kwargs, &block)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.for(schema_class)
|
|
121
|
+
Module.new do
|
|
122
|
+
include SchemaHelpers
|
|
123
|
+
@@schema_class_for_helpers = schema_class
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -195,7 +195,7 @@ module GraphQL
|
|
|
195
195
|
else
|
|
196
196
|
[key, data[key]]
|
|
197
197
|
end
|
|
198
|
-
end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
|
|
198
|
+
end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
|
|
199
199
|
end
|
|
200
200
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
201
201
|
|
|
@@ -226,7 +226,7 @@ module GraphQL
|
|
|
226
226
|
end
|
|
227
227
|
|
|
228
228
|
def graphql_multiplex(data)
|
|
229
|
-
names = data.queries.map(&:operations).map(&:keys).flatten.compact
|
|
229
|
+
names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
|
|
230
230
|
multiplex_transaction_name(names) if names.size > 1
|
|
231
231
|
|
|
232
232
|
[:Operations, names.join(', ')]
|
|
@@ -117,7 +117,7 @@ module GraphQL
|
|
|
117
117
|
else
|
|
118
118
|
[key, data[key]]
|
|
119
119
|
end
|
|
120
|
-
end.flatten(2).each_slice(2).to_h.merge(Spec: 'graphql')
|
|
120
|
+
end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql')
|
|
121
121
|
end
|
|
122
122
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
123
123
|
|
|
@@ -148,7 +148,7 @@ module GraphQL
|
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
def graphql_multiplex(data)
|
|
151
|
-
names = data.queries.map(&:operations).map(&:keys).flatten.compact
|
|
151
|
+
names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!)
|
|
152
152
|
multiplex_transaction_name(names) if names.size > 1
|
|
153
153
|
|
|
154
154
|
[:Operations, names.join(', ')]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Tracing
|
|
4
|
+
module LegacyHooksTrace
|
|
5
|
+
def execute_multiplex(multiplex:)
|
|
6
|
+
multiplex_instrumenters = multiplex.schema.instrumenters[:multiplex]
|
|
7
|
+
query_instrumenters = multiplex.schema.instrumenters[:query]
|
|
8
|
+
# First, run multiplex instrumentation, then query instrumentation for each query
|
|
9
|
+
RunHooks.call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
|
|
10
|
+
RunHooks.each_query_call_hooks(query_instrumenters, multiplex.queries) do
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module RunHooks
|
|
17
|
+
module_function
|
|
18
|
+
# Call the before_ hooks of each query,
|
|
19
|
+
# Then yield if no errors.
|
|
20
|
+
# `call_hooks` takes care of appropriate cleanup.
|
|
21
|
+
def each_query_call_hooks(instrumenters, queries, i = 0)
|
|
22
|
+
if i >= queries.length
|
|
23
|
+
yield
|
|
24
|
+
else
|
|
25
|
+
query = queries[i]
|
|
26
|
+
call_hooks(instrumenters, query, :before_query, :after_query) {
|
|
27
|
+
each_query_call_hooks(instrumenters, queries, i + 1) {
|
|
28
|
+
yield
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Call each before hook, and if they all succeed, yield.
|
|
35
|
+
# If they don't all succeed, call after_ for each one that succeeded.
|
|
36
|
+
def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
|
|
37
|
+
begin
|
|
38
|
+
successful = []
|
|
39
|
+
instrumenters.each do |instrumenter|
|
|
40
|
+
instrumenter.public_send(before_hook_name, object)
|
|
41
|
+
successful << instrumenter
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# if any before hooks raise an exception, quit calling before hooks,
|
|
45
|
+
# but call the after hooks on anything that succeeded but also
|
|
46
|
+
# raise the exception that came from the before hook.
|
|
47
|
+
rescue GraphQL::ExecutionError => err
|
|
48
|
+
object.context.errors << err
|
|
49
|
+
rescue => e
|
|
50
|
+
raise call_after_hooks(successful, object, after_hook_name, e)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
yield # Call the user code
|
|
55
|
+
ensure
|
|
56
|
+
ex = call_after_hooks(successful, object, after_hook_name, nil)
|
|
57
|
+
raise ex if ex
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def call_after_hooks(instrumenters, object, after_hook_name, ex)
|
|
62
|
+
instrumenters.reverse_each do |instrumenter|
|
|
63
|
+
begin
|
|
64
|
+
instrumenter.public_send(after_hook_name, object)
|
|
65
|
+
rescue => e
|
|
66
|
+
ex = e
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
ex
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -81,8 +81,10 @@ module GraphQL
|
|
|
81
81
|
trace_name = tracing_name.sub("Tracing", "Trace")
|
|
82
82
|
if GraphQL::Tracing.const_defined?(trace_name, false)
|
|
83
83
|
trace_module = GraphQL::Tracing.const_get(trace_name)
|
|
84
|
+
warn("`use(#{self.name})` is deprecated, use the equivalent `trace_with(#{trace_module.name})` instead. More info: https://graphql-ruby.org/queries/tracing.html")
|
|
84
85
|
schema_defn.trace_with(trace_module, **options)
|
|
85
86
|
else
|
|
87
|
+
warn("`use(#{self.name})` and `Tracing::PlatformTracing` are deprecated. Use a `trace_with(...)` module instead. More info: https://graphql-ruby.org/queries/tracing.html. Please open an issue on the GraphQL-Ruby repo if you want to discuss further!")
|
|
86
88
|
tracer = self.new(**options)
|
|
87
89
|
schema_defn.tracer(tracer)
|
|
88
90
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module GraphQL
|
|
4
4
|
module Tracing
|
|
5
|
-
|
|
5
|
+
module PrometheusTrace
|
|
6
6
|
class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector
|
|
7
7
|
def initialize
|
|
8
8
|
@graphql_gauge = PrometheusExporter::Metric::Base.default_aggregation.new(
|
|
@@ -28,5 +28,7 @@ module GraphQL
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
|
+
# Backwards-compat:
|
|
32
|
+
PrometheusTracing::GraphQLCollector = PrometheusTrace::GraphQLCollector
|
|
31
33
|
end
|
|
32
34
|
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GraphQL
|
|
4
|
+
module Tracing
|
|
5
|
+
module SentryTrace
|
|
6
|
+
include PlatformTrace
|
|
7
|
+
|
|
8
|
+
# @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
|
|
9
|
+
# This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
|
|
10
|
+
# It can also be specified per-query with `context[:set_sentry_transaction_name]`.
|
|
11
|
+
def initialize(set_transaction_name: false, **_rest)
|
|
12
|
+
@set_transaction_name = set_transaction_name
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute_query(**data)
|
|
17
|
+
set_this_txn_name = data[:query].context[:set_sentry_transaction_name]
|
|
18
|
+
if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
|
|
19
|
+
Sentry.configure_scope do |scope|
|
|
20
|
+
scope.set_transaction_name(transaction_name(data[:query]))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
instrument_execution("graphql.execute", "execute_query", data) { super }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
"lex" => "graphql.lex",
|
|
28
|
+
"parse" => "graphql.parse",
|
|
29
|
+
"validate" => "graphql.validate",
|
|
30
|
+
"analyze_query" => "graphql.analyze",
|
|
31
|
+
"analyze_multiplex" => "graphql.analyze_multiplex",
|
|
32
|
+
"execute_multiplex" => "graphql.execute_multiplex",
|
|
33
|
+
"execute_query_lazy" => "graphql.execute"
|
|
34
|
+
}.each do |trace_method, platform_key|
|
|
35
|
+
module_eval <<-RUBY, __FILE__, __LINE__
|
|
36
|
+
def #{trace_method}(**data)
|
|
37
|
+
instrument_execution("#{platform_key}", "#{trace_method}", data) { super }
|
|
38
|
+
end
|
|
39
|
+
RUBY
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def platform_execute_field(platform_key, &block)
|
|
43
|
+
instrument_execution(platform_key, "execute_field", &block)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def platform_execute_field_lazy(platform_key, &block)
|
|
47
|
+
instrument_execution(platform_key, "execute_field_lazy", &block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def platform_authorized(platform_key, &block)
|
|
51
|
+
instrument_execution(platform_key, "authorized", &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def platform_authorized_lazy(platform_key, &block)
|
|
55
|
+
instrument_execution(platform_key, "authorized_lazy", &block)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def platform_resolve_type(platform_key, &block)
|
|
59
|
+
instrument_execution(platform_key, "resolve_type", &block)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def platform_resolve_type_lazy(platform_key, &block)
|
|
63
|
+
instrument_execution(platform_key, "resolve_type_lazy", &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def platform_field_key(field)
|
|
67
|
+
"graphql.field.#{field.path}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def platform_authorized_key(type)
|
|
71
|
+
"graphql.authorized.#{type.graphql_name}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def platform_resolve_type_key(type)
|
|
75
|
+
"graphql.resolve_type.#{type.graphql_name}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def instrument_execution(platform_key, trace_method, data=nil, &block)
|
|
81
|
+
return yield unless Sentry.initialized?
|
|
82
|
+
|
|
83
|
+
Sentry.with_child_span(op: platform_key, start_timestamp: Sentry.utc_now.to_f) do |span|
|
|
84
|
+
result = yield
|
|
85
|
+
return result unless span
|
|
86
|
+
|
|
87
|
+
span.finish
|
|
88
|
+
if trace_method == "execute_multiplex" && data.key?(:multiplex)
|
|
89
|
+
operation_names = data[:multiplex].queries.map{|q| operation_name(q) }
|
|
90
|
+
span.set_description(operation_names.join(", "))
|
|
91
|
+
elsif trace_method == "execute_query" && data.key?(:query)
|
|
92
|
+
span.set_description(operation_name(data[:query]))
|
|
93
|
+
span.set_data('graphql.document', data[:query].query_string)
|
|
94
|
+
span.set_data('graphql.operation.name', data[:query].selected_operation_name) if data[:query].selected_operation_name
|
|
95
|
+
span.set_data('graphql.operation.type', data[:query].selected_operation.operation_type)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def operation_name(query)
|
|
103
|
+
selected_op = query.selected_operation
|
|
104
|
+
if selected_op
|
|
105
|
+
[selected_op.operation_type, selected_op.name].compact.join(' ')
|
|
106
|
+
else
|
|
107
|
+
'GraphQL Operation'
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/graphql/tracing.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "graphql/tracing/trace"
|
|
3
3
|
require "graphql/tracing/legacy_trace"
|
|
4
|
+
require "graphql/tracing/legacy_hooks_trace"
|
|
4
5
|
|
|
5
6
|
# Legacy tracing:
|
|
6
7
|
require "graphql/tracing/active_support_notifications_tracing"
|
|
@@ -21,11 +22,12 @@ require "graphql/tracing/appsignal_trace"
|
|
|
21
22
|
require "graphql/tracing/data_dog_trace"
|
|
22
23
|
require "graphql/tracing/new_relic_trace"
|
|
23
24
|
require "graphql/tracing/notifications_trace"
|
|
25
|
+
require "graphql/tracing/sentry_trace"
|
|
24
26
|
require "graphql/tracing/scout_trace"
|
|
25
27
|
require "graphql/tracing/statsd_trace"
|
|
26
28
|
require "graphql/tracing/prometheus_trace"
|
|
27
29
|
if defined?(PrometheusExporter::Server)
|
|
28
|
-
require "graphql/tracing/
|
|
30
|
+
require "graphql/tracing/prometheus_trace/graphql_collector"
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
module GraphQL
|