graphql 2.0.16 → 2.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/ast/visitor.rb +42 -35
  3. data/lib/graphql/analysis/ast.rb +2 -2
  4. data/lib/graphql/backtrace/tracer.rb +1 -1
  5. data/lib/graphql/execution/interpreter/resolve.rb +19 -0
  6. data/lib/graphql/execution/interpreter/runtime.rb +106 -88
  7. data/lib/graphql/execution/interpreter.rb +14 -9
  8. data/lib/graphql/execution/lazy.rb +6 -12
  9. data/lib/graphql/execution/multiplex.rb +2 -1
  10. data/lib/graphql/graphql_ext.bundle +0 -0
  11. data/lib/graphql/introspection/directive_type.rb +2 -2
  12. data/lib/graphql/introspection/field_type.rb +1 -1
  13. data/lib/graphql/introspection/schema_type.rb +2 -2
  14. data/lib/graphql/introspection/type_type.rb +5 -5
  15. data/lib/graphql/language/lexer.rb +216 -1505
  16. data/lib/graphql/language/lexer.ri +744 -0
  17. data/lib/graphql/language/nodes.rb +39 -31
  18. data/lib/graphql/language/parser.rb +9 -9
  19. data/lib/graphql/language/parser.y +9 -9
  20. data/lib/graphql/language/visitor.rb +191 -83
  21. data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
  22. data/lib/graphql/query/context.rb +45 -11
  23. data/lib/graphql/query.rb +15 -2
  24. data/lib/graphql/schema/argument.rb +0 -4
  25. data/lib/graphql/schema/directive.rb +12 -2
  26. data/lib/graphql/schema/enum.rb +24 -17
  27. data/lib/graphql/schema/enum_value.rb +5 -3
  28. data/lib/graphql/schema/field.rb +53 -45
  29. data/lib/graphql/schema/interface.rb +0 -10
  30. data/lib/graphql/schema/late_bound_type.rb +2 -0
  31. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -14
  32. data/lib/graphql/schema/member/has_arguments.rb +104 -57
  33. data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
  34. data/lib/graphql/schema/member/has_fields.rb +14 -2
  35. data/lib/graphql/schema/member/has_interfaces.rb +49 -8
  36. data/lib/graphql/schema/member/has_validators.rb +31 -5
  37. data/lib/graphql/schema/member/type_system_helpers.rb +17 -0
  38. data/lib/graphql/schema/object.rb +2 -4
  39. data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
  40. data/lib/graphql/schema/timeout.rb +23 -27
  41. data/lib/graphql/schema/warden.rb +26 -4
  42. data/lib/graphql/schema.rb +37 -19
  43. data/lib/graphql/static_validation/literal_validator.rb +15 -1
  44. data/lib/graphql/static_validation/validator.rb +1 -1
  45. data/lib/graphql/subscriptions/event.rb +2 -7
  46. data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
  47. data/lib/graphql/tracing/appoptics_trace.rb +231 -0
  48. data/lib/graphql/tracing/appsignal_trace.rb +66 -0
  49. data/lib/graphql/tracing/data_dog_trace.rb +148 -0
  50. data/lib/graphql/tracing/new_relic_trace.rb +75 -0
  51. data/lib/graphql/tracing/notifications_trace.rb +41 -0
  52. data/lib/graphql/tracing/platform_trace.rb +107 -0
  53. data/lib/graphql/tracing/platform_tracing.rb +15 -3
  54. data/lib/graphql/tracing/prometheus_trace.rb +89 -0
  55. data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
  56. data/lib/graphql/tracing/scout_trace.rb +72 -0
  57. data/lib/graphql/tracing/statsd_trace.rb +56 -0
  58. data/lib/graphql/tracing.rb +136 -39
  59. data/lib/graphql/type_kinds.rb +6 -3
  60. data/lib/graphql/types/relay/connection_behaviors.rb +0 -4
  61. data/lib/graphql/types/relay/edge_behaviors.rb +0 -4
  62. data/lib/graphql/types/string.rb +1 -1
  63. data/lib/graphql/version.rb +1 -1
  64. data/lib/graphql.rb +7 -8
  65. metadata +15 -4
  66. data/lib/graphql/language/lexer.rl +0 -280
