graphql 2.0.14 → 2.0.16

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: d626d72a5ae0bfb10f8fcdb9cbd781f04b5bb38b3f90b20de062a78679c254a8
4
+ data.tar.gz: 6793ba627f3f59a43426258e187567f391a44f8973816fb0b1e1eb7784ae19b0
5
5
  SHA512:
6
- metadata.gz: 8f3aac93aa1013c257c84521b54285719d6edb8c212a83140123902a06f3cffabc1005d2a176b723cff5677614eab7ae1617b71ce7eb403e721fcbafbe07c1e1
7
- data.tar.gz: a84bf37728b5c0ff8795070fd682ac7af83e8888d47cadff5103a76ee333788c30bf78f505528fee4f868288b99dfc11d82f7c604710e13b4a7d9690eb2efe36
6
+ metadata.gz: 5958721b08e45d886036f8afe0a5942e443117d9aa71d94450b727ec20d13b63a945a97c247f8605d4eb1ae8631ba301599e95b938355f18dc7c5bf64e2b1b44
7
+ data.tar.gz: 9d029e7d0288c87cc9c09e627538b5e93b3d1ec7e785914b135088ec7bafcf8172458fa1d68a95a6385786d472c7260f6bc82b719e86743b5dac1f2a87501f7c
@@ -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)
@@ -93,7 +93,7 @@ module GraphQL
93
93
  class ScopedContext
94
94
  def initialize(query_context)
95
95
  @query_context = query_context
96
- @path_contexts = {}
96
+ @scoped_contexts = {}
97
97
  @no_path = [].freeze
98
98
  end
99
99
 
@@ -106,12 +106,17 @@ module GraphQL
106
106
  end
107
107
 
108
108
  def merge!(hash)
109
- current_ctx = @path_contexts[current_path] ||= {}
110
- current_ctx.merge!(hash)
109
+ ctx = @scoped_contexts
110
+ current_path.each do |path_part|
111
+ ctx = ctx[path_part] ||= { parent: ctx }
112
+ end
113
+ this_scoped_ctx = ctx[:scoped_context] ||= {}
114
+ this_scoped_ctx.merge!(hash)
111
115
  end
112
116
 
113
117
  def current_path
114
- @query_context.namespace(:interpreter)[:current_path] || @no_path
118
+ thread_info = Thread.current[:__graphql_runtime_info]
119
+ (thread_info && thread_info[:current_path]) || @no_path
115
120
  end
116
121
 
117
122
  def key?(key)
@@ -151,16 +156,20 @@ module GraphQL
151
156
  # Start at the current location,
152
157
  # but look up the tree for previously-assigned scoped values
153
158
  def each_present_path_ctx
154
- search_path = current_path.dup
155
- if (current_path_ctx = @path_contexts[search_path])
156
- yield(current_path_ctx)
159
+ ctx = @scoped_contexts
160
+ current_path.each do |path_part|
161
+ if ctx.key?(path_part)
162
+ ctx = ctx[path_part]
163
+ else
164
+ break
165
+ end
157
166
  end
158
167
 
159
- while search_path.size > 0
160
- search_path.pop # look one level higher
161
- if (search_path_ctx = @path_contexts[search_path])
162
- yield(search_path_ctx)
168
+ while ctx
169
+ if (scoped_ctx = ctx[:scoped_context])
170
+ yield(scoped_ctx)
163
171
  end
172
+ ctx = ctx[:parent]
164
173
  end
165
174
  end
166
175
  end
@@ -189,6 +198,7 @@ module GraphQL
189
198
 
190
199
  def_delegators :@query, :trace, :interpreter?
191
200
 
201
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
192
202
  # @!method []=(key, value)
193
203
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
194
204
 
@@ -196,8 +206,14 @@ module GraphQL
196
206
  def [](key)
197
207
  if @scoped_context.key?(key)
198
208
  @scoped_context[key]
199
- else
209
+ elsif @provided_values.key?(key)
200
210
  @provided_values[key]
