graphql 1.9.16 → 1.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +8 -0
  3. data/lib/graphql/argument.rb +2 -2
  4. data/lib/graphql/define/assign_object_field.rb +2 -2
  5. data/lib/graphql/define/defined_object_proxy.rb +3 -0
  6. data/lib/graphql/define/instance_definable.rb +14 -3
  7. data/lib/graphql/execution/errors.rb +15 -14
  8. data/lib/graphql/execution/execute.rb +1 -1
  9. data/lib/graphql/execution/interpreter/runtime.rb +39 -17
  10. data/lib/graphql/execution/multiplex.rb +3 -3
  11. data/lib/graphql/introspection/entry_points.rb +2 -1
  12. data/lib/graphql/introspection/schema_type.rb +2 -1
  13. data/lib/graphql/language/document_from_schema_definition.rb +9 -3
  14. data/lib/graphql/language/nodes.rb +2 -2
  15. data/lib/graphql/query.rb +7 -1
  16. data/lib/graphql/query/context.rb +31 -9
  17. data/lib/graphql/query/null_context.rb +4 -0
  18. data/lib/graphql/query/variables.rb +3 -1
  19. data/lib/graphql/relay/node.rb +2 -2
  20. data/lib/graphql/schema.rb +58 -7
  21. data/lib/graphql/schema/argument.rb +4 -0
  22. data/lib/graphql/schema/base_64_bp.rb +3 -2
  23. data/lib/graphql/schema/build_from_definition.rb +26 -9
  24. data/lib/graphql/schema/directive.rb +7 -1
  25. data/lib/graphql/schema/introspection_system.rb +4 -1
  26. data/lib/graphql/schema/loader.rb +9 -3
  27. data/lib/graphql/schema/member/has_fields.rb +1 -4
  28. data/lib/graphql/schema/mutation.rb +1 -1
  29. data/lib/graphql/schema/object.rb +6 -4
  30. data/lib/graphql/schema/possible_types.rb +3 -3
  31. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  32. data/lib/graphql/schema/resolver.rb +1 -1
  33. data/lib/graphql/schema/subscription.rb +5 -5
  34. data/lib/graphql/schema/type_membership.rb +34 -0
  35. data/lib/graphql/schema/union.rb +26 -6
  36. data/lib/graphql/schema/warden.rb +77 -3
  37. data/lib/graphql/subscriptions.rb +2 -2
  38. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  39. data/lib/graphql/union_type.rb +58 -23
  40. data/lib/graphql/version.rb +1 -1
  41. metadata +6 -5
@@ -143,9 +143,9 @@ module GraphQL
143
143
  # Make a new context which delegates key lookup to `values`
144
144
  # @param query [GraphQL::Query] the query who owns this context
145
145
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
146
- def initialize(query:, values: , object:)
146
+ def initialize(query:, schema: query.schema, values:, object:)
147
147
  @query = query
148
- @schema = query.schema
148
+ @schema = schema
149
149
  @provided_values = values || {}
150
150
  @object = object
151
151
  # Namespaced storage, where user-provided values are in `nil` namespace:
@@ -155,6 +155,7 @@ module GraphQL
155
155
  @path = []
156
156
  @value = nil
157
157
  @context = self # for SharedMethods
158
+ @scoped_context = {}
158
159
  end
159
160
 
160
161
  # @api private
@@ -163,15 +164,30 @@ module GraphQL
163
164
  # @api private
164
165
  attr_writer :value
165
166
 
166
- def_delegators :@provided_values, :[], :[]=, :to_h, :to_hash, :key?, :fetch, :dig
167
- def_delegators :@query, :trace, :interpreter?
167
+ # @api private
168
+ attr_accessor :scoped_context
168
169
 
169
- # @!method [](key)
170
- # Lookup `key` from the hash passed to {Schema#execute} as `context:`
170
+ def_delegators :@provided_values, :[]=
171
+ def_delegators :to_h, :fetch, :dig
172
+ def_delegators :@query, :trace, :interpreter?
171
173
 
172
174
  # @!method []=(key, value)
173
175
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
174
176
 
