graphql 2.0.14 → 2.0.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3bc6610f88b6689ee6991a6ad3543580f9bdf1b3cb9f2f7e3f78862d76072f7
4
- data.tar.gz: ce52505d6c43e330aa8e5def38719f62bdfe067274a86530c889350c64994bd0
3
+ metadata.gz: 46b43157a331eae41dd54897d24dbc3adac190910237c7c1c158fc2bc32b6616
4
+ data.tar.gz: 118ac97b740ba1340d3e6b9aee5fa2f81ca25dc8769a98ec0d8e25d63e839711
5
5
  SHA512:
6
- metadata.gz: 8f3aac93aa1013c257c84521b54285719d6edb8c212a83140123902a06f3cffabc1005d2a176b723cff5677614eab7ae1617b71ce7eb403e721fcbafbe07c1e1
7
- data.tar.gz: a84bf37728b5c0ff8795070fd682ac7af83e8888d47cadff5103a76ee333788c30bf78f505528fee4f868288b99dfc11d82f7c604710e13b4a7d9690eb2efe36
6
+ metadata.gz: be38356ecdff7f8a9627bb021e83d13ea92e951b287386e76fd6e42ff05ef0a5a37ce9c98e6e4bf1834459989558f6c8b512c98e7642bef2c624578c1db24932
7
+ data.tar.gz: b0b75f342dca7a731b1b59e6399d364184152ceb0c5deb9fb4338a3582b3b6e38bf789a53839f6def9eb4b9c079c45e74675fb608a184dff4e6aeaf0ce052dfd
@@ -83,7 +83,7 @@ module GraphQL
83
83
  value = if top && @override_value
84
84
  @override_value
85
85
  else
86
- value_at(@context.query.context.namespace(:interpreter)[:runtime], context_entry.path)
86
+ value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path)
87
87
  end