211
+ elsif RUNTIME_METADATA_KEYS.include?(key)
212
+ thread_info = Thread.current[:__graphql_runtime_info]
213
+ thread_info && thread_info[key]
214
+ else
215
+ # not found
216
+ nil
201
217
  end
202
218
  end
203
219
 
@@ -212,7 +228,10 @@ module GraphQL
212
228
  UNSPECIFIED_FETCH_DEFAULT = Object.new
213
229
 
214
230
  def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
215
- if @scoped_context.key?(key)
231
+ if RUNTIME_METADATA_KEYS.include?(key)
232
+ (thread_info = Thread.current[:__graphql_runtime_info]) &&
233
+ thread_info[key]
234
+ elsif @scoped_context.key?(key)
216
235
  scoped_context[key]
217
236
  elsif @provided_values.key?(key)
218
237
  @provided_values[key]
@@ -226,7 +245,10 @@ module GraphQL
226
245
  end
227
246
 
228
247
  def dig(key, *other_keys)
229
- if @scoped_context.key?(key)
248
+ if RUNTIME_METADATA_KEYS.include?(key)
249
+ (thread_info = Thread.current[:__graphql_runtime_info]).key?(key) &&
250
+ thread_info.dig(key, *other_keys)
251
+ elsif @scoped_context.key?(key)
230
252
  @scoped_context.dig(key, *other_keys)
231
253
  else
232
254
  @provided_values.dig(key, *other_keys)
@@ -259,7 +281,11 @@ module GraphQL
259
281
  # @param ns [Object] a usage-specific namespace identifier
260
282
  # @return [Hash] namespaced storage
261
283
  def namespace(ns)
262
- @storage[ns]
284
+ if ns == :interpreter
285
+ self
286
+ else
287
+ @storage[ns]
288
+ end
263
289
  end
264
290
 
265
291
  # @return [Boolean] true if this namespace was accessed before
@@ -23,6 +23,10 @@ module GraphQL
23
23
  # @example Invoking the task from Ruby
24
24
  # require "rake"
25
25
  # Rake::Task["graphql:schema:dump"].invoke
26
+ #
27
+ # @example Providing arguments to build the introspection query
28
+ # require "graphql/rake_task"
29
+ # GraphQL::RakeTask.new(schema_name: "MySchema", include_is_one_of: true)
26
30
  class RakeTask
27
31
  include Rake::DSL
28
32
 
@@ -37,6 +41,11 @@ module GraphQL
37
41
  directory: ".",
38
42
  idl_outfile: "schema.graphql",
39
43
  json_outfile: "schema.json",
44
+ include_deprecated_args: true,
45
+ include_schema_description: false,
46
+ include_is_repeatable: false,
47
+ include_specified_by_url: false,
48
+ include_is_one_of: false
40
49
  }
41
50
 
42
51
  # @return [String] Namespace for generated tasks
@@ -74,6 +83,10 @@ module GraphQL
74
83
  # @return [String] directory for IDL & JSON files
75
84
  attr_accessor :directory
76
85
 
86
+ # @return [Boolean] Options for additional fields in the introspection query JSON response
87
+ # @see GraphQL::Schema.as_json
88
+ attr_accessor :include_deprecated_args, :include_schema_description, :include_is_repeatable, :include_specified_by_url, :include_is_one_of
89
+
77
90
  # Set the parameters of this task by passing keyword arguments
78
91
  # or assigning attributes inside the block
79
92
  def initialize(options = {})
@@ -96,7 +109,21 @@ module GraphQL
96
109
  def write_outfile(method_name, file)
97
110
  schema = @load_schema.call(self)
98
111
  context = @load_context.call(self)