177
+ # Lookup `key` from the hash passed to {Schema#execute} as `context:`
178
+ def [](key)
179
+ return @scoped_context[key] if @scoped_context.key?(key)
180
+ @provided_values[key]
181
+ end
182
+
183
+ def to_h
184
+ @provided_values.merge(@scoped_context)
185
+ end
186
+ alias :to_hash :to_h
187
+
188
+ def key?(key)
189
+ @scoped_context.key?(key) || @provided_values.key?(key)
190
+ end
175
191
 
176
192
  # @return [GraphQL::Schema::Warden]
177
193
  def warden
@@ -195,6 +211,15 @@ module GraphQL
195
211
  @value = nil
196
212
  end
197
213
 
214
+ def scoped_merge!(hash)
215
+ @scoped_context = @scoped_context.merge(hash)
216
+ end
217
+
218
+ def scoped_set!(key, value)
219
+ scoped_merge!(key => value)
220
+ nil
221
+ end
222
+
198
223
  class FieldResolutionContext
199
224
  include SharedMethods
200
225
  include Tracing::Traceable
@@ -309,6 +334,3 @@ module GraphQL
309
334
  end
310
335
  end
311
336
  end
312
-
313
-
314
- GraphQL::Schema::Context = GraphQL::Query::Context
@@ -21,9 +21,13 @@ module GraphQL
21
21
  )
22
22
  end
23
23
 
24
+ def [](key); end
25
+
24
26
  class << self
25
27
  extend Forwardable
26
28
 
29
+ def [](key); end
30
+
27
31
  def instance
28
32
  @instance = self.new
29
33
  end
@@ -35,7 +35,9 @@ module GraphQL
35
35
  if validation_result.valid?
36
36
  if value_was_provided
37
37
  # Add the variable if a value was provided
38
- memo[variable_name] = variable_type.coerce_input(provided_value, ctx)
38
+ memo[variable_name] = schema.error_handler.with_error_handling(context) do
39
+ variable_type.coerce_input(provided_value, ctx)
40
+ end
39
41
  elsif default_value != nil
40
42
  # Add the variable if it wasn't provided but it has a default value (including `null`)
41
43
  memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
@@ -11,7 +11,7 @@ module GraphQL
11
11
  field = GraphQL::Types::Relay::NodeField.graphql_definition
12
12
 
13
13
  if kwargs.any? || block
14
- field = field.redefine(kwargs, &block)
14
+ field = field.redefine(**kwargs, &block)
15
15
  end
16
16
 
17
17
  field
@@ -21,7 +21,7 @@ module GraphQL
21
21
  field = GraphQL::Types::Relay::NodesField.graphql_definition
22
22
 
23
23
  if kwargs.any? || block
24
- field = field.redefine(kwargs, &block)
24
+ field = field.redefine(**kwargs, &block)
25
25
  end
26
26
 
27
27
  field
@@ -40,6 +40,7 @@ require "graphql/schema/directive/include"
40
40
  require "graphql/schema/directive/skip"
41
41
  require "graphql/schema/directive/feature"
42
42
  require "graphql/schema/directive/transform"
43
+ require "graphql/schema/type_membership"
43
44
 
44
45
  require "graphql/schema/resolver"
45
46
  require "graphql/schema/mutation"