@@ -11,6 +11,7 @@ module GraphQL
11
11
  def self.extended(cls)
12
12
  cls.extend(ArgumentClassAccessor)
13
13
  cls.include(ArgumentObjectLoader)
14
+ cls.extend(ClassConfigured)
14
15
  end
15
16
 
16
17
  # @see {GraphQL::Schema::Argument#initialize} for parameters
@@ -109,14 +110,6 @@ module GraphQL
109
110
 
110
111
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
111
112
  def arguments(context = GraphQL::Query::NullContext)
112
- inherited_arguments = if self.is_a?(Class) && superclass.respond_to?(:arguments)
113
- superclass.arguments(context)
114
- elsif defined?(@resolver_class) && @resolver_class
115
- @resolver_class.field_arguments(context)
116
- else
117
- nil
118
- end
119
- # Local definitions override inherited ones
120
113
  if own_arguments.any?
121
114
  own_arguments_that_apply = {}
122
115
  own_arguments.each do |name, args_entry|
@@ -125,47 +118,107 @@ module GraphQL
125
118
  end
126
119
  end
127
120
  end
121
+ # might be nil if there are actually no arguments
122
+ own_arguments_that_apply || own_arguments
123
+ end
128
124
 
129
- if inherited_arguments
130
- if own_arguments_that_apply
131
- inherited_arguments.merge(own_arguments_that_apply)
132
- else
133
- inherited_arguments
125
+ module ClassConfigured
126
+ def inherited(child_class)
127
+ super
128
+ child_class.extend(InheritedArguments)
129
+ end
130
+
131
+ module InheritedArguments
132
+ def arguments(context = GraphQL::Query::NullContext)
133
+ own_arguments = super
134
+ inherited_arguments = superclass.arguments(context)
135
+
136
+ if own_arguments.any?
137
+ if inherited_arguments.any?
138
+ # Local definitions override inherited ones
139
+ inherited_arguments.merge(own_arguments)
140
+ else
141
+ own_arguments
142
+ end
143
+ else
144
+ inherited_arguments
145
+ end
146
+ end
147
+
148
+ def all_argument_definitions
149
+ all_defns = {}
150
+ ancestors.reverse_each do |ancestor|
151
+ if ancestor.respond_to?(:own_arguments)
152
+ all_defns.merge!(ancestor.own_arguments)
153
+ end
154
+ end
155
+ all_defns = all_defns.values
156
+ all_defns.flatten!
157
+ all_defns
158
+ end
159
+
160
+
161
+ def get_argument(argument_name, context = GraphQL::Query::NullContext)
162
+ warden = Warden.from_context(context)
163
+ for ancestor in ancestors
164
+ if ancestor.respond_to?(:own_arguments) &&
165
+ (a = ancestor.own_arguments[argument_name]) &&
166
+ (a = Warden.visible_entry?(:visible_argument?, a, context, warden))
167
+ return a
168
+ end
169
+ end
170
+ nil
134
171
  end
135
- else
136
- # might be nil if there are actually no arguments
137
- own_arguments_that_apply || own_arguments
138
172
  end
139
173
  end
140
174
 
141
- def all_argument_definitions
142
- if self.is_a?(Class)
143
- all_defns = {}
144
- ancestors.reverse_each do |ancestor|
145
- if ancestor.respond_to?(:own_arguments)
146
- all_defns.merge!(ancestor.own_arguments)
175
+ module FieldConfigured
176
+ def arguments(context = GraphQL::Query::NullContext)
177
+ own_arguments = super
178
+ if defined?(@resolver_class) && @resolver_class
179
+ inherited_arguments = @resolver_class.field_arguments(context)
180
+ if own_arguments.any?
181
+ if inherited_arguments.any?
182
+ inherited_arguments.merge(own_arguments)
183
+ else
184
+ own_arguments
185
+ end
186
+ else
187
+ inherited_arguments
147
188
  end