88
88
  rows << [
89
89
  "#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
@@ -112,7 +112,7 @@ module GraphQL
112
112
  if object.is_a?(GraphQL::Schema::Object)
113
113
  object = object.object
114
114
  end
115
- value = value_at(context_entry.namespace(:interpreter)[:runtime], [])
115
+ value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], [])
116
116
  rows << [
117
117
  "#{position}",
118
118
  "#{op_type}#{op_name ? " #{op_name}" : ""}",
@@ -289,7 +289,10 @@ module GraphQL
289
289
  fiber_locals = {}
290
290
 
291
291
  Thread.current.keys.each do |fiber_var_key|
292
- fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
292
+ # This variable should be fresh in each new fiber
293
+ if fiber_var_key != :__graphql_runtime_info
294
+ fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
295
+ end
293
296
  end
294
297
 
295
298
  if @nonblocking
@@ -148,13 +148,23 @@ module GraphQL
148
148
  # @return [GraphQL::Query::Context]
149
149
  attr_reader :context
150
150
 
151
+ def thread_info
152
+ info = Thread.current[:__graphql_runtime_info]
153
+ if !info
154
+ new_ti = {}
155
+ info = Thread.current[:__graphql_runtime_info] = new_ti
156
+ end
157
+ info
158
+ end
159
+
151
160
  def initialize(query:)
152
161
  @query = query
153
162
  @dataloader = query.multiplex.dataloader
154
163
  @schema = query.schema
155
164
  @context = query.context
156
165
  @multiplex_context = query.multiplex.context
157
- @interpreter_context = @context.namespace(:interpreter)
166
+ # Start this off empty:
167
+ Thread.current[:__graphql_runtime_info] = nil
158
168
  @response = GraphQLResultHash.new(nil, nil)
159
169
  # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
160
170
  @runtime_directive_names = []
@@ -680,7 +690,11 @@ module GraphQL
680
690
 
681
691
  case current_type.kind.name
682
692
  when "SCALAR", "ENUM"
683
- r = current_type.coerce_result(value, context)
693
+ r = begin
694
+ current_type.coerce_result(value, context)
695
+ rescue StandardError => err
696
+ schema.handle_or_reraise(context, err)
697
+ end
684
698
  set_result(selection_result, result_name, r)
685
699
  r
686
700
  when "UNION", "INTERFACE"
@@ -870,17 +884,18 @@ module GraphQL
870
884
  end
871
885
 
872
886
  def set_all_interpreter_context(object, field, arguments, path)
887
+ ti = thread_info
873
888
  if object
874
- @context[:current_object] = @interpreter_context[:current_object] = object
889
+ ti[:current_object] = object
875
890
  end
876
891
  if field
877
- @context[:current_field] = @interpreter_context[:current_field] = field
892
+ ti[:current_field] = field
878
893
  end
879
894
  if arguments
880
- @context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
895
+ ti[:current_arguments] = arguments
881
896
  end
882
897
  if path
883
- @context[:current_path] = @interpreter_context[:current_path] = path
898
+ ti[:current_path] = path
884
899
  end
885
900
  end
886
901
 
@@ -940,13 +955,11 @@ module GraphQL
940
955
  # Set this pair in the Query context, but also in the interpeter namespace,
941
956
  # for compatibility.
942
957
  def set_interpreter_context(key, value)
943
- @interpreter_context[key] = value
944
- @context[key] = value
958
+ thread_info[key] = value
945
959
  end
946
960
 
947
961
  def delete_interpreter_context(key)
948
- @interpreter_context.delete(key)
949
- @context.delete(key)
962
+ (ti = thread_info) && ti.delete(key)
950
963
  end
951
964
 
952
965
  def resolve_type(type, value, path)
@@ -68,7 +68,7 @@ module GraphQL
68
68
  # they also have another item of state, which is private to that query
69
69
  # in particular, assign it here:
70
70
  runtime = Runtime.new(query: query)
71
- query.context.namespace(:interpreter)[:runtime] = runtime
71
+ query.context.namespace(:interpreter_runtime)[:runtime] = runtime
72
72
 
73
73
  query.trace("execute_query", {query: query}) do
74
74
  runtime.run_eager
@@ -90,7 +90,7 @@ module GraphQL
90
90
  query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
91
91
  queries = multiplex ? multiplex.queries : [query]
92
92
  final_values = queries.map do |query|
93
- runtime = query.context.namespace(:interpreter)[:runtime]
93
+ runtime = query.context.namespace(:interpreter_runtime)[:runtime]
94
94
  # it might not be present if the query has an error
95
95
  runtime ? runtime.final_result : nil
96
96
  end
@@ -99,7 +99,7 @@ module GraphQL
99
99
  Interpreter::Resolve.resolve_all(final_values, multiplex.dataloader)
100
100
  end
101
101
  queries.each do |query|
102
- runtime = query.context.namespace(:interpreter)[:runtime]
102
+ runtime = query.context.namespace(:interpreter_runtime)[:runtime]
103
103
  if runtime
104
104
  runtime.delete_interpreter_context(:current_path)
105
105
  runtime.delete_interpreter_context(:current_field)
@@ -123,7 +123,7 @@ module GraphQL
123
123
  end
124
124
  else
125
125
  result = {
126
- "data" => query.context.namespace(:interpreter)[:runtime].final_result
126
+ "data" => query.context.namespace(:interpreter_runtime)[:runtime].final_result
127
127
  }
128
128
 
129
129
  if query.context.errors.any?
@@ -76,8 +76,8 @@ module GraphQL
76
76
  # @param field_name [String, Symbol]
77
77
  # @param arguments [Hash] Arguments which must match in the selection
78
78
  # @return [Boolean]
79
- def selects?(field_name, arguments: nil)
80
- selection(field_name, arguments: arguments).selected?
79
+ def selects?(field_name, selected_type: @selected_type, arguments: nil)
80
+ selection(field_name, selected_type: selected_type, arguments: arguments).selected?
81
81
  end
82
82
 
83
83
  # @return [Boolean] True if this lookahead represents a field that was requested
@@ -95,11 +95,22 @@ module GraphQL
95
95
  @query.get_field(selected_type, field_name)
96
96
  when Symbol
97
97
  # Try to avoid the `.to_s` below, if possible
98
- all_fields = @query.warden.fields(selected_type)
98
+ all_fields = if selected_type.kind.fields?
99
+ @query.warden.fields(selected_type)
100
+ else
101
+ # Handle unions by checking possible
102
+ @query.warden
103
+ .possible_types(selected_type)
104
+ .map { |t| @query.warden.fields(t) }
105
+ .flatten
106
+ end
107
+
99
108
  if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name })
100
109
  match_by_orig_name
101
110
  else
102
- guessed_name = Schema::Member::BuildType.camelize(field_name.to_s)
111
+ # Symbol#name is only present on 3.0+
112
+ sym_s = field_name.respond_to?(:name) ? field_name.name : field_name.to_s
113
+ guessed_name = Schema::Member::BuildType.camelize(sym_s)
103
114
  @query.get_field(selected_type, guessed_name)
104
115
  end
105
116
  end
@@ -4,7 +4,7 @@ module GraphQL
4
4
  def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