@@ -98,6 +99,8 @@ module GraphQL
98
99
  mutation: ->(schema, t) { schema.mutation = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
99
100
  subscription: ->(schema, t) { schema.subscription = t.respond_to?(:graphql_definition) ? t.graphql_definition : t },
100
101
  disable_introspection_entry_points: ->(schema) { schema.disable_introspection_entry_points = true },
102
+ disable_schema_introspection_entry_point: ->(schema) { schema.disable_schema_introspection_entry_point = true },
103
+ disable_type_introspection_entry_point: ->(schema) { schema.disable_type_introspection_entry_point = true },
101
104
  directives: ->(schema, directives) { schema.directives = directives.reduce({}) { |m, d| m[d.name] = d; m } },
102
105
  directive: ->(schema, directive) { schema.directives[directive.graphql_name] = directive },
103
106
  instrument: ->(schema, type, instrumenter, after_built_ins: false) {
@@ -153,6 +156,12 @@ module GraphQL
153
156
  # [Boolean] True if this object disables the introspection entry point fields
154
157
  attr_accessor :disable_introspection_entry_points
155
158
 
159
+ # [Boolean] True if this object disables the __schema introspection entry point field
160
+ attr_accessor :disable_schema_introspection_entry_point
161
+
162
+ # [Boolean] True if this object disables the __type introspection entry point field
163
+ attr_accessor :disable_type_introspection_entry_point
164
+
156
165
  class << self
157
166
  attr_writer :default_execution_strategy
158
167
  end
@@ -202,6 +211,8 @@ module GraphQL
202
211
  @interpreter = false
203
212
  @error_bubbling = false
204
213
  @disable_introspection_entry_points = false
214
+ @disable_schema_introspection_entry_point = false
215
+ @disable_type_introspection_entry_point = false
205
216
  end
206
217
 
207
218
  # @return [Boolean] True if using the new {GraphQL::Execution::Interpreter}
@@ -271,7 +282,7 @@ module GraphQL
271
282
  query = GraphQL::Query.new(self, document: doc, context: context)
272
283
  validator_opts = { schema: self }
273
284
  rules && (validator_opts[:rules] = rules)
274
- validator = GraphQL::StaticValidation::Validator.new(validator_opts)
285
+ validator = GraphQL::StaticValidation::Validator.new(**validator_opts)
275
286
  res = validator.validate(query)
276
287
  res[:errors]
277
288
  end
@@ -448,10 +459,11 @@ module GraphQL
448
459
 
449
460
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
450
461
  # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
462
+ # @param context [GraphQL::Query::Context] The context for the current query
451
463
  # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
452
- def possible_types(type_defn)
464
+ def possible_types(type_defn, context = GraphQL::Query::NullContext)
453
465
  @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
454
- @possible_types.possible_types(type_defn)
466
+ @possible_types.possible_types(type_defn, context)
455
467
  end
456
468
 
457
469
  # @see [GraphQL::Schema::Warden] Resticted access to root types
@@ -597,6 +609,7 @@ module GraphQL
597
609
  alias :_schema_class :class
598
610
  def_delegators :_schema_class, :visible?, :accessible?, :authorized?, :unauthorized_object, :unauthorized_field, :inaccessible_fields
599
611
  def_delegators :_schema_class, :directive
612
+ def_delegators :_schema_class, :error_handler
600
613
 
601
614
  # A function to call when {#execute} receives an invalid query string
602
615
  #
@@ -676,9 +689,12 @@ module GraphQL
676
689
  end
677
690
 
678
691
  # Return the GraphQL::Language::Document IDL AST for the schema
692
+ # @param context [Hash]
693
+ # @param only [<#call(member, ctx)>]
694
+ # @param except [<#call(member, ctx)>]
679
695
  # @return [GraphQL::Language::Document]
680
- def to_document
681
- GraphQL::Language::DocumentFromSchemaDefinition.new(self).document
696
+ def to_document(only: nil, except: nil, context: {})
697
+ GraphQL::Language::DocumentFromSchemaDefinition.new(self, only: only, except: except, context: context).document
682
698
  end
683
699
 
684
700
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
@@ -729,13 +745,15 @@ module GraphQL
729
745
  :union_memberships,
730
746
  :get_field, :root_types, :references_to, :type_from_ast,
731
747
  :possible_types,
732
- :disable_introspection_entry_points=
748
+ :disable_introspection_entry_points=,
749
+ :disable_schema_introspection_entry_point=,
750
+ :disable_type_introspection_entry_point=
733
751
 
734
752
  def graphql_definition
735
753
  @graphql_definition ||= to_graphql
736
754
  end
737
755
 
738
- def use(plugin, options = {})
756
+ def use(plugin, **options)
739
757
  own_plugins << [plugin, options]
740
758
  end
741
759
 
@@ -755,6 +773,8 @@ module GraphQL
755
773
  schema_defn.default_max_page_size = default_max_page_size
756
774
  schema_defn.orphan_types = orphan_types
757
775
  schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
776
+ schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point?
777
+ schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point?
758
778
 
759
779
  prepped_dirs = {}
760
780
  directives.each { |k, v| prepped_dirs[k] = v.graphql_definition}
@@ -910,6 +930,14 @@ module GraphQL
910
930
  @disable_introspection_entry_points = true
911
931
  end
912
932
 
933
+ def disable_schema_introspection_entry_point
934
+ @disable_schema_introspection_entry_point = true
935
+ end
936
+
937
+ def disable_type_introspection_entry_point
938
+ @disable_type_introspection_entry_point = true
939
+ end
940
+
913
941
  def disable_introspection_entry_points?
914
942
  if instance_variable_defined?(:@disable_introspection_entry_points)
915
943
  @disable_introspection_entry_points
@@ -918,6 +946,22 @@ module GraphQL
918
946
  end
919
947
  end
920
948
 
949
+ def disable_schema_introspection_entry_point?
950
+ if instance_variable_defined?(:@disable_schema_introspection_entry_point)
951
+ @disable_schema_introspection_entry_point
952
+ else
953
+ find_inherited_value(:disable_schema_introspection_entry_point?, false)
954
+ end
955
+ end
956
+
957
+ def disable_type_introspection_entry_point?
958
+ if instance_variable_defined?(:@disable_type_introspection_entry_point)
959
+ @disable_type_introspection_entry_point
960
+ else
961
+ find_inherited_value(:disable_type_introspection_entry_point?, false)
962
+ end
963
+ end
964
+
921
965
  def orphan_types(*new_orphan_types)
922
966
  if new_orphan_types.any?
923
967
  own_orphan_types.concat(new_orphan_types.flatten)
@@ -1027,6 +1071,13 @@ module GraphQL
1027
1071
  DefaultTypeError.call(type_err, ctx)
1028
1072
  end
1029
1073
 
1074
+ attr_writer :error_handler
1075
+
1076
+ # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1077
+ def error_handler
1078
+ @error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
1079
+ end
1080
+
1030
1081
  def lazy_resolve(lazy_class, value_method)
1031
1082
  lazy_classes[lazy_class] = value_method
1032
1083
  end
@@ -121,6 +121,10 @@ module GraphQL
121
121
  # Used by the runtime.
122
122
  # @api private
123
123
  def prepare_value(obj, value)
124
+ if value.is_a?(GraphQL::Schema::InputObject)
125
+ value = value.prepare
126
+ end
127
+
124
128
  if @prepare.nil?
125
129
  value
126
130
  elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol)
@@ -10,8 +10,9 @@ module Base64Bp
10
10
  module_function
11
11
 
12
12
  def urlsafe_encode64(bin, padding:)
13
- str = strict_encode64(bin).tr("+/", "-_")
14
- str = str.delete("=") unless padding
13
+ str = strict_encode64(bin)
14
+ str.tr!("+/", "-_")
15
+ str.delete!("=") unless padding
15
16
  str
16
17
  end
17
18
 
@@ -183,12 +183,16 @@ module GraphQL
183
183
  def build_object_type(object_type_definition, type_resolver, default_resolve:)
184
184
  type_def = nil
185
185
  typed_resolve_fn = ->(field, obj, args, ctx) { default_resolve.call(type_def, field, obj, args, ctx) }
186
- type_def = GraphQL::ObjectType.define(
186
+ defns = {
187
187
  name: object_type_definition.name,
188
188
  description: object_type_definition.description,
189
- fields: Hash[build_fields(object_type_definition.fields, type_resolver, default_resolve: typed_resolve_fn)],
190
189
  interfaces: object_type_definition.interfaces.map{ |interface_name| type_resolver.call(interface_name) },
191
- )
190
+ }
191
+ obj_fields = Hash[build_fields(object_type_definition.fields, type_resolver, default_resolve: typed_resolve_fn)]
192
+ if obj_fields.any?
193
+ defns[:fields] = obj_fields
194
+ end
195
+ type_def = GraphQL::ObjectType.define(**defns)
192
196
  type_def.ast_node = object_type_definition
193
197
  type_def
194
198
  end
@@ -246,13 +250,19 @@ module GraphQL
246
250
  end
247
251
 
248
252
  def build_directive(directive_definition, type_resolver)
249
- directive = GraphQL::Directive.define(
253
+ directive_args = Hash[build_directive_arguments(directive_definition, type_resolver)]
254
+
255
+ defn = {
250
256
  name: directive_definition.name,
251
257
  description: directive_definition.description,
252
- arguments: Hash[build_directive_arguments(directive_definition, type_resolver)],
253
258
  locations: directive_definition.locations.map { |location| location.name.to_sym },
254
- )
259
+ }
260
+
261
+ if directive_args.any?
262
+ defn[:arguments] = directive_args
263
+ end
255
264
 
265
+ directive = GraphQL::Directive.define(**defn)
256
266
  directive.ast_node = directive_definition
257
267
 
258
268
  directive
@@ -317,14 +327,21 @@ module GraphQL
317
327
  [argument.name, arg]
318
328
  end]