99
- result = schema.public_send(method_name, only: @only, except: @except, context: context)
112
+ result = case method_name
113
+ when :to_json
114
+ schema.to_json(
115
+ include_is_one_of: include_is_one_of,
116
+ include_deprecated_args: include_deprecated_args,
117
+ include_is_repeatable: include_is_repeatable,
118
+ include_specified_by_url: include_specified_by_url,
119
+ include_schema_description: include_schema_description,
120
+ only: @only, except: @except, context: context
121
+ )
122
+ when :to_definition
123
+ schema.to_definition(only: @only, except: @except, context: context)
124
+ else
125
+ raise ArgumentError, "Unexpected schema dump method: #{method_name.inspect}"
126
+ end
100
127
  dir = File.dirname(file)
101
128
  FileUtils.mkdir_p(dir)
102
129
  if !result.end_with?("\n")
@@ -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
  #
@@ -70,9 +70,18 @@ module GraphQL
70
70
  private
71
71
 
72
72
  def assert_valid_union_member(type_defn)
73
- if type_defn.is_a?(Module) && !type_defn.is_a?(Class)
73
+ case type_defn
74
+ when Class
75
+ if !type_defn.kind.object?
76
+ raise ArgumentError, "Union possible_types can only be object types (not #{type_defn.kind.name}, #{type_defn.inspect})"
77
+ end
78
+ when Module
74
79
  # it's an interface type, defined as a module
75
80
  raise ArgumentError, "Union possible_types can only be object types (not interface types), remove #{type_defn.graphql_name} (#{type_defn.inspect})"
81
+ when String, GraphQL::Schema::LateBoundType
82
+ # Ok - assume it will get checked later
83
+ else
84
+ raise ArgumentError, "Union possible_types can only be class-based GraphQL types (not #{type_defn.inspect} (#{type_defn.class.name}))."
76
85
  end
77
86
  end
78
87
  end
@@ -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,
@@ -152,9 +154,22 @@ module GraphQL
152
154
  # @param context [Hash]
153
155
  # @param only [<#call(member, ctx)>]
154
156
  # @param except [<#call(member, ctx)>]
157
+ # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response
158
+ # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response
159
+ # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives
160
+ # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
161
+ # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
155
162
  # @return [Hash] GraphQL result
156
- def as_json(only: nil, except: nil, context: {})
157
- execute(Introspection.query(include_deprecated_args: true), only: only, except: except, context: context).to_h
163
+ def as_json(only: nil, except: nil, context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
164
+ introspection_query = Introspection.query(
165
+ include_deprecated_args: include_deprecated_args,
166
+ include_schema_description: include_schema_description,
167
+ include_is_repeatable: include_is_repeatable,
168
+ include_is_one_of: include_is_one_of,
169
+ include_specified_by_url: include_specified_by_url,
170
+ )
171
+
172
+ execute(introspection_query, only: only, except: except, context: context).to_h
158
173
  end
159
174
 
160
175
  # Return the GraphQL IDL for the schema
@@ -738,11 +753,10 @@ module GraphQL
738
753
  def handle_or_reraise(context, err)
739
754
  handler = Execution::Errors.find_handler_for(self, err.class)
740
755
  if handler
741
- runtime_info = context.namespace(:interpreter) || {}
742
- obj = runtime_info[:current_object]
743
- args = runtime_info[:current_arguments]
756
+ obj = context[:current_object]
757
+ args = context[:current_arguments]
744
758
  args = args && args.keyword_arguments
745
- field = runtime_info[:current_field]
759
+ field = context[:current_field]
746
760
  if obj.is_a?(GraphQL::Schema::Object)
747
761
  obj = obj.object
748
762
  end
@@ -816,6 +830,15 @@ module GraphQL
816
830
  member.accessible?(ctx)
817
831
  end
818
832
 
833
+ def schema_directive(dir_class, **options)
834
+ @own_schema_directives ||= []
835
+ Member::HasDirectives.add_directive(self, @own_schema_directives, dir_class, options)
836
+ end
837
+
838
+ def schema_directives
839
+ Member::HasDirectives.get_directives(self, @own_schema_directives, :schema_directives)
840
+ end
841
+
819
842
  # This hook is called when a client tries to access one or more
820
843
  # fields that fail the `accessible?` check.
821
844
  #
@@ -127,8 +127,14 @@ module GraphQL
127
127
  # same name as if they were the same name. If _any_ of the fragments
128
128
  # with that name has a dependency, we record it.
129
129
  independent_fragment_nodes = @defdep_fragment_definitions.values.flatten - @defdep_immediate_dependencies.keys
130
-
130
+ visited_fragment_names = Set.new
131
131
  while fragment_node = independent_fragment_nodes.pop
132
+ if visited_fragment_names.add?(fragment_node.name)
133
+ # this is a new fragment name
134
+ else
135
+ # this is a duplicate fragment name
136
+ next
137
+ end
132
138
  loops += 1
133
139
  if loops > max_loops
134
140
  raise("Resolution loops exceeded the number of definitions; infinite loop detected. (Max: #{max_loops}, Current: #{loops})")
@@ -91,7 +91,13 @@ module GraphQL
91
91
  # A per-process map of subscriptions to deliver.
92
92
  # This is provided by Rails, so let's use it
93
93
  @subscriptions = Concurrent::Map.new
94
- @events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } }
94
+ @events = Concurrent::Map.new do |h, k|
95
+ h.compute_if_absent(k) do
96
+ Concurrent::Map.new do |h2, k2|
97
+ h2.compute_if_absent(k2) { Concurrent::Array.new }
98
+ end
99
+ end
100
+ end
95
101
  @action_cable = action_cable