5
5
  # The introspection query to end all introspection queries, copied from
6
6
  # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js
7
- <<-QUERY
7
+ <<-QUERY.gsub(/\n{2,}/, "\n")
8
8
  query IntrospectionQuery {
9
9
  __schema {
10
10
  #{include_schema_description ? "description" : ""}
@@ -44,16 +44,18 @@ module GraphQL
44
44
  end
45
45
 
46
46
  def build_schema_node
47
- GraphQL::Language::Nodes::SchemaDefinition.new(
48
- query: (q = warden.root_type_for_operation("query")) && q.graphql_name,
49
- mutation: (m = warden.root_type_for_operation("mutation")) && m.graphql_name,
50
- subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name,
51
- # This only supports directives from parsing,
52
- # use a custom printer to add to this list.
53
- #
47
+ schema_options = {
54
48
  # `@schema.directives` is covered by `build_definition_nodes`
55
- directives: ast_directives(@schema),
56
- )
49
+ directives: definition_directives(@schema, :schema_directives),
50
+ }
51
+ if !schema_respects_root_name_conventions?(@schema)
52
+ schema_options.merge!({
53
+ query: (q = warden.root_type_for_operation("query")) && q.graphql_name,
54
+ mutation: (m = warden.root_type_for_operation("mutation")) && m.graphql_name,
55
+ subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name,
56
+ })
57
+ end
58
+ GraphQL::Language::Nodes::SchemaDefinition.new(schema_options)
57
59
  end
58
60
 
59
61
  def build_object_type_node(object_type)
@@ -283,7 +285,9 @@ module GraphQL
283
285
  private
284
286
 
285
287
  def include_schema_node?
286
- always_include_schema || !schema_respects_root_name_conventions?(schema)
288
+ always_include_schema ||
289
+ !schema_respects_root_name_conventions?(schema) ||
290
+ !schema.schema_directives.empty?
287
291
  end
288
292
 
289
293
  def schema_respects_root_name_conventions?(schema)
@@ -293,14 +297,14 @@ module GraphQL
293
297
  end
294
298
 
295
299
  def directives(member)
296
- definition_directives(member)
300
+ definition_directives(member, :directives)
297
301
  end
298
302
 
299
- def definition_directives(member)
300
- dirs = if !member.respond_to?(:directives) || member.directives.empty?
303
+ def definition_directives(member, directives_method)
304
+ dirs = if !member.respond_to?(directives_method) || member.directives.empty?
301
305
  []
302
306
  else
303
- member.directives.map do |dir|
307
+ member.public_send(directives_method).map do |dir|
304
308
  args = []
305
309
  dir.arguments.argument_values.each_value do |arg_value| # rubocop:disable Development/ContextIsPassedCop -- directive instance method
306
310
  arg_defn = arg_value.definition
@@ -324,10 +328,6 @@ module GraphQL
324
328
  dirs
325
329
  end
326
330
 
327
- def ast_directives(member)
328
- member.ast_node ? member.ast_node.directives : []
329
- end
330
-
331
331
  attr_reader :schema, :warden, :always_include_schema,
332
332
  :include_introspection_types, :include_built_in_directives, :include_built_in_scalars
333
333
  end
@@ -132,10 +132,11 @@ module GraphQL
132
132
  end
133
133
 
134
134
  def print_schema_definition(schema)
135
- if (schema.query.nil? || schema.query == 'Query') &&
136
- (schema.mutation.nil? || schema.mutation == 'Mutation') &&
137
- (schema.subscription.nil? || schema.subscription == 'Subscription') &&
138
- (schema.directives.empty?)
135
+ has_conventional_names = (schema.query.nil? || schema.query == 'Query') &&
136
+ (schema.mutation.nil? || schema.mutation == 'Mutation') &&
137
+ (schema.subscription.nil? || schema.subscription == 'Subscription')
138
+
139
+ if has_conventional_names && schema.directives.empty?
139
140
  return
140
141
  end
141
142
 
@@ -145,14 +146,22 @@ module GraphQL
145
146
  out << "\n "
146
147
  out << print_node(dir)
147
148
  end
148
- out << "\n{"
149
- else
150
- out << " {\n"
149
+ if !has_conventional_names
150
+ out << "\n"
151
+ end
151
152
  end
152
- out << " query: #{schema.query}\n" if schema.query
153
- out << " mutation: #{schema.mutation}\n" if schema.mutation
154
- out << " subscription: #{schema.subscription}\n" if schema.subscription
155
- out << "}"
153
+
154
+ if !has_conventional_names
155
+ if schema.directives.empty?
156
+ out << " "
157
+ end
158
+ out << "{\n"
159
+ out << " query: #{schema.query}\n" if schema.query
160
+ out << " mutation: #{schema.mutation}\n" if schema.mutation
161
+ out << " subscription: #{schema.subscription}\n" if schema.subscription
162
+ out << "}"
163
+ end
164
+ out
156
165
  end
157
166
 
158
167
  def print_scalar_type_definition(scalar_type)
@@ -111,7 +111,7 @@ module GraphQL
111
111
  end
112
112
 
113
113
  def current_path
114
- @query_context.namespace(:interpreter)[:current_path] || @no_path
114
+ @query_context[:current_path] || @no_path
115
115
  end
116
116
 
117
117
  def key?(key)
@@ -189,12 +189,16 @@ module GraphQL
189
189
 
190
190
  def_delegators :@query, :trace, :interpreter?
191
191
 
192
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
192
193
  # @!method []=(key, value)
193
194
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
194
195
 
195
196
  # Lookup `key` from the hash passed to {Schema#execute} as `context:`
196
197
  def [](key)
197
- if @scoped_context.key?(key)
198
+ if RUNTIME_METADATA_KEYS.include?(key)
199
+ thread_info = Thread.current[:__graphql_runtime_info]
200
+ thread_info && thread_info[key]
201
+ elsif @scoped_context.key?(key)
198
202
  @scoped_context[key]
199
203
  else
200
204
  @provided_values[key]
@@ -212,7 +216,10 @@ module GraphQL
212
216
  UNSPECIFIED_FETCH_DEFAULT = Object.new
213
217
 
214
218
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
215
- if @scoped_context.key?(key)
219
+ if RUNTIME_METADATA_KEYS.include?(key)
220
+ (thread_info = Thread.current[:__graphql_runtime_info]) &&
221
+ thread_info[key]
222
+ elsif @scoped_context.key?(key)
216
223
  scoped_context[key]
217
224
  elsif @provided_values.key?(key)
218
225
  @provided_values[key]
@@ -226,7 +233,10 @@ module GraphQL
226
233
  end
227
234
 
228
235
  def dig(key, *other_keys)
229
- if @scoped_context.key?(key)
236
+ if RUNTIME_METADATA_KEYS.include?(key)
237
+ (thread_info = Thread.current[:__graphql_runtime_info]).key?(key) &&
238
+ thread_info.dig(key)
239
+ elsif @scoped_context.key?(key)
230
240
  @scoped_context.dig(key, *other_keys)
231
241
  else
232
242
  @provided_values.dig(key, *other_keys)
@@ -259,7 +269,11 @@ module GraphQL
259
269
  # @param ns [Object] a usage-specific namespace identifier
260
270
  # @return [Hash] namespaced storage
261
271
  def namespace(ns)
262
- @storage[ns]
272
+ if ns == :interpreter
273
+ self
274
+ else
275
+ @storage[ns]
276
+ end
263
277
  end
264
278
 
265
279
  # @return [Boolean] true if this namespace was accessed before
@@ -6,16 +6,16 @@ module GraphQL
6
6
  module BuildFromDefinition
7
7
  class << self
8
8
  # @see {Schema.from_definition}
9
- def from_definition(definition_string, parser: GraphQL.default_parser, **kwargs)
10
- from_document(parser.parse(definition_string), **kwargs)
9
+ def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs)
10
+ from_document(schema_superclass, parser.parse(definition_string), **kwargs)
11
11
  end
12
12
 
13
- def from_definition_path(definition_path, parser: GraphQL.default_parser, **kwargs)
14
- from_document(parser.parse_file(definition_path), **kwargs)
13
+ def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs)
14
+ from_document(schema_superclass, parser.parse_file(definition_path), **kwargs)
15
15
  end