189
+ else
190
+ own_arguments
148
191
  end
149
- elsif defined?(@resolver_class) && @resolver_class
150
- all_defns = {}
151
- @resolver_class.all_field_argument_definitions.each do |arg_defn|
152
- key = arg_defn.graphql_name
153
- case (current_value = all_defns[key])
154
- when nil
155
- all_defns[key] = arg_defn
156
- when Array
157
- current_value << arg_defn
158
- when GraphQL::Schema::Argument
159
- all_defns[key] = [current_value, arg_defn]
160
- else
161
- raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}"
192
+ end
193
+
194
+ def all_argument_definitions
195
+ if defined?(@resolver_class) && @resolver_class
196
+ all_defns = {}
197
+ @resolver_class.all_field_argument_definitions.each do |arg_defn|
198
+ key = arg_defn.graphql_name
199
+ case (current_value = all_defns[key])
200
+ when nil
201
+ all_defns[key] = arg_defn
202
+ when Array
203
+ current_value << arg_defn
204
+ when GraphQL::Schema::Argument
205
+ all_defns[key] = [current_value, arg_defn]
206
+ else
207
+ raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}"
208
+ end
162
209
  end
210
+ all_defns.merge!(own_arguments)
211
+ all_defns = all_defns.values
212
+ all_defns.flatten!
213
+ all_defns
214
+ else
215
+ super
163
216
  end
164
- all_defns.merge!(own_arguments)
165
- else
166
- all_defns = own_arguments
167
217
  end
168
- all_defns = all_defns.values
218
+ end
219
+
220
+ def all_argument_definitions
221
+ all_defns = own_arguments.values
169
222
  all_defns.flatten!
170
223
  all_defns
171
224
  end
@@ -173,22 +226,11 @@ module GraphQL
173
226
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
174
227
  def get_argument(argument_name, context = GraphQL::Query::NullContext)
175
228
  warden = Warden.from_context(context)
176
- if !self.is_a?(Class)
177
- if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
178
- visible_arg
179
- elsif defined?(@resolver_class) && @resolver_class
180
- @resolver_class.get_field_argument(argument_name, context)
181
- else
182
- nil
183
- end
229
+ if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
230
+ visible_arg
231
+ elsif defined?(@resolver_class) && @resolver_class
232
+ @resolver_class.get_field_argument(argument_name, context)
184
233
  else
185
- for ancestor in ancestors
186
- if ancestor.respond_to?(:own_arguments) &&
187
- (a = ancestor.own_arguments[argument_name]) &&
188
- (a = Warden.visible_entry?(:visible_argument?, a, context, warden))
189
- return a
190
- end
191
- end
192
234
  nil
193
235
  end
194
236
  end
@@ -209,7 +251,7 @@ module GraphQL
209
251
  # @return [Interpreter::Arguments, Execution::Lazy<Interpeter::Arguments>]
210
252
  def coerce_arguments(parent_object, values, context, &block)
211
253
  # Cache this hash to avoid re-merging it
212
- arg_defns = self.arguments(context)
254
+ arg_defns = context.warden.arguments(self)
213
255
  total_args_count = arg_defns.size
214
256
 
215
257
  finished_args = nil
@@ -223,7 +265,7 @@ module GraphQL
223
265
  argument_values = {}
224
266
  resolved_args_count = 0
225
267
  raised_error = false
226
- arg_defns.each do |arg_name, arg_defn|
268
+ arg_defns.each do |arg_defn|
227
269
  context.dataloader.append_job do
228
270
  begin
229
271
  arg_defn.coerce_into_values(parent_object, values, context, argument_values)
@@ -265,7 +307,12 @@ module GraphQL
265
307
  # but not for directives.
266
308
  # TODO apply static validations on schema definitions?
267
309
  def validate_directive_argument(arg_defn, value)