96
102
  @action_cable_coder = action_cable_coder
97
103
  @serializer = serializer
@@ -90,6 +90,7 @@ module GraphQL
90
90
  arguments: normalized_args,
91
91
  field: field,
92
92
  scope: scope,
93
+ context: context,
93
94
  )
94
95
  execute_all(event, object)
95
96
  end
@@ -124,6 +125,10 @@ module GraphQL
124
125
  variables: variables,
125
126
  root_value: object,
126
127
  }
128
+
129
+ # merge event's and query's context together
130
+ context.merge!(event.context) unless event.context.nil? || context.nil?
131
+
127
132
  execute_options[:validate] = validate_update?(**execute_options)
128
133
  result = @schema.execute(**execute_options)
129
134
  subscriptions_context = result.context.namespace(:subscriptions)
@@ -50,6 +50,8 @@ module GraphQL
50
50
  null: edges_nullable,
51
51
  description: "A list of edges.",
52
52
  connection: false,
53
+ # Assume that the connection was scoped before this step:
54
+ scope: false,
53
55
  }
54
56
 
55
57
  if field_options
@@ -135,6 +137,8 @@ module GraphQL
135
137
  null: nullable,
136
138
  description: "A list of nodes.",
137
139
  connection: false,
140
+ # Assume that the connection was scoped before this step:
141
+ scope: false,
138
142
  }
139
143
  if field_options
140
144
  base_field_options.merge!(field_options)
@@ -13,7 +13,13 @@ module GraphQL
13
13
  end
14
14
 
15
15
  def default_relay?
16
- !!@default_relay
16
+ if defined?(@default_relay)
17
+ @default_relay
18
+ elsif self.is_a?(Class)
19
+ superclass.respond_to?(:default_relay?) && superclass.default_relay?
20
+ else
21
+ false
22
+ end
17
23
  end
18
24
  end
19
25
  end
@@ -8,6 +8,7 @@ module GraphQL
8
8
  child_class.description("An edge in a connection.")
9
9
  child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.")
10
10
  child_class.extend(ClassMethods)
11
+ child_class.extend(GraphQL::Types::Relay::DefaultRelay)
11
12
  child_class.node_nullable(true)
12
13
  end
13
14
 
@@ -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.16"
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.16
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-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -597,7 +597,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
597
597
  - !ruby/object:Gem::Version
598
598
  version: '0'
599
599
  requirements: []
600
- rubygems_version: 3.2.33
600
+ rubygems_version: 3.3.3
601
601
  signing_key:
602
602
  specification_version: 4
603
603
  summary: A GraphQL language and runtime for Ruby