graphql 2.0.30 → 2.3.6
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/mutation_root_generator.rb +2 -2
- 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 +5 -0
- data/lib/graphql/analysis/analyzer.rb +89 -0
- data/lib/graphql/analysis/field_usage.rb +82 -0
- data/lib/graphql/analysis/max_query_complexity.rb +20 -0
- data/lib/graphql/analysis/max_query_depth.rb +20 -0
- data/lib/graphql/analysis/query_complexity.rb +183 -0
- data/lib/graphql/analysis/query_depth.rb +58 -0
- data/lib/graphql/analysis/visitor.rb +282 -0
- data/lib/graphql/analysis.rb +92 -1
- data/lib/graphql/backtrace/inspect_result.rb +0 -12
- data/lib/graphql/backtrace/trace.rb +12 -15
- data/lib/graphql/coercion_error.rb +1 -9
- data/lib/graphql/dataloader/async_dataloader.rb +88 -0
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/dataloader/request.rb +5 -0
- data/lib/graphql/dataloader/source.rb +11 -3
- data/lib/graphql/dataloader.rb +112 -142
- data/lib/graphql/duration_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +175 -0
- data/lib/graphql/execution/interpreter/runtime.rb +163 -365
- data/lib/graphql/execution/interpreter.rb +92 -158
- 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 +38 -38
- data/lib/graphql/language/lexer.rb +305 -193
- data/lib/graphql/language/nodes.rb +113 -66
- data/lib/graphql/language/parser.rb +787 -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 +61 -0
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/array_connection.rb +6 -6
- data/lib/graphql/pagination/connection.rb +28 -1
- data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
- data/lib/graphql/query/context/scoped_context.rb +101 -0
- data/lib/graphql/query/context.rb +66 -131
- data/lib/graphql/query/null_context.rb +4 -11
- data/lib/graphql/query/validation_pipeline.rb +4 -4
- data/lib/graphql/query/variables.rb +3 -3
- data/lib/graphql/query.rb +17 -26
- data/lib/graphql/railtie.rb +9 -6
- data/lib/graphql/rake_task.rb +3 -12
- data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
- data/lib/graphql/schema/addition.rb +21 -11
- data/lib/graphql/schema/argument.rb +43 -8
- data/lib/graphql/schema/base_64_encoder.rb +3 -5
- data/lib/graphql/schema/build_from_definition.rb +9 -12
- 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 +3 -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 +49 -35
- data/lib/graphql/schema/has_single_input_argument.rb +157 -0
- data/lib/graphql/schema/input_object.rb +4 -4
- data/lib/graphql/schema/interface.rb +10 -10
- data/lib/graphql/schema/introspection_system.rb +4 -2
- data/lib/graphql/schema/late_bound_type.rb +4 -0
- data/lib/graphql/schema/list.rb +2 -2
- data/lib/graphql/schema/loader.rb +2 -3
- data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
- data/lib/graphql/schema/member/has_arguments.rb +63 -73
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- 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/relay_shortcuts.rb +1 -1
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -2
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/mutation.rb +7 -0
- 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 +27 -13
- 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 -95
- data/lib/graphql/schema.rb +323 -102
- 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 +2 -2
- 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 +4 -3
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
- data/lib/graphql/subscriptions/event.rb +8 -2
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +15 -13
- data/lib/graphql/testing/helpers.rb +151 -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 +3 -1
- data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
- data/lib/graphql/tracing/prometheus_trace.rb +9 -9
- 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/type_kinds.rb +1 -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 +13 -13
- data/readme.md +12 -2
- metadata +33 -26
- data/lib/graphql/analysis/ast/analyzer.rb +0 -84
- data/lib/graphql/analysis/ast/field_usage.rb +0 -57
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
- data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
- data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
- data/lib/graphql/analysis/ast/query_depth.rb +0 -55
- data/lib/graphql/analysis/ast/visitor.rb +0 -276
- data/lib/graphql/analysis/ast.rb +0 -81
- 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
|
@@ -3,7 +3,7 @@ module GraphQL
|
|
|
3
3
|
module StaticValidation
|
|
4
4
|
# Default rules for {GraphQL::StaticValidation::Validator}
|
|
5
5
|
#
|
|
6
|
-
# Order is important here. Some validators
|
|
6
|
+
# Order is important here. Some validators skip later hooks.
|
|
7
7
|
# which stops the visit on that node. That way it doesn't try to find fields on types that
|
|
8
8
|
# don't exist, etc.
|
|
9
9
|
ALL_RULES = [
|
|
@@ -110,8 +110,8 @@ module GraphQL
|
|
|
110
110
|
# TODO - would be nice to use these to create an error message so the caller knows
|
|
111
111
|
# that required fields are missing
|
|
112
112
|
required_field_names = @warden.arguments(type)
|
|
113
|
-
.select { |argument| argument.type.kind.non_null? &&
|
|
114
|
-
.map(&:name)
|
|
113
|
+
.select { |argument| argument.type.kind.non_null? && !argument.default_value? }
|
|
114
|
+
.map!(&:name)
|
|
115
115
|
|
|
116
116
|
present_field_names = ast_node.arguments.map(&:name)
|
|
117
117
|
missing_required_field_names = required_field_names - present_field_names
|
|
@@ -122,7 +122,6 @@ module GraphQL
|
|
|
122
122
|
arg_type = @warden.get_argument(type, name).type
|
|
123
123
|
recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
|
|
124
124
|
end
|
|
125
|
-
|
|
126
125
|
if type.one_of? && ast_node.arguments.size != 1
|
|
127
126
|
results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})")
|
|
128
127
|
end
|
|
@@ -340,7 +340,7 @@ module GraphQL
|
|
|
340
340
|
selections.each do |node|
|
|
341
341
|
case node
|
|
342
342
|
when GraphQL::Language::Nodes::Field
|
|
343
|
-
definition = context.
|
|
343
|
+
definition = context.warden.get_field(owner_type, node.name)
|
|
344
344
|
fields << Field.new(node, definition, owner_type, parents)
|
|
345
345
|
when GraphQL::Language::Nodes::InlineFragment
|
|
346
346
|
fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type
|
|
@@ -396,7 +396,7 @@ module GraphQL
|
|
|
396
396
|
end
|
|
397
397
|
|
|
398
398
|
# Given two list of parents, find out if they are mutually exclusive
|
|
399
|
-
# In this context, `parents`
|
|
399
|
+
# In this context, `parents` represents the "self scope" of the field,
|
|
400
400
|
# what types may be found at this point in the query.
|
|
401
401
|
def mutually_exclusive?(parents1, parents2)
|
|
402
402
|
if parents1.empty? || parents2.empty?
|
|
@@ -21,7 +21,7 @@ module GraphQL
|
|
|
21
21
|
present_argument_names = ast_node.arguments.map(&:name)
|
|
22
22
|
required_argument_names = context.warden.arguments(defn)
|
|
23
23
|
.select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
|
|
24
|
-
.map(&:name)
|
|
24
|
+
.map!(&:name)
|
|
25
25
|
|
|
26
26
|
missing_names = required_argument_names - present_argument_names
|
|
27
27
|
if missing_names.any?
|
|
@@ -35,8 +35,8 @@ module GraphQL
|
|
|
35
35
|
return unless parent_type && parent_type.kind.input_object?
|
|
36
36
|
|
|
37
37
|
required_fields = context.warden.arguments(parent_type)
|
|
38
|
-
.select{|arg| arg.type.kind.non_null?}
|
|
39
|
-
.map(&:graphql_name)
|
|
38
|
+
.select{ |arg| arg.type.kind.non_null? && !arg.default_value? }
|
|
39
|
+
.map!(&:graphql_name)
|
|
40
40
|
|
|
41
41
|
present_fields = ast_node.arguments.map(&:name)
|
|
42
42
|
missing_fields = required_fields - present_fields
|
|
@@ -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
|
|
@@ -107,7 +107,7 @@ module GraphQL
|
|
|
107
107
|
when 2
|
|
108
108
|
true
|
|
109
109
|
else
|
|
110
|
-
raise ArgumentError, "#{@serializer} must
|
|
110
|
+
raise ArgumentError, "#{@serializer} must respond to `.load` accepting one or two arguments"
|
|
111
111
|
end
|
|
112
112
|
@transmit_ns = namespace
|
|
113
113
|
super
|
|
@@ -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
|
|
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
|
9
9
|
# Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
|
|
10
10
|
# @api private
|
|
11
11
|
# @see Subscriptions#broadcastable? for a public API
|
|
12
|
-
class BroadcastAnalyzer < GraphQL::Analysis::
|
|
12
|
+
class BroadcastAnalyzer < GraphQL::Analysis::Analyzer
|
|
13
13
|
def initialize(subject)
|
|
14
14
|
super
|
|
15
15
|
@default_broadcastable = subject.schema.subscriptions.default_broadcastable
|
|
@@ -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,11 +231,11 @@ 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
|
|
236
|
-
GraphQL::Analysis
|
|
238
|
+
GraphQL::Analysis.analyze_query(query, @schema.query_analyzers)
|
|
237
239
|
query.context.namespace(:subscriptions)[:subscription_broadcastable]
|
|
238
240
|
end
|
|
239
241
|
|
|
@@ -0,0 +1,151 @@
|
|
|
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: {}, ast_node: nil, lookahead: nil)
|
|
43
|
+
type_name, *field_names = field_path.split(".")
|
|
44
|
+
dummy_query = GraphQL::Query.new(schema, "{ __typename }", 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
|
+
if visible_field.extras.any?
|
|
61
|
+
extra_args = {}
|
|
62
|
+
visible_field.extras.each do |extra|
|
|
63
|
+
extra_args[extra] = case extra
|
|
64
|
+
when :ast_node
|
|
65
|
+
ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
|
|
66
|
+
when :lookahead
|
|
67
|
+
lookahead ||= begin
|
|
68
|
+
ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name)
|
|
69
|
+
Execution::Lookahead.new(
|
|
70
|
+
query: dummy_query,
|
|
71
|
+
ast_nodes: [ast_node],
|
|
72
|
+
field: visible_field,
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
raise ArgumentError, "This extra isn't supported in `run_graphql_field` yet: `#{extra.inspect}`. Open an issue on GitHub to request it: https://github.com/rmosolgo/graphql-ruby/issues/new"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
field_args = field_args.merge_extras(extra_args)
|
|
81
|
+
end
|
|
82
|
+
graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context)
|
|
83
|
+
graphql_result = schema.sync_lazy(graphql_result)
|
|
84
|
+
}
|
|
85
|
+
object_type = visible_field.type.unwrap
|
|
86
|
+
elsif object_type.all_field_definitions.any? { |f| f.graphql_name == field_name }
|
|
87
|
+
raise FieldNotVisibleError.new(field_name: field_name, type_name: type_name)
|
|
88
|
+
else
|
|
89
|
+
raise FieldNotDefinedError.new(type_name: type_name, field_name: field_name)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
graphql_result
|
|
93
|
+
elsif schema.has_defined_type?(type_name)
|
|
94
|
+
raise TypeNotVisibleError.new(type_name: type_name)
|
|
95
|
+
else
|
|
96
|
+
raise TypeNotDefinedError.new(type_name: type_name)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def with_resolution_context(schema, type:, object:, context:{})
|
|
101
|
+
resolution_context = ResolutionAssertionContext.new(
|
|
102
|
+
self,
|
|
103
|
+
schema: schema,
|
|
104
|
+
type_name: type,
|
|
105
|
+
object: object,
|
|
106
|
+
context: context
|
|
107
|
+
)
|
|
108
|
+
yield(resolution_context)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class ResolutionAssertionContext
|
|
112
|
+
def initialize(test, type_name:, object:, schema:, context:)
|
|
113
|
+
@test = test
|
|
114
|
+
@type_name = type_name
|
|
115
|
+
@object = object
|
|
116
|
+
@schema = schema
|
|
117
|
+
@context = context
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run_graphql_field(field_name, arguments: {})
|
|
122
|
+
if @schema
|
|
123
|
+
@test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
|
|
124
|
+
else
|
|
125
|
+
@test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
module SchemaHelpers
|
|
131
|
+
include Helpers
|
|
132
|
+
|
|
133
|
+
def run_graphql_field(field_path, object, arguments: {}, context: {})
|
|
134
|
+
super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def with_resolution_context(*args, **kwargs, &block)
|
|
138
|
+
# schema will be added later
|
|
139
|
+
super(nil, *args, **kwargs, &block)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.for(schema_class)
|
|
143
|
+
Module.new do
|
|
144
|
+
include SchemaHelpers
|
|
145
|
+
@@schema_class_for_helpers = schema_class
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
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,10 +81,12 @@ 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, silence_deprecation_warning: true)
|
|
88
90
|
end
|
|
89
91
|
end
|
|
90
92
|
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
|
|
@@ -24,34 +24,34 @@ module GraphQL
|
|
|
24
24
|
'execute_query_lazy' => "graphql.execute",
|
|
25
25
|
}.each do |trace_method, platform_key|
|
|
26
26
|
module_eval <<-RUBY, __FILE__, __LINE__
|
|
27
|
-
def #{trace_method}(**data
|
|
28
|
-
|
|
27
|
+
def #{trace_method}(**data)
|
|
28
|
+
instrument_prometheus_execution("#{platform_key}", "#{trace_method}") { super }
|
|
29
29
|
end
|
|
30
30
|
RUBY
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def platform_execute_field(platform_key, &block)
|
|
34
|
-
|
|
34
|
+
instrument_prometheus_execution(platform_key, "execute_field", &block)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def platform_execute_field_lazy(platform_key, &block)
|
|
38
|
-
|
|
38
|
+
instrument_prometheus_execution(platform_key, "execute_field_lazy", &block)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def platform_authorized(platform_key, &block)
|
|
42
|
-
|
|
42
|
+
instrument_prometheus_execution(platform_key, "authorized", &block)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def platform_authorized_lazy(platform_key, &block)
|
|
46
|
-
|
|
46
|
+
instrument_prometheus_execution(platform_key, "authorized_lazy", &block)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def platform_resolve_type(platform_key, &block)
|
|
50
|
-
|
|
50
|
+
instrument_prometheus_execution(platform_key, "resolve_type", &block)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def platform_resolve_type_lazy(platform_key, &block)
|
|
54
|
-
|
|
54
|
+
instrument_prometheus_execution(platform_key, "resolve_type_lazy", &block)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def platform_field_key(field)
|
|
@@ -68,7 +68,7 @@ module GraphQL
|
|
|
68
68
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
|
-
def
|
|
71
|
+
def instrument_prometheus_execution(platform_key, key, &block)
|
|
72
72
|
if @keys_whitelist.include?(key)
|
|
73
73
|
start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
|
|
74
74
|
result = block.call
|