16
16
 
17
- def from_document(document, default_resolve:, using: {}, relay: false)
18
- Builder.build(document, default_resolve: default_resolve || {}, relay: relay, using: using)
17
+ def from_document(schema_superclass, document, default_resolve:, using: {}, relay: false)
18
+ Builder.build(schema_superclass, document, default_resolve: default_resolve || {}, relay: relay, using: using)
19
19
  end
20
20
  end
21
21
 
@@ -23,7 +23,7 @@ module GraphQL
23
23
  module Builder
24
24
  extend self
25
25
 
26
- def build(document, default_resolve:, using: {}, relay:)
26
+ def build(schema_superclass, document, default_resolve:, using: {}, relay:)
27
27
  raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document)
28
28
 
29
29
  if default_resolve.is_a?(Hash)
@@ -36,7 +36,7 @@ module GraphQL
36
36
  end
37
37
  schema_definition = schema_defns.first
38
38
  types = {}
39
- directives = {}
39
+ directives = schema_superclass.directives.dup
40
40
  type_resolver = build_resolve_type(types, directives, ->(type_name) { types[type_name] ||= Schema::LateBoundType.new(type_name)})
41
41
  # Make a different type resolver because we need to coerce directive arguments
42
42
  # _while_ building the schema.
@@ -65,10 +65,14 @@ module GraphQL
65
65
  # In case any directives referenced built-in types for their arguments:
