graphql 1.9.6 → 1.9.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/generators/graphql/install_generator.rb +2 -1
- data/lib/generators/graphql/templates/base_field.erb +7 -0
- data/lib/graphql/analysis/ast/visitor.rb +5 -2
- data/lib/graphql/execution/multiplex.rb +5 -1
- data/lib/graphql/query.rb +6 -1
- data/lib/graphql/relay/array_connection.rb +1 -1
- data/lib/graphql/schema.rb +15 -1
- data/lib/graphql/schema/argument.rb +5 -1
- data/lib/graphql/schema/input_object.rb +21 -12
- data/lib/graphql/schema/introspection_system.rb +6 -1
- data/lib/graphql/schema/member/has_arguments.rb +4 -2
- data/lib/graphql/schema/resolver.rb +8 -3
- data/lib/graphql/schema/subscription.rb +22 -0
- data/lib/graphql/schema/timeout.rb +109 -0
- data/lib/graphql/types/relay/base_edge.rb +0 -3
- data/lib/graphql/upgrader/member.rb +148 -111
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/fixtures/upgrader/mutation.original.rb +28 -0
- data/spec/fixtures/upgrader/mutation.transformed.rb +28 -0
- data/spec/graphql/analysis/ast_spec.rb +27 -0
- data/spec/graphql/execution/instrumentation_spec.rb +34 -6
- data/spec/graphql/execution/multiplex_spec.rb +11 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +6 -1
- data/spec/graphql/schema/input_object_spec.rb +56 -7
- data/spec/graphql/schema/introspection_system_spec.rb +24 -0
- data/spec/graphql/schema/subscription_spec.rb +65 -0
- data/spec/graphql/schema/timeout_spec.rb +206 -0
- data/spec/integration/mongoid/star_trek/schema.rb +1 -2
- data/spec/integration/rails/graphql/input_object_spec.rb +19 -0
- data/spec/integration/rails/graphql/relay/array_connection_spec.rb +47 -28
- data/spec/integration/rails/graphql/schema_spec.rb +18 -0
- data/spec/integration/tmp/app/graphql/types/date_type.rb +14 -0
- data/spec/integration/tmp/dummy/Gemfile +50 -0
- data/spec/integration/tmp/dummy/README.md +24 -0
- data/spec/integration/tmp/dummy/Rakefile +6 -0
- data/spec/integration/tmp/dummy/app/assets/config/manifest.js +3 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/integration/tmp/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/integration/tmp/dummy/app/channels/application_cable/channel.rb +5 -0
- data/spec/integration/tmp/dummy/app/channels/application_cable/connection.rb +5 -0
- data/spec/integration/tmp/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/integration/tmp/dummy/app/controllers/graphql_controller.rb +44 -0
- data/spec/integration/tmp/dummy/app/helpers/application_helper.rb +3 -0
- data/spec/integration/tmp/dummy/app/jobs/application_job.rb +3 -0
- data/spec/integration/tmp/dummy/app/mailers/application_mailer.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/dummy_schema.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/mutations/update_name.rb +15 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_enum.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_input_object.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_interface.rb +6 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_object.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_scalar.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_union.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/mutation_type.rb +12 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/query_type.rb +14 -0
- data/spec/integration/tmp/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/integration/tmp/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/integration/tmp/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/integration/tmp/dummy/bin/bundle +3 -0
- data/spec/integration/tmp/dummy/bin/rails +4 -0
- data/spec/integration/tmp/dummy/bin/rake +4 -0
- data/spec/integration/tmp/dummy/bin/setup +34 -0
- data/spec/integration/tmp/dummy/bin/update +29 -0
- data/spec/integration/tmp/dummy/config.ru +5 -0
- data/spec/integration/tmp/dummy/config/application.rb +26 -0
- data/spec/integration/tmp/dummy/config/boot.rb +4 -0
- data/spec/integration/tmp/dummy/config/cable.yml +9 -0
- data/spec/integration/tmp/dummy/config/environment.rb +6 -0
- data/spec/integration/tmp/dummy/config/environments/development.rb +52 -0
- data/spec/integration/tmp/dummy/config/environments/production.rb +84 -0
- data/spec/integration/tmp/dummy/config/environments/test.rb +43 -0
- data/spec/integration/tmp/dummy/config/initializers/application_controller_renderer.rb +9 -0
- data/spec/integration/tmp/dummy/config/initializers/assets.rb +12 -0
- data/spec/integration/tmp/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/spec/integration/tmp/dummy/config/initializers/cookies_serializer.rb +6 -0
- data/spec/integration/tmp/dummy/config/initializers/filter_parameter_logging.rb +5 -0
- data/spec/integration/tmp/dummy/config/initializers/inflections.rb +17 -0
- data/spec/integration/tmp/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/integration/tmp/dummy/config/initializers/new_framework_defaults.rb +24 -0
- data/spec/integration/tmp/dummy/config/initializers/session_store.rb +4 -0
- data/spec/integration/tmp/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/spec/integration/tmp/dummy/config/locales/en.yml +23 -0
- data/spec/integration/tmp/dummy/config/puma.rb +48 -0
- data/spec/integration/tmp/dummy/config/routes.rb +9 -0
- data/spec/integration/tmp/dummy/config/secrets.yml +22 -0
- data/spec/integration/tmp/dummy/db/seeds.rb +8 -0
- data/spec/integration/tmp/dummy/log/test.log +0 -0
- data/spec/integration/tmp/dummy/public/404.html +67 -0
- data/spec/integration/tmp/dummy/public/422.html +67 -0
- data/spec/integration/tmp/dummy/public/500.html +66 -0
- data/spec/integration/tmp/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/integration/tmp/dummy/public/apple-touch-icon.png +0 -0
- data/spec/integration/tmp/dummy/public/favicon.ico +0 -0
- data/spec/integration/tmp/dummy/public/robots.txt +5 -0
- data/spec/integration/tmp/dummy/test/test_helper.rb +8 -0
- data/spec/support/jazz.rb +6 -0
- data/spec/support/star_wars/schema.rb +1 -2
- metadata +171 -6
- data/spec/integration/tmp/app/graphql/types/bird_type.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bc1010a9a70360cc6b3d6dcb13db9d35c207179686f503d01d5904c2655f9ae2
|
4
|
+
data.tar.gz: 510f939a5c9dd9b1a66610e7d526aa84e9ae2b494f76a9a07973aef0b24e6cde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bebd9ccec78e0ba8e2d2a13d351a33e9a169261faf954bc50710f80c4ad6d555e495761681e0691213a9ed409b3057f77118f91e73e577fdd988759e4c7eb743
|
7
|
+
data.tar.gz: d458a432047fa847b775f8ff5558084eb2cc845a5277d4f47643483de5f9049d2b19c8c7831058c79b28bc962000d8f13c5201a7396169fa81bf080be8f12e18
|
@@ -13,6 +13,7 @@ module Graphql
|
|
13
13
|
# - graphql/
|
14
14
|
# - resolvers/
|
15
15
|
# - types/
|
16
|
+
# - base_field.rb
|
16
17
|
# - base_enum.rb
|
17
18
|
# - base_input_object.rb
|
18
19
|
# - base_interface.rb
|
@@ -93,7 +94,7 @@ module Graphql
|
|
93
94
|
create_dir("#{options[:directory]}/types")
|
94
95
|
template("schema.erb", schema_file_path)
|
95
96
|
|
96
|
-
["base_object", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type|
|
97
|
+
["base_object", "base_field", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type|
|
97
98
|
template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb")
|
98
99
|
end
|
99
100
|
|
@@ -220,8 +220,11 @@ module GraphQL
|
|
220
220
|
|
221
221
|
# @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
|
222
222
|
def argument_definition
|
223
|
-
|
224
|
-
|
223
|
+
@argument_definitions.last
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [GraphQL::Argument, nil] The previous GraphQL argument
|
227
|
+
def previous_argument_definition
|
225
228
|
@argument_definitions[-2]
|
226
229
|
end
|
227
230
|
|
@@ -175,7 +175,11 @@ module GraphQL
|
|
175
175
|
schema = multiplex.schema
|
176
176
|
multiplex_analyzers = schema.multiplex_analyzers
|
177
177
|
if multiplex.max_complexity
|
178
|
-
multiplex_analyzers +=
|
178
|
+
multiplex_analyzers += if schema.using_ast_analysis?
|
179
|
+
[GraphQL::Analysis::AST::MaxQueryComplexity]
|
180
|
+
else
|
181
|
+
[GraphQL::Analysis::MaxQueryComplexity.new(multiplex.max_complexity)]
|
182
|
+
end
|
179
183
|
end
|
180
184
|
|
181
185
|
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
data/lib/graphql/query.rb
CHANGED
@@ -44,7 +44,12 @@ module GraphQL
|
|
44
44
|
|
45
45
|
# @return [GraphQL::Language::Nodes::Document]
|
46
46
|
def document
|
47
|
-
|
47
|
+
# It's ok if this hasn't been assigned yet
|
48
|
+
if @query_string || @document
|
49
|
+
with_prepared_ast { @document }
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
48
53
|
end
|
49
54
|
|
50
55
|
def inspect
|
@@ -13,7 +13,7 @@ module GraphQL
|
|
13
13
|
sliced_nodes.count > first
|
14
14
|
elsif GraphQL::Relay::ConnectionType.bidirectional_pagination && before
|
15
15
|
# The original array is longer than the `before` index
|
16
|
-
index_from_cursor(before) < nodes.length
|
16
|
+
index_from_cursor(before) < nodes.length + 1
|
17
17
|
else
|
18
18
|
false
|
19
19
|
end
|
data/lib/graphql/schema.rb
CHANGED
@@ -11,6 +11,7 @@ require "graphql/schema/middleware_chain"
|
|
11
11
|
require "graphql/schema/null_mask"
|
12
12
|
require "graphql/schema/possible_types"
|
13
13
|
require "graphql/schema/rescue_middleware"
|
14
|
+
require "graphql/schema/timeout"
|
14
15
|
require "graphql/schema/timeout_middleware"
|
15
16
|
require "graphql/schema/traversal"
|
16
17
|
require "graphql/schema/type_expression"
|
@@ -90,6 +91,7 @@ module GraphQL
|
|
90
91
|
:object_from_id, :id_from_object,
|
91
92
|
:default_mask,
|
92
93
|
:cursor_encoder,
|
94
|
+
disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true },
|
93
95
|
directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.name] = d; m } },
|
94
96
|
directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive },
|
95
97
|
instrument: ->(schema, type, instrumenter, after_built_ins: false) {
|
@@ -110,6 +112,8 @@ module GraphQL
|
|
110
112
|
rescue_from: ->(schema, err_class, &block) { schema.rescue_from(err_class, &block) },
|
111
113
|
tracer: ->(schema, tracer) { schema.tracers.push(tracer) }
|
112
114
|
|
115
|
+
ensure_defined :introspection_system
|
116
|
+
|
113
117
|
attr_accessor \
|
114
118
|
:query, :mutation, :subscription,
|
115
119
|
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
|
@@ -140,6 +144,9 @@ module GraphQL
|
|
140
144
|
# @return [Class] Instantiated for each query
|
141
145
|
attr_accessor :context_class
|
142
146
|
|
147
|
+
# [Boolean] True if this object disables the introspection entry point fields
|
148
|
+
attr_accessor :disable_introspection_entry_points
|
149
|
+
|
143
150
|
class << self
|
144
151
|
attr_writer :default_execution_strategy
|
145
152
|
end
|
@@ -186,6 +193,7 @@ module GraphQL
|
|
186
193
|
@introspection_system = nil
|
187
194
|
@interpreter = false
|
188
195
|
@error_bubbling = false
|
196
|
+
@disable_introspection_entry_points = false
|
189
197
|
end
|
190
198
|
|
191
199
|
# @return [Boolean] True if using the new {GraphQL::Execution::Interpreter}
|
@@ -712,7 +720,8 @@ module GraphQL
|
|
712
720
|
:subscriptions,
|
713
721
|
:union_memberships,
|
714
722
|
:get_field, :root_types, :references_to, :type_from_ast,
|
715
|
-
:possible_types
|
723
|
+
:possible_types,
|
724
|
+
:disable_introspection_entry_points=
|
716
725
|
|
717
726
|
def graphql_definition
|
718
727
|
@graphql_definition ||= to_graphql
|
@@ -737,6 +746,7 @@ module GraphQL
|
|
737
746
|
schema_defn.max_depth = max_depth
|
738
747
|
schema_defn.default_max_page_size = default_max_page_size
|
739
748
|
schema_defn.orphan_types = orphan_types
|
749
|
+
schema_defn.disable_introspection_entry_points = @disable_introspection_entry_points
|
740
750
|
|
741
751
|
prepped_dirs = {}
|
742
752
|
directives.each { |k, v| prepped_dirs[k] = v.graphql_definition}
|
@@ -887,6 +897,10 @@ module GraphQL
|
|
887
897
|
end
|
888
898
|
end
|
889
899
|
|
900
|
+
def disable_introspection_entry_points
|
901
|
+
@disable_introspection_entry_points = true
|
902
|
+
end
|
903
|
+
|
890
904
|
def orphan_types(*new_orphan_types)
|
891
905
|
if new_orphan_types.any?
|
892
906
|
@orphan_types = new_orphan_types.flatten
|
@@ -21,6 +21,9 @@ module GraphQL
|
|
21
21
|
# @return [Symbol] This argument's name in Ruby keyword arguments
|
22
22
|
attr_reader :keyword
|
23
23
|
|
24
|
+
# @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load
|
25
|
+
attr_reader :loads
|
26
|
+
|
24
27
|
# @param arg_name [Symbol]
|
25
28
|
# @param type_expr
|
26
29
|
# @param desc [String]
|
@@ -30,7 +33,7 @@ module GraphQL
|
|
30
33
|
# @param as [Symbol] Override the keyword name when passed to a method
|
31
34
|
# @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution
|
32
35
|
# @param camelize [Boolean] if true, the name will be camelized when building the schema
|
33
|
-
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, description: nil, default_value: NO_DEFAULT, as: nil, camelize: true, prepare: nil, owner:, &definition_block)
|
36
|
+
def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, default_value: NO_DEFAULT, as: nil, camelize: true, prepare: nil, owner:, &definition_block)
|
34
37
|
arg_name ||= name
|
35
38
|
name_str = camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s
|
36
39
|
@name = name_str.freeze
|
@@ -40,6 +43,7 @@ module GraphQL
|
|
40
43
|
@default_value = default_value
|
41
44
|
@owner = owner
|
42
45
|
@as = as
|
46
|
+
@loads = loads
|
43
47
|
@keyword = as || Schema::Member::BuildType.underscore(@name).to_sym
|
44
48
|
@prepare = prepare
|
45
49
|
|
@@ -21,6 +21,17 @@ module GraphQL
|
|
21
21
|
self.class.arguments.each do |name, arg_defn|
|
22
22
|
@arguments_by_keyword[arg_defn.keyword] = arg_defn
|
23
23
|
ruby_kwargs_key = arg_defn.keyword
|
24
|
+
loads = arg_defn.loads
|
25
|
+
|
26
|
+
if @ruby_style_hash.key?(ruby_kwargs_key) && loads
|
27
|
+
value = @ruby_style_hash[ruby_kwargs_key]
|
28
|
+
@ruby_style_hash[ruby_kwargs_key] = if arg_defn.type.list?
|
29
|
+
GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(arg_defn, loads, val) })
|
30
|
+
else
|
31
|
+
load_application_object(arg_defn, loads, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
24
35
|
if @ruby_style_hash.key?(ruby_kwargs_key) && arg_defn.prepare
|
25
36
|
@ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
|
26
37
|
end
|
@@ -42,6 +53,10 @@ module GraphQL
|
|
42
53
|
end
|
43
54
|
end
|
44
55
|
|
56
|
+
def to_hash
|
57
|
+
to_h
|
58
|
+
end
|
59
|
+
|
45
60
|
def unwrap_value(value)
|
46
61
|
case value
|
47
62
|
when Array
|
@@ -83,20 +98,14 @@ module GraphQL
|
|
83
98
|
# @return [Class<GraphQL::Arguments>]
|
84
99
|
attr_accessor :arguments_class
|
85
100
|
|
86
|
-
def argument(
|
87
|
-
|
101
|
+
def argument(*args, **kwargs, &block)
|
102
|
+
# Translate `loads:` to `as:` if needed`
|
103
|
+
*args, kwargs = argument_with_loads(*args, **kwargs, &block)
|
104
|
+
argument_defn = super(*args, **kwargs, &block)
|
88
105
|
# Add a method access
|
89
106
|
method_name = argument_defn.keyword
|
90
107
|
define_method(method_name) do
|
91
|
-
|
92
|
-
argument = @arguments_by_keyword[method_name]
|
93
|
-
if loads && argument_defn.type.list?
|
94
|
-
GraphQL::Execution::Lazy.all(value.map { |val| load_application_object(argument, loads, val) })
|
95
|
-
elsif loads
|
96
|
-
load_application_object(argument, loads, value)
|
97
|
-
else
|
98
|
-
value
|
99
|
-
end
|
108
|
+
self[method_name]
|
100
109
|
end
|
101
110
|
end
|
102
111
|
|
@@ -122,4 +131,4 @@ module GraphQL
|
|
122
131
|
end
|
123
132
|
end
|
124
133
|
end
|
125
|
-
end
|
134
|
+
end
|
@@ -18,7 +18,12 @@ module GraphQL
|
|
18
18
|
@input_value_type = load_constant(:InputValueType).to_graphql
|
19
19
|
@type_kind_enum = load_constant(:TypeKindEnum).to_graphql
|
20
20
|
@directive_location_enum = load_constant(:DirectiveLocationEnum).to_graphql
|
21
|
-
@entry_point_fields =
|
21
|
+
@entry_point_fields =
|
22
|
+
if schema.disable_introspection_entry_points
|
23
|
+
{}
|
24
|
+
else
|
25
|
+
get_fields_from_class(class_sym: :EntryPoints)
|
26
|
+
end
|
22
27
|
@dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
|
23
28
|
end
|
24
29
|
|
@@ -13,8 +13,10 @@ module GraphQL
|
|
13
13
|
cls.include(ArgumentObjectLoader)
|
14
14
|
end
|
15
15
|
|
16
|
-
def argument_with_loads(
|
16
|
+
def argument_with_loads(*args, **kwargs)
|
17
|
+
loads = kwargs[:loads]
|
17
18
|
if loads
|
19
|
+
name = args[0]
|
18
20
|
name_as_string = name.to_s
|
19
21
|
|
20
22
|
inferred_arg_name = case name_as_string
|
@@ -31,7 +33,7 @@ module GraphQL
|
|
31
33
|
kwargs[:as] ||= inferred_arg_name
|
32
34
|
end
|
33
35
|
|
34
|
-
return [
|
36
|
+
return [*args, **kwargs]
|
35
37
|
end
|
36
38
|
|
37
39
|
# @see {GraphQL::Schema::Argument#initialize} for parameters
|
@@ -255,8 +255,11 @@ module GraphQL
|
|
255
255
|
# also add some preparation hook methods which will be used for this argument
|
256
256
|
# @see {GraphQL::Schema::Argument#initialize} for the signature
|
257
257
|
def argument(name, type, *rest, loads: nil, **kwargs, &block)
|
258
|
-
|
259
|
-
|
258
|
+
*args, kwargs = argument_with_loads(name, type, *rest, loads: loads, **kwargs, &block)
|
259
|
+
# Short-circuit the InputObject's own `loads:` implementation
|
260
|
+
# so that we can support `#load_{x}` methods below.
|
261
|
+
kwargs.delete(:loads)
|
262
|
+
arg_defn = super(*args, **kwargs)
|
260
263
|
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
261
264
|
|
262
265
|
if loads && arg_defn.type.list?
|
@@ -264,7 +267,9 @@ module GraphQL
|
|
264
267
|
def load_#{arg_defn.keyword}(values)
|
265
268
|
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
266
269
|
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
267
|
-
|
270
|
+
context.schema.after_lazy(values) do |values2|
|
271
|
+
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value) })
|
272
|
+
end
|
268
273
|
end
|
269
274
|
RUBY
|
270
275
|
elsif loads
|
@@ -92,6 +92,28 @@ module GraphQL
|
|
92
92
|
def unsubscribe
|
93
93
|
raise UnsubscribedError
|
94
94
|
end
|
95
|
+
|
96
|
+
# Call this method to provide a new subscription_scope; OR
|
97
|
+
# call it without an argument to get the subscription_scope
|
98
|
+
# @param new_scope [Symbol]
|
99
|
+
# @return [Symbol]
|
100
|
+
READING_SCOPE = ::Object.new
|
101
|
+
def self.subscription_scope(new_scope = READING_SCOPE)
|
102
|
+
if new_scope != READING_SCOPE
|
103
|
+
@subscription_scope = new_scope
|
104
|
+
elsif defined?(@subscription_scope)
|
105
|
+
@subscription_scope
|
106
|
+
else
|
107
|
+
find_inherited_method(:subscription_scope, nil)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Overriding Resolver#field_options to include subscription_scope
|
112
|
+
def self.field_options
|
113
|
+
super.merge(
|
114
|
+
subscription_scope: subscription_scope
|
115
|
+
)
|
116
|
+
end
|
95
117
|
end
|
96
118
|
end
|
97
119
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
# This plugin will stop resolving new fields after `max_seconds` have elapsed.
|
6
|
+
# After the time has passed, any remaining fields will be `nil`, with errors added
|
7
|
+
# to the `errors` key. Any already-resolved fields will be in the `data` key, so
|
8
|
+
# you'll get a partial response.
|
9
|
+
#
|
10
|
+
# You can subclass `GraphQL::Schema::Timeout` and override the `handle_timeout` method
|
11
|
+
# to provide custom logic when a timeout error occurs.
|
12
|
+
#
|
13
|
+
# Note that this will stop a query _in between_ field resolutions, but
|
14
|
+
# it doesn't interrupt long-running `resolve` functions. Be sure to use
|
15
|
+
# timeout options for external connections. For more info, see
|
16
|
+
# www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/
|
17
|
+
#
|
18
|
+
# @example Stop resolving fields after 2 seconds
|
19
|
+
# class MySchema < GraphQL::Schema
|
20
|
+
# use GraphQL::Schema::Timeout, max_seconds: 2
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Notifying Bugsnag and logging a timeout
|
24
|
+
# class MyTimeout < GraphQL::Schema::Timeout
|
25
|
+
# def handle_timeout(error, query)
|
26
|
+
# Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}")
|
27
|
+
# Bugsnag.notify(error, {query_string: query.query_string})
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# class MySchema < GraphQL::Schema
|
32
|
+
# use MyTimeout, max_seconds: 2
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
class Timeout
|
36
|
+
attr_reader :max_seconds
|
37
|
+
|
38
|
+
def self.use(schema, **options)
|
39
|
+
tracer = new(**options)
|
40
|
+
schema.tracer(tracer)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
|
44
|
+
def initialize(max_seconds:)
|
45
|
+
@max_seconds = max_seconds
|
46
|
+
end
|
47
|
+
|
48
|
+
def trace(key, data)
|
49
|
+
case key
|
50
|
+
when 'execute_multiplex'
|
51
|
+
timeout_state = {
|
52
|
+
timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
|
53
|
+
timed_out: false
|
54
|
+
}
|
55
|
+
|
56
|
+
data.fetch(:multiplex).queries.each do |query|
|
57
|
+
query.context.namespace(self.class)[:state] = timeout_state
|
58
|
+
end
|
59
|
+
|
60
|
+
yield
|
61
|
+
when 'execute_field', 'execute_field_lazy'
|
62
|
+
query = data[:context] ? data.fetch(:context).query : data.fetch(:query)
|
63
|
+
timeout_state = query.context.namespace(self.class).fetch(:state)
|
64
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
|
65
|
+
error = if data[:context]
|
66
|
+
context = data.fetch(:context)
|
67
|
+
GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
|
68
|
+
else
|
69
|
+
field = data.fetch(:field)
|
70
|
+
GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Only invoke the timeout callback for the first timeout
|
74
|
+
unless timeout_state[:timed_out]
|
75
|
+
timeout_state[:timed_out] = true
|
76
|
+
handle_timeout(error, query)
|
77
|
+
end
|
78
|
+
|
79
|
+
error
|
80
|
+
else
|
81
|
+
yield
|
82
|
+
end
|
83
|
+
else
|
84
|
+
yield
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Invoked when a query times out.
|
89
|
+
# @param error [GraphQL::Schema::Timeout::TimeoutError]
|
90
|
+
# @param query [GraphQL::Error]
|
91
|
+
def handle_timeout(error, query)
|
92
|
+
# override to do something interesting
|
93
|
+
end
|
94
|
+
|
95
|
+
# This error is raised when a query exceeds `max_seconds`.
|
96
|
+
# Since it's a child of {GraphQL::ExecutionError},
|
97
|
+
# its message will be added to the response's `errors` key.
|
98
|
+
#
|
99
|
+
# To raise an error that will stop query resolution, use a custom block
|
100
|
+
# to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError},
|
101
|
+
# such as `RuntimeError`.
|
102
|
+
class TimeoutError < GraphQL::ExecutionError
|
103
|
+
def initialize(parent_type, field)
|
104
|
+
super("Timeout on #{parent_type.graphql_name}.#{field.graphql_name}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|