268
- if arg_defn.owner.is_a?(Class) && arg_defn.owner < GraphQL::Schema::Directive
310
+ # this is only implemented on directives.
311
+ nil
312
+ end
313
+
314
+ module HasDirectiveArguments
315
+ def validate_directive_argument(arg_defn, value)
269
316
  if value.nil? && arg_defn.type.non_null?
270
317
  raise ArgumentError, "#{arg_defn.path} is required, but no value was given"
271
318
  end
@@ -5,17 +5,16 @@ module GraphQL
5
5
  class Member
6
6
  module HasDeprecationReason
7
7
  # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
8
- def deprecation_reason
9
- dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
10
- dir && dir.arguments[:reason] # rubocop:disable Development/ContextIsPassedCop -- definition-related
11
- end
8
+ attr_reader :deprecation_reason
12
9
 
13
10
  # Set the deprecation reason for this member, or remove it by assigning `nil`
14
11
  # @param text [String, nil]
15
12
  def deprecation_reason=(text)
13
+ @deprecation_reason = text
16
14
  if text.nil?
17
15
  remove_directive(GraphQL::Schema::Directive::Deprecated)
18
16
  else
17
+ # This removes a previously-attached directive, if there is one:
19
18
  directive(GraphQL::Schema::Directive::Deprecated, reason: text)
20
19
  end
21
20
  end
@@ -72,7 +72,12 @@ module GraphQL
72
72
  def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
73
73
  # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
74
74
  # that shows that no override value was given manually.
75
- if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym && field_defn.hash_key.nil? && field_defn.dig_keys.nil?
75
+ if method_conflict_warning &&
76
+ CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) &&
77
+ field_defn.original_name == field_defn.resolver_method &&
78
+ field_defn.original_name == field_defn.method_sym &&
79
+ field_defn.hash_key == NOT_CONFIGURED &&
80
+ field_defn.dig_keys.nil?
76
81
  warn(conflict_field_name_warning(field_defn))
77
82
  end
78
83
  prev_defn = own_fields[field_defn.name]
@@ -129,12 +134,19 @@ module GraphQL
129
134
 
130
135
  private
131
136
 
137
+ def inherited(subclass)
138
+ super
139
+ subclass.class_eval do
140
+ @own_fields ||= nil
141
+ end
142
+ end
143
+
132
144
  # If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible.
133
145
  def visible_interface_implementation?(type, context, warden)
134
146
  if type.respond_to?(:kind) && type.kind.interface?
135
147
  implements_this_interface = false
136
148
  implementation_is_visible = false
137
- interface_type_memberships.each do |tm|
149
+ warden.interface_type_memberships(self, context).each do |tm|
138
150
  if tm.abstract_type == type
139
151
  implements_this_interface ||= true
140
152
  if warden.visible_type_membership?(tm, context)
@@ -55,7 +55,38 @@ module GraphQL
55
55
  end
56
56
 
57
57
  def interface_type_memberships
58
- own_interface_type_memberships + ((self.is_a?(Class) && superclass.respond_to?(:interface_type_memberships)) ? superclass.interface_type_memberships : [])
58
+ own_interface_type_memberships
59
+ end
60
+
61
+ module ClassConfigured
62
+ # This combination of extended -> inherited -> extended
63
+ # means that the base class (`Schema::Object`) *won't*
64
+ # have the superclass-related code in `InheritedInterfaces`,
65
+ # but child classes of `Schema::Object` will have it.
66
+ # That way, we don't need a `superclass.respond_to?(...)` check.
67
+ def inherited(child_class)
68
+ super
69
+ child_class.extend(InheritedInterfaces)
70
+ end
71
+
72
+ module InheritedInterfaces
73
+ def interfaces(context = GraphQL::Query::NullContext)
74
+ visible_interfaces = super
75
+ visible_interfaces.concat(superclass.interfaces(context))
76
+ visible_interfaces.uniq!
77
+ visible_interfaces
78
+ end
79
+
80
+ def interface_type_memberships
81
+ own_tms = super
82
+ inherited_tms = superclass.interface_type_memberships
83
+ if inherited_tms.size > 0
84
+ own_tms + inherited_tms
85
+ else
86
+ own_tms
87
+ end
88
+ end
89
+ end
59
90
  end