66
66
  replace_late_bound_types_with_built_in(types)
67
67
 
68
+ schema_extensions = nil
68
69
  document.definitions.each do |definition|
69
70
  case definition
70
71
  when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
71
72
  nil # already handled
73
+ when GraphQL::Language::Nodes::SchemaExtension
74
+ schema_extensions ||= []
75
+ schema_extensions << definition
72
76
  else
73
77
  # It's possible that this was already loaded by the directives
74
78
  prev_type = types[definition.name]
@@ -103,7 +107,7 @@ module GraphQL
103
107
 
104
108
  raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type
105
109
 
106
- Class.new(GraphQL::Schema) do
110
+ schema_class = Class.new(schema_superclass) do
107
111
  begin
108
112
  # Add these first so that there's some chance of resolving late-bound types
109
113
  orphan_types types.values
@@ -157,6 +161,14 @@ module GraphQL
157
161
  child_class.definition_default_resolve = self.definition_default_resolve
158
162
  end
159
163
  end
164
+
165
+ if schema_extensions
166
+ schema_extensions.each do |ext|
167
+ build_directives(schema_class, ext, type_resolver)
168
+ end
169
+ end
170
+
171
+ schema_class
160
172
  end
161
173
 
162
174
  NullResolveType = ->(type, obj, ctx) {
@@ -196,13 +208,18 @@ module GraphQL
196
208
 
197
209
  def build_directives(definition, ast_node, type_resolver)
198
210
  dirs = prepare_directives(ast_node, type_resolver)
199
- dirs.each do |dir_class, options|
200
- definition.directive(dir_class, **options)
211
+ dirs.each do |(dir_class, options)|
212
+ if definition.respond_to?(:schema_directive)
213
+ # it's a schema
214
+ definition.schema_directive(dir_class, **options)
215
+ else
216
+ definition.directive(dir_class, **options)
217
+ end
201
218
  end
202
219
  end
203
220
 
204
221
  def prepare_directives(ast_node, type_resolver)
205
- dirs = {}
222
+ dirs = []
206
223
  ast_node.directives.each do |dir_node|
207
224
  if dir_node.name == "deprecated"
208
225
  # This is handled using `deprecation_reason`
@@ -210,10 +227,10 @@ module GraphQL
210
227
  else
211
228
  dir_class = type_resolver.call(dir_node.name)
212
229
  if dir_class.nil?
213
- raise ArgumentError, "No definition for @#{dir_node.name} on #{ast_node.name} at #{ast_node.line}:#{ast_node.col}"
230
+ raise ArgumentError, "No definition for @#{dir_node.name} #{ast_node.respond_to?(:name) ? "on #{ast_node.name} " : ""}at #{ast_node.line}:#{ast_node.col}"
214
231
  end
215
232
  options = args_to_kwargs(dir_class, dir_node)
216
- dirs[dir_class] = options
233
+ dirs << [dir_class, options]
217
234
  end
218
235
  end
219
236
  dirs
@@ -389,7 +406,6 @@ module GraphQL
389
406
  graphql_name(interface_type_definition.name)
390
407
  description(interface_type_definition.description)
391
408
  interface_type_definition.interfaces.each do |interface_name|
392
- "Implements: #{interface_type_definition} -> #{interface_name}"
393
409
  interface_defn = type_resolver.call(interface_name)
394
410
  implements(interface_defn)
395
411
  end
@@ -39,7 +39,7 @@ module GraphQL
39
39
  transform_name = arguments[:by]
40
40
  if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name)
41
41
  return_value = return_value.public_send(transform_name)
42
- response = context.namespace(:interpreter)[:runtime].final_result
42
+ response = context.namespace(:interpreter_runtime)[:runtime].final_result
43
43
  *keys, last = path
44
44
  keys.each do |key|