319
329
 
320
- field = GraphQL::Field.define(
330
+ field = nil
331
+
332
+ defns = {
321
333
  name: field_definition.name,
322
334
  description: field_definition.description,
323
335
  type: type_resolver.call(field_definition.type),
324
- arguments: field_arguments,
325
336
  resolve: ->(obj, args, ctx) { default_resolve.call(field, obj, args, ctx) },
326
337
  deprecation_reason: build_deprecation_reason(field_definition.directives),
327
- )
338
+ }
339
+
340
+ if field_arguments.any?
341
+ defns[:arguments] = field_arguments
342
+ end
343
+
344
+ field = GraphQL::Field.define(**defns)
328
345
 
329
346
  field.ast_node = field_definition
330
347
 
@@ -9,8 +9,14 @@ module GraphQL
9
9
  class Directive < GraphQL::Schema::Member
10
10
  extend GraphQL::Schema::Member::HasArguments
11
11
  class << self
12
+ # Return a name based on the class name,
13
+ # but downcase the first letter.
12
14
  def default_graphql_name
13
- super.downcase
15
+ @default_graphql_name ||= begin
16
+ camelized_name = super
17
+ camelized_name[0] = camelized_name[0].downcase
18
+ camelized_name
19
+ end
14
20
  end
15
21
 