60
91
 
61
92
  # param context [Query::Context] If omitted, skip filtering.
@@ -63,24 +94,34 @@ module GraphQL
63
94
  warden = Warden.from_context(context)
64
95
  visible_interfaces = []
65
96
  own_interface_type_memberships.each do |type_membership|
66
- # During initialization, `type_memberships` can hold late-bound types
67
97
  case type_membership
68
- when String, Schema::LateBoundType
69
- visible_interfaces << type_membership
70
98
  when Schema::TypeMembership
71
99
  if warden.visible_type_membership?(type_membership, context)
72
100
  visible_interfaces << type_membership.abstract_type
73
101
  end
102
+ when String, Schema::LateBoundType
103
+ # During initialization, `type_memberships` can hold late-bound types
104
+ visible_interfaces << type_membership
74
105
  else
75
106
  raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
76
107
  end
77
108
  end
109
+ visible_interfaces.uniq!
78
110
 
79
- if self.is_a?(Class) && superclass <= GraphQL::Schema::Object
80
- visible_interfaces.concat(superclass.interfaces(context))
81
- end
111
+ visible_interfaces
112
+ end
82
113
 
83
- visible_interfaces.uniq
114
+ private
115
+
116
+ def self.extended(child_class)
117
+ child_class.extend(ClassConfigured)
118
+ end
119
+
120
+ def inherited(subclass)
121
+ super
122
+ subclass.class_eval do
123
+ @own_interface_type_memberships ||= nil
124
+ end
84
125
  end
85
126
  end
86
127
  end
@@ -18,12 +18,38 @@ module GraphQL
18
18
 
19
19
  # @return [Array<GraphQL::Schema::Validator>]
20
20
  def validators
21
- own_validators = @own_validators || EMPTY_ARRAY
22
- if self.is_a?(Class) && superclass.respond_to?(:validators) && (inherited_validators = superclass.validators).any?
23
- inherited_validators + own_validators
24
- else
25
- own_validators
21
+ @own_validators || EMPTY_ARRAY
22
+ end
23
+
24
+ module ClassConfigured
25
+ def inherited(child_cls)
26
+ super
27
+ child_cls.extend(ClassValidators)
26
28
  end
29
+
30
+ module ClassValidators
31
+ include Schema::FindInheritedValue::EmptyObjects
32
+
33
+ def validators
34
+ inherited_validators = superclass.validators
35
+ if inherited_validators.any?
36
+ if @own_validators.nil?
37
+ inherited_validators
38
+ else
39
+ inherited_validators + @own_validators
40
+ end
41
+ elsif @own_validators.nil?
42
+ EMPTY_ARRAY
43
+ else
44
+ @own_validators
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def self.extended(child_cls)
51
+ super
52
+ child_cls.extend(ClassConfigured)
27
53
  end
28
54
  end
29
55
  end
@@ -4,6 +4,13 @@ module GraphQL
4
4
  class Schema
5
5
  class Member
6
6
  module TypeSystemHelpers
7
+ def initialize(*args, &block)
8
+ super
9
+ @to_non_null_type ||= nil
10
+ @to_list_type ||= nil
11
+ end
12
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
13
+
7
14
  # @return [Schema::NonNull] Make a non-null-type representation of this type
8
15
  def to_non_null_type
9
16
  @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
@@ -32,6 +39,16 @@ module GraphQL
32
39
  def kind
33
40
  raise GraphQL::RequiredImplementationMissingError, "No `.kind` defined for #{self}"
34
41
  end
42
+
43
+ private
44
+
45
+ def inherited(subclass)
46
+ super
47
+ subclass.class_eval do
48
+ @to_non_null_type ||= nil
49
+ @to_list_type ||= nil
50
+ end
51
+ end
35
52
  end
36
53
  end
37
54
  end