45
45
  if response && (response = response[key])
@@ -129,10 +129,10 @@ module GraphQL
129
129
  def connection?
130
130
  if @connection.nil?
131
131
  # Provide default based on type name
132
- return_type_name = if @resolver_class && @resolver_class.type
133
- Member::BuildType.to_type_name(@resolver_class.type)
134
- elsif @return_type_expr
132
+ return_type_name = if @return_type_expr
135
133
  Member::BuildType.to_type_name(@return_type_expr)
134
+ elsif @resolver_class && @resolver_class.type
135
+ Member::BuildType.to_type_name(@resolver_class.type)
136
136
  else
137
137
  # As a last ditch, try to force loading the return type:
138
138
  type.unwrap.name
@@ -120,7 +120,7 @@ module GraphQL
120
120
  def camelize(string)
121
121
  return string if string == '_'
122
122
  return string unless string.include?("_")
123
- camelized = string.split('_').map(&:capitalize).join
123
+ camelized = string.split('_').each(&:capitalize!).join
124
124
  camelized[0] = camelized[0].downcase
125
125
  if (match_data = string.match(/\A(_+)/))
126
126
  camelized = "#{match_data[0]}#{camelized}"
@@ -11,8 +11,7 @@ module GraphQL
11
11
  # @return [void]
12
12
  def directive(dir_class, **options)
13
13
  @own_directives ||= []
14
- remove_directive(dir_class) unless dir_class.repeatable?
15
- @own_directives << dir_class.new(self, **options)
14
+ HasDirectives.add_directive(self, @own_directives, dir_class, options)
16
15
  nil
17
16
  end
18
17
 
@@ -20,78 +19,94 @@ module GraphQL
20
19
  # @param dir_class [Class<GraphQL::Schema::Directive>]
21
20
  # @return [viod]
22
21
  def remove_directive(dir_class)
23
- @own_directives && @own_directives.reject! { |d| d.is_a?(dir_class) }
22
+ HasDirectives.remove_directive(@own_directives, dir_class)
24
23
  nil
25
24
  end
26
25
 
27
26
  NO_DIRECTIVES = [].freeze
28
27
 
29
28
  def directives
30
- case self
31
- when Class
32
- inherited_directives = if superclass.respond_to?(:directives)
33
- superclass.directives
34
- else
35
- NO_DIRECTIVES
36
- end
37
- if inherited_directives.any? && @own_directives
38
- dirs = []
39
- merge_directives(dirs, inherited_directives)
40
- merge_directives(dirs, @own_directives)
41
- dirs
42
- elsif @own_directives
43
- @own_directives
44
- elsif inherited_directives.any?
45
- inherited_directives
46
- else
47
- NO_DIRECTIVES
48
- end
49
- when Module
50
- dirs = nil
51
- self.ancestors.reverse_each do |ancestor|
52
- if ancestor.respond_to?(:own_directives) &&
53
- (anc_dirs = ancestor.own_directives).any?
29
+ HasDirectives.get_directives(self, @own_directives, :directives)
30
+ end
31
+
32
+ class << self
33
+ def add_directive(schema_member, directives, directive_class, directive_options)
34
+ remove_directive(directives, directive_class) unless directive_class.repeatable?
35
+ directives << directive_class.new(schema_member, **directive_options)
36
+ end
37
+
38
+ def remove_directive(directives, directive_class)
39
+ directives && directives.reject! { |d| d.is_a?(directive_class) }
40
+ end
41
+
42
+ def get_directives(schema_member, directives, directives_method)
43
+ case schema_member
44
+ when Class
45
+ inherited_directives = if schema_member.superclass.respond_to?(directives_method)
46
+ get_directives(schema_member.superclass, schema_member.superclass.public_send(directives_method), directives_method)
47
+ else
48
+ NO_DIRECTIVES
49
+ end
50
+ if inherited_directives.any? && directives
51
+ dirs = []
52
+ merge_directives(dirs, inherited_directives)
53
+ merge_directives(dirs, directives)
54
+ dirs
55
+ elsif directives
56
+ directives
57
+ elsif inherited_directives.any?
58
+ inherited_directives
59
+ else
60
+ NO_DIRECTIVES
61
+ end
62
+ when Module
63
+ dirs = nil
64
+ schema_member.ancestors.reverse_each do |ancestor|
65
+ if ancestor.respond_to?(:own_directives) &&
66
+ (anc_dirs = ancestor.own_directives).any?
67
+ dirs ||= []
68
+ merge_directives(dirs, anc_dirs)
69
+ end
70
+ end
71
+ if directives
54
72
  dirs ||= []