16
22
  def locations(*new_locations)
@@ -22,7 +22,10 @@ module GraphQL
22
22
  if schema.disable_introspection_entry_points
23
23
  {}
24
24
  else
25
- get_fields_from_class(class_sym: :EntryPoints)
25
+ entry_point_fields = get_fields_from_class(class_sym: :EntryPoints)
26
+ entry_point_fields.delete('__schema') if schema.disable_schema_introspection_entry_point
27
+ entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point
28
+ entry_point_fields
26
29
  end
27
30
  @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
28
31
  end
@@ -118,14 +118,20 @@ module GraphQL
118
118
  }]
119
119
  )
120
120
  when "FIELD"
121
- GraphQL::Field.define(
121
+ defns = {
122
122
  name: type["name"],
123
123
  type: type_resolver.call(type["type"]),
124
124
  description: type["description"],
125
- arguments: Hash[type["args"].map { |arg|
125
+ }
126
+
127
+ # Avoid passing an empty hash, which warns on Ruby 2.7
128
+ if type["args"].any?
129
+ defns[:arguments] = Hash[type["args"].map { |arg|
126
130
  [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)]
127
131
  }]
128
- )
132
+ end
133
+
134
+ GraphQL::Field.define(**defns)
129
135
  when "ARGUMENT"
130
136
  kwargs = {}
131
137
  if type["defaultValue"]
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'irb/ruby-token'
3
2
 
4
3
  module GraphQL
5
4
  class Schema
@@ -43,9 +42,7 @@ module GraphQL
43
42
  # A list of Ruby keywords.
44
43
  #
45
44
  # @api private
46
- RUBY_KEYWORDS = RubyToken::TokenDefinitions.select { |definition| definition[1] == RubyToken::TkId }
47
- .map { |definition| definition[2] }
48
- .compact
45
+ RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__]
49
46
 
50
47
  # A list of GraphQL-Ruby keywords.
51
48
  #
@@ -64,7 +64,7 @@ module GraphQL
64
64
 
65
65
  class << self
66
66
  # Override this method to handle legacy-style usages of `MyMutation.field`
67
- def field(*args, &block)
67
+ def field(*args, **kwargs, &block)
68
68
  if args.empty?
69
69
  raise ArgumentError, "#{name}.field is used for adding fields to this mutation. Use `mutation: #{name}` to attach this mutation instead."
70
70
  else