@@ -48,9 +48,7 @@ module GraphQL
48
48
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
49
49
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
50
50
  def authorized_new(object, context)
51
- trace_payload = { context: context, type: self, object: object, path: context[:current_path] }
52
-
53
- maybe_lazy_auth_val = context.query.trace("authorized", trace_payload) do
51
+ maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do
54
52
  begin
55
53
  authorized?(object, context)
56
54
  rescue GraphQL::UnauthorizedError => err
@@ -62,7 +60,7 @@ module GraphQL
62
60
 
63
61
  auth_val = if context.schema.lazy?(maybe_lazy_auth_val)
64
62
  GraphQL::Execution::Lazy.new do
65
- context.query.trace("authorized_lazy", trace_payload) do
63
+ context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do
66
64
  context.schema.sync_lazy(maybe_lazy_auth_val)
67
65
  end
68
66
  end
@@ -89,16 +89,16 @@ module GraphQL
89
89
  def generate_payload_type
90
90
  resolver_name = graphql_name
91
91
  resolver_fields = all_field_definitions
92
- Class.new(object_class) do
93
- graphql_name("#{resolver_name}Payload")
94
- description("Autogenerated return type of #{resolver_name}.")
95
- resolver_fields.each do |f|
96
- # Reattach the already-defined field here
97
- # (The field's `.owner` will still point to the mutation, not the object type, I think)
98
- # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
99
- add_field(f, method_conflict_warning: false)
100
- end
92
+ pt = Class.new(object_class)
93
+ pt.graphql_name("#{resolver_name}Payload")
94
+ pt.description("Autogenerated return type of #{resolver_name}.")
95
+ resolver_fields.each do |f|
96
+ # Reattach the already-defined field here
97
+ # (The field's `.owner` will still point to the mutation, not the object type, I think)
98
+ # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
99
+ pt.add_field(f, method_conflict_warning: false)
101
100
  end
101
+ pt
102
102
  end
103
103
  end
104
104
  end
@@ -33,60 +33,56 @@ module GraphQL
33
33
  # end
34
34
  #
35
35
  class Timeout
36
- def self.use(schema, **options)
37
- tracer = new(**options)
38
- schema.tracer(tracer)
36
+ def self.use(schema, max_seconds: nil)
37
+ timeout = self.new(max_seconds: max_seconds)
38
+ schema.trace_with(self::Trace, timeout: timeout)
39
39
  end
40
40
 
41
- # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
42
41
  def initialize(max_seconds:)
43
42
  @max_seconds = max_seconds
44
43
  end
45
44
 
46
- def trace(key, data)
47
- case key
48
- when 'execute_multiplex'
49
- data.fetch(:multiplex).queries.each do |query|
50
- timeout_duration_s = max_seconds(query)
45
+ module Trace
46
+ # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
47
+ def initialize(timeout:, **rest)
48
+ @timeout = timeout
49
+ super
50
+ end
51
+
52
+ def execute_multiplex(multiplex:)
53
+ multiplex.queries.each do |query|
54
+ timeout_duration_s = @timeout.max_seconds(query)
51
55
  timeout_state = if timeout_duration_s == false
52
56
  # if the method returns `false`, don't apply a timeout
53
57
  false
54
58
  else
55
59
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
56
- timeout_at = now + (max_seconds(query) * 1000)
60
+ timeout_at = now + (timeout_duration_s * 1000)
57
61
  {
58
62
  timeout_at: timeout_at,
59
63
  timed_out: false
60
64
  }
61
65
  end
62
- query.context.namespace(self.class)[:state] = timeout_state
66
+ query.context.namespace(@timeout)[:state] = timeout_state
63
67
  end
68
+ super
69
+ end
64
70
 
65
- yield
66
- when 'execute_field', 'execute_field_lazy'
67
- query_context = data[:context] || data[:query].context
68
- timeout_state = query_context.namespace(self.class).fetch(:state)
71
+ def execute_field(query:, field:, **_rest)
72
+ timeout_state = query.context.namespace(@timeout).fetch(:state)
69
73
  # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