55
- merge_directives(dirs, anc_dirs)
73
+ merge_directives(dirs, directives)
56
74
  end
75
+ dirs || NO_DIRECTIVES
76
+ when HasDirectives
77
+ directives || NO_DIRECTIVES
78
+ else
79
+ raise "Invariant: how could #{schema_member} not be a Class, Module, or instance of HasDirectives?"
57
80
  end
58
- if own_directives
59
- dirs ||= []
60
- merge_directives(dirs, own_directives)
81
+ end
82
+
83
+ private
84
+
85
+ # Modify `target` by adding items from `dirs` such that:
86
+ # - Any name conflict is overriden by the incoming member of `dirs`
87
+ # - Any other member of `dirs` is appended
88
+ # @param target [Array<GraphQL::Schema::Directive>]
89
+ # @param dirs [Array<GraphQL::Schema::Directive>]
90
+ # @return [void]
91
+ def merge_directives(target, dirs)
92
+ dirs.each do |dir|
93
+ if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
94
+ target.slice!(idx)
95
+ target.insert(idx, dir)
96
+ else
97
+ target << dir
98
+ end
61
99
  end
62
- dirs || NO_DIRECTIVES
63
- when HasDirectives
64
- @own_directives || NO_DIRECTIVES
65
- else
66
- raise "Invariant: how could #{self} not be a Class, Module, or instance of HasDirectives?"
100
+ nil
67
101
  end
68
102
  end
69
103
 
104
+
70
105
  protected
71
106
 
72
107
  def own_directives
73
108
  @own_directives
74
109
  end
75
-
76
- private
77
-
78
- # Modify `target` by adding items from `dirs` such that:
79
- # - Any name conflict is overriden by the incoming member of `dirs`
80
- # - Any other member of `dirs` is appended
81
- # @param target [Array<GraphQL::Schema::Directive>]
82
- # @param dirs [Array<GraphQL::Schema::Directive>]
83
- # @return [void]
84
- def merge_directives(target, dirs)
85
- dirs.each do |dir|
86
- if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
87
- target.slice!(idx)
88
- target.insert(idx, dir)
89
- else
90
- target << dir
91
- end
92
- end
93
- nil
94
- end
95
110
  end
96
111
  end
97
112
  end
@@ -91,7 +91,7 @@ module GraphQL
91
91
  resolver_fields = all_field_definitions
92
92
  Class.new(object_class) do
93
93
  graphql_name("#{resolver_name}Payload")
94
- description("Autogenerated return type of #{resolver_name}")
94
+ description("Autogenerated return type of #{resolver_name}.")
95
95
  resolver_fields.each do |f|
96
96
  # Reattach the already-defined field here
97
97
  # (The field's `.owner` will still point to the mutation, not the object type, I think)
@@ -11,6 +11,9 @@ module GraphQL
11
11
  # @return [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
12
12
  attr_reader :abstract_type
13
13
 
14
+ # @return [Hash]
15
+ attr_reader :options
16
+
14
17
  # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types}
15
18
  # or {Schema::Object.implements} (for interfaces).
16
19
  #
@@ -110,9 +110,10 @@ module GraphQL
110
110
  # @param using [Hash] Plugins to attach to the created schema with `use(key, value)`
111
111
  # @return [Class] the schema described by `document`
112
112
  def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {})
113
- # If the file ends in `.graphql`, treat it like a filepath
114
- if definition_or_path.end_with?(".graphql")
113
+ # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath
114
+ if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls")
115
115
  GraphQL::Schema::BuildFromDefinition.from_definition_path(
116
+ self,
116
117
  definition_or_path,
117
118
  default_resolve: default_resolve,
118
119
  parser: parser,
@@ -120,6 +121,7 @@ module GraphQL
120
121
  )
121
122
  else
