graphql 1.9.16 → 1.9.21

Sign up to get free protection for your applications and to get access to all the features.
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