70
74
  if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
71
- error = if data[:context]
72
- GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
73
- else
74
- field = data.fetch(:field)
75
- GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
76
- end
77
-
75
+ error = GraphQL::Schema::Timeout::TimeoutError.new(field)
78
76
  # Only invoke the timeout callback for the first timeout
79
77
  if !timeout_state[:timed_out]
80
78
  timeout_state[:timed_out] = true
81
- handle_timeout(error, query_context.query)
79
+ @timeout.handle_timeout(error, query)
82
80
  end
83
81
 
84
82
  error
85
83
  else
86
84
  yield
87
85
  end
88
- else
89
- yield
90
86
  end
91
87
  end
92
88
 
@@ -114,8 +110,8 @@ module GraphQL
114
110
  # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError},
115
111
  # such as `RuntimeError`.
116
112
  class TimeoutError < GraphQL::ExecutionError
117
- def initialize(parent_type, field)
118
- super("Timeout on #{parent_type.graphql_name}.#{field.graphql_name}")
113
+ def initialize(field)
114
+ super("Timeout on #{field.path}")
119
115
  end
120
116
  end
121
117
  end
@@ -80,6 +80,8 @@ module GraphQL
80
80
  def visible_type?(type, ctx); type.visible?(ctx); end
81
81
  def visible_enum_value?(ev, ctx); ev.visible?(ctx); end
82
82
  def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
83
+ def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
84
+ def arguments(owner, ctx); owner.arguments(ctx); end
83
85
  end
84
86
  end
85
87
 
@@ -94,6 +96,13 @@ module GraphQL
94
96
  @subscription = @schema.subscription
95
97
  @context = context
96
98
  @visibility_cache = read_through { |m| filter.call(m, context) }
99
+ # Initialize all ivars to improve object shape consistency:
100
+ @types = @visible_types = @reachable_types = @visible_parent_fields =
101
+ @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
102
+ @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
103
+ @visible_and_reachable_type = @unions = @unfiltered_interfaces = @references_to =
104
+ @reachable_type_set =
105
+ nil
97
106
  end
98
107
 
99
108
  # @return [Hash<String, GraphQL::BaseType>] Visible types in the schema
@@ -174,14 +183,20 @@ module GraphQL
174
183
 
175
184
  # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
176
185
  # @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
177
- def arguments(argument_owner)
178
- @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a) } }
186
+ def arguments(argument_owner, ctx = nil)
187
+ @visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a, @context) } }
179
188
  @visible_arguments[argument_owner]
180
189
  end
181
190
 
182
191
  # @return [Array<GraphQL::EnumType::EnumValue>] Visible members of `enum_defn`
183
192
  def enum_values(enum_defn)
184
- @visible_enum_arrays ||= read_through { |e| e.enum_values(@context) }
193
+ @visible_enum_arrays ||= read_through { |e|
194
+ values = e.enum_values(@context)
195
+ if values.size == 0
196
+ raise GraphQL::Schema::Enum::MissingValuesError.new(e)
197
+ end
198
+ values
199
+ }
185
200
  @visible_enum_arrays[enum_defn]
186
201
  end
187
202
 
@@ -233,6 +248,13 @@ module GraphQL
233
248
  visible?(type_membership)
234
249
  end
235
250
 
251
+ def interface_type_memberships(obj_type, _ctx = nil)
252
+ @type_memberships ||= read_through do |obj_t|
253
+ obj_t.interface_type_memberships
254
+ end
255
+ @type_memberships[obj_type]
256
+ end
257
+
236
258
  private
237
259
 
238
260
  def visible_and_reachable_type?(type_defn)
@@ -332,7 +354,7 @@ module GraphQL
332
354
  end
333
355
 
334
356
  def reachable_type_set
335
- return @reachable_type_set if defined?(@reachable_type_set)
357
+ return @reachable_type_set if @reachable_type_set
336
358
 
337
359
  @reachable_type_set = Set.new
338
360
  rt_hash = {}