122
123
  GraphQL::Schema::BuildFromDefinition.from_definition(
124
+ self,
123
125
  definition_or_path,
124
126
  default_resolve: default_resolve,
125
127
  parser: parser,
@@ -738,11 +740,10 @@ module GraphQL
738
740
  def handle_or_reraise(context, err)
739
741
  handler = Execution::Errors.find_handler_for(self, err.class)
740
742
  if handler
741
- runtime_info = context.namespace(:interpreter) || {}
742
- obj = runtime_info[:current_object]
743
- args = runtime_info[:current_arguments]
743
+ obj = context[:current_object]
744
+ args = context[:current_arguments]
744
745
  args = args && args.keyword_arguments
745
- field = runtime_info[:current_field]
746
+ field = context[:current_field]
746
747
  if obj.is_a?(GraphQL::Schema::Object)
747
748
  obj = obj.object
748
749
  end
@@ -816,6 +817,15 @@ module GraphQL
816
817
  member.accessible?(ctx)
817
818
  end
818
819
 
820
+ def schema_directive(dir_class, **options)
821
+ @own_schema_directives ||= []
822
+ Member::HasDirectives.add_directive(self, @own_schema_directives, dir_class, options)
823
+ end
824
+
825
+ def schema_directives
826
+ Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
827
+ end
828
+
819
829
  # This hook is called when a client tries to access one or more
820
830
  # fields that fail the `accessible?` check.
821
831
  #
@@ -0,0 +1,83 @@
1
+ module GraphQL
2
+ module Tracing
3
+ class InstrumentationTracing
4
+ def initialize(schema)
5
+ @schema = schema
6
+ end
7
+
8
+ def trace(event, data)
9
+ instrumenters = nil
10
+ query_instrumenters = nil
11
+ case event
12
+ when "execute_multiplex"
13
+ instrumenters = @schema.instrumenters
14
+ multiplex_instrumenters = instrumenters[:multiplex]
15
+ query_instrumenters = instrumenters[:query]
16
+ call_hooks(multiplex_instrumenters, data[:multiplex], :before_multiplex, :after_multiplex) {
17
+ each_query_call_hooks(query_instrumenters, data[:multiplex].queries) {
18
+ yield
19
+ }
20
+ }
21
+ else
22
+ yield
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # Call the before_ hooks of each query,
29
+ # Then yield if no errors.
30
+ # `call_hooks` takes care of appropriate cleanup.
31
+ def each_query_call_hooks(instrumenters, queries, i = 0)
32
+ if i >= queries.length
33
+ yield
34
+ else
35
+ query = queries[i]
36
+ call_hooks(instrumenters, query, :before_query, :after_query) {
37
+ each_query_call_hooks(instrumenters, queries, i + 1) {
38
+ yield
39
+ }
40
+ }
41
+ end
42
+ end
43
+
44
+ # Call each before hook, and if they all succeed, yield.
45
+ # If they don't all succeed, call after_ for each one that succeeded.
46
+ def call_hooks(instrumenters, object, before_hook_name, after_hook_name)
47
+ begin
48
+ successful = []
49
+ instrumenters.each do |instrumenter|
50
+ instrumenter.public_send(before_hook_name, object)
51
+ successful << instrumenter
52
+ end
53
+
54
+ # if any before hooks raise an exception, quit calling before hooks,
55
+ # but call the after hooks on anything that succeeded but also
56
+ # raise the exception that came from the before hook.
57
+ rescue GraphQL::ExecutionError => err
58
+ object.context.errors << err
59
+ rescue => e
60
+ raise call_after_hooks(successful, object, after_hook_name, e)
61
+ end
62
+
63
+ begin
64
+ yield # Call the user code
65
+ ensure
66
+ ex = call_after_hooks(successful, object, after_hook_name, nil)
67
+ raise ex if ex
68
+ end
69
+ end
70
+
71
+ def call_after_hooks(instrumenters, object, after_hook_name, ex)
72
+ instrumenters.reverse_each do |instrumenter|
73
+ begin
74
+ instrumenter.public_send(after_hook_name, object)
75
+ rescue => e
76
+ ex = e
77
+ end
78
+ end
79
+ ex
80
+ end
81
+ end
82
+ end
83
+ end
@@ -11,7 +11,7 @@ module GraphQL
11
11
  end
12
12
 
13
13
  def default_global_id
14
- context.schema.id_from_object(object, self, context)
14
+ context.schema.id_from_object(object, self.class, context)
15
15
  end
16
16
  end
17
17
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.14"
3
+ VERSION = "2.0.15"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.14
4
+ version: 2.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-08 00:00:00.000000000 Z
11
+ date: 2022-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -538,6 +538,7 @@ files:
538
538
  - lib/graphql/tracing/appoptics_tracing.rb
539
539
  - lib/graphql/tracing/appsignal_tracing.rb
540
540
  - lib/graphql/tracing/data_dog_tracing.rb
541
+ - lib/graphql/tracing/instrumentation_tracing.rb
541
542
  - lib/graphql/tracing/new_relic_tracing.rb
542
543
  - lib/graphql/tracing/notifications_tracing.rb
543
544
  - lib/graphql/tracing/platform_tracing.rb