graphql 1.10.9 → 1.10.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +50 -8
  3. data/lib/graphql.rb +3 -3
  4. data/lib/graphql/directive.rb +4 -0
  5. data/lib/graphql/execution/instrumentation.rb +1 -1
  6. data/lib/graphql/execution/interpreter/arguments.rb +1 -5
  7. data/lib/graphql/execution/interpreter/runtime.rb +19 -23
  8. data/lib/graphql/field.rb +4 -0
  9. data/lib/graphql/input_object_type.rb +4 -0
  10. data/lib/graphql/object_type.rb +1 -1
  11. data/lib/graphql/schema.rb +11 -3
  12. data/lib/graphql/schema/argument.rb +6 -0
  13. data/lib/graphql/schema/enum.rb +9 -1
  14. data/lib/graphql/schema/field.rb +24 -20
  15. data/lib/graphql/schema/input_object.rb +17 -18
  16. data/lib/graphql/schema/interface.rb +5 -0
  17. data/lib/graphql/schema/list.rb +2 -1
  18. data/lib/graphql/schema/loader.rb +3 -0
  19. data/lib/graphql/schema/member.rb +1 -0
  20. data/lib/graphql/schema/member/has_arguments.rb +22 -0
  21. data/lib/graphql/schema/member/has_fields.rb +1 -1
  22. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  23. data/lib/graphql/schema/object.rb +7 -0
  24. data/lib/graphql/schema/union.rb +6 -0
  25. data/lib/graphql/schema/warden.rb +13 -1
  26. data/lib/graphql/static_validation/literal_validator.rb +7 -7
  27. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  28. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  29. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
  30. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  31. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  32. data/lib/graphql/tracing/platform_tracing.rb +14 -0
  33. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  34. data/lib/graphql/types/iso_8601_date.rb +2 -2
  35. data/lib/graphql/types/iso_8601_date_time.rb +19 -15
  36. data/lib/graphql/version.rb +1 -1
  37. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faf92321ef690867c10449792e3ac7a512b6c70a8cb98c4abe55701dbab68b5c
4
- data.tar.gz: c0d7c15c4f74ec0dff027d1e1dbc102d8e0a99973e7d856e2ccb0dfb90dcf9cb
3
+ metadata.gz: e1d76918804bdeeba0c80bbcfcdde70d04132110aee8e4520dcba8480da390fc
4
+ data.tar.gz: c0e1954ae3f6d18bf03fc33807b94d482b83b4e4a6f8a392452e2f6e8297f241
5
5
  SHA512:
6
- metadata.gz: '0380579063581e40b9c94c64e7c040c0e654e36608ec20079a6d53e34015e8f7aac7a1f4a718c6c9f2200ee0f4a5ce0a1226cf5c1c838aafebb82d1819ca4bb5'
7
- data.tar.gz: 98a72e31dee880098191b2ad6e3fec2cacc459ee17d4a8e5dab380dd5bd9c2d08c7aa31b6164cce23999c557a78de02f7d0a2f3eb3e5a0ff007b46f032be3fea
6
+ metadata.gz: 3e945033f8657c252fcd99a18d4be5c9d267535753e88a94f3b589c968ca186e90f3368768ff2f3f7c7e1eff7a210bf57694bde763255ee0a91f1e46534b0b4d
7
+ data.tar.gz: de56338439eeb22b8484033c5acfb4ac027edee60ac30218c8a7516c5bc6d4bfa07b8be54ebd42a63fca5decdfbbcf85e4b96832bb8d7fcd304dc2fb98bc7625
@@ -15,20 +15,62 @@ module Graphql
15
15
  desc "Create a GraphQL::ObjectType with the given name and fields"
16
16
  source_root File.expand_path('../templates', __FILE__)
17
17
 
18
- argument :fields,
19
- type: :array,
20
- default: [],
21
- banner: "name:type name:type ...",
22
- desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
18
+ argument :custom_fields,
19
+ type: :array,
20
+ default: [],
21
+ banner: "name:type name:type ...",
22
+ desc: "Fields for this object (type may be expressed as Ruby or GraphQL)"
23
23
 
24
24
  class_option :node,
25
- type: :boolean,
26
- default: false,
27
- desc: "Include the Relay Node interface"
25
+ type: :boolean,
26
+ default: false,
27
+ desc: "Include the Relay Node interface"
28
28
 
29
29
  def create_type_file
30
30
  template "object.erb", "#{options[:directory]}/types/#{type_file_name}.rb"
31
31
  end
32
+
33
+ def fields
34
+ columns = []
35
+ columns += klass.columns.map { |c| generate_column_string(c) } if class_exists?
36
+ columns + custom_fields
37
+ end
38
+
39
+ def self.normalize_type_expression(type_expression, mode:, null: true)
40
+ case type_expression
41
+ when "Text"
42
+ ["String", null]
43
+ when "DateTime", "Datetime"
44
+ ["GraphQL::Types::ISO8601DateTime", null]
45
+ when "Date"
46
+ ["GraphQL::Types::ISO8601Date", null]
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def generate_column_string(column)
55
+ name = column.name
56
+ required = column.null ? "" : "!"
57
+ type = column_type_string(column)
58
+ "#{name}:#{required}#{type}"
59
+ end
60
+
61
+ def column_type_string(column)
62
+ column.name == "id" ? "ID" : column.type.to_s.camelize
63
+ end
64
+
65
+ def class_exists?
66
+ klass.is_a?(Class) && klass.ancestors.include?(ActiveRecord::Base)
67
+ rescue NameError
68
+ return false
69
+ end
70
+
71
+ def klass
72
+ @klass ||= Module.const_get(type_name.camelize)
73
+ end
32
74
  end
33
75
  end
34
76
  end
@@ -95,6 +95,9 @@ require "graphql/schema"
95
95
  require "graphql/query"
96
96
  require "graphql/directive"
97
97
  require "graphql/execution"
98
+ require "graphql/runtime_type_error"
99
+ require "graphql/unresolved_type_error"
100
+ require "graphql/invalid_null_error"
98
101
  require "graphql/types"
99
102
  require "graphql/relay"
100
103
  require "graphql/boolean_type"
@@ -109,10 +112,7 @@ require "graphql/introspection"
109
112
 
110
113
  require "graphql/analysis_error"
111
114
  require "graphql/coercion_error"
112
- require "graphql/runtime_type_error"
113
- require "graphql/invalid_null_error"
114
115
  require "graphql/invalid_name_error"
115
- require "graphql/unresolved_type_error"
116
116
  require "graphql/integer_encoding_error"
117
117
  require "graphql/string_encoding_error"
118
118
  require "graphql/internal_representation"
@@ -99,6 +99,10 @@ module GraphQL
99
99
  def type_class
100
100
  metadata[:type_class]
101
101
  end
102
+
103
+ def get_argument(argument_name)
104
+ arguments[argument_name]
105
+ end
102
106
  end
103
107
  end
104
108
 
@@ -77,7 +77,7 @@ module GraphQL
77
77
  end
78
78
 
79
79
  def call_after_hooks(instrumenters, object, after_hook_name, ex)
80
- instrumenters.reverse.each do |instrumenter|
80
+ instrumenters.reverse_each do |instrumenter|
81
81
  begin
82
82
  instrumenter.public_send(after_hook_name, object)
83
83
  rescue => e
@@ -21,15 +21,11 @@ module GraphQL
21
21
  @argument_values = argument_values
22
22
  end
23
23
 
24
- # Yields `ArgumentValue` instances which contain detailed metadata about each argument.
25
- def each_value
26
- argument_values.each { |arg_v| yield(arg_v) }
27
- end
28
-
29
24
  # @return [Hash{Symbol => ArgumentValue}]
30
25
  attr_reader :argument_values
31
26
 
32
27
  def_delegators :@keyword_arguments, :key?, :[], :keys, :each, :values
28
+ def_delegators :@argument_values, :each_value
33
29
 
34
30
  def inspect
35
31
  "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>"
@@ -182,8 +182,6 @@ module GraphQL
182
182
 
183
183
  kwarg_arguments = resolved_arguments.keyword_arguments
184
184
 
185
- # It might turn out that making arguments for every field is slow.
186
- # If we have to cache them, we'll need a more subtle approach here.
187
185
  field_defn.extras.each do |extra|
188
186
  case extra
189
187
  when :ast_node
@@ -256,7 +254,7 @@ module GraphQL
256
254
  def continue_value(path, value, field, is_non_null, ast_node)
257
255
  if value.nil?
258
256
  if is_non_null
259
- err = GraphQL::InvalidNullError.new(field.owner, field, value)
257
+ err = field.owner::InvalidNullError.new(field.owner, field, value)
260
258
  write_invalid_null_in_response(path, err)
261
259
  else
262
260
  write_in_response(path, nil)
@@ -306,18 +304,21 @@ module GraphQL
306
304
  write_in_response(path, r)
307
305
  r
308
306
  when "UNION", "INTERFACE"
309
- resolved_type_or_lazy = resolve_type(type, value, path)
307
+ resolved_type_or_lazy, resolved_value = resolve_type(type, value, path)
308
+ resolved_value ||= value
309
+
310
310
  after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
311
311
  possible_types = query.possible_types(type)
312
312
 
313
313
  if !possible_types.include?(resolved_type)
314
314
  parent_type = field.owner
315
- type_error = GraphQL::UnresolvedTypeError.new(value, field, parent_type, resolved_type, possible_types)
315
+ err_class = type::UnresolvedTypeError
316
+ type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
316
317
  schema.type_error(type_error, context)
317
318
  write_in_response(path, nil)
318
319
  nil
319
320
  else
320
- continue_field(path, value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
321
+ continue_field(path, resolved_value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
321
322
  end
322
323
  end
323
324
  when "OBJECT"
@@ -459,7 +460,14 @@ module GraphQL
459
460
  end
460
461
 
461
462
  def arguments(graphql_object, arg_owner, ast_node)
462
- query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
463
+ # Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
464
+ if arg_owner.arguments_statically_coercible? &&
465
+ (!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
466
+ query.arguments_for(ast_node, arg_owner)
467
+ else
468
+ # The arguments must be prepared in the context of the given object
469
+ query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
470
+ end
463
471
  end
464
472
 
465
473
  def write_invalid_null_in_response(path, invalid_null_error)
@@ -499,23 +507,11 @@ module GraphQL
499
507
  # at previous parts of the response.
500
508
  # This hash matches the response
501
509
  def type_at(path)
502
- t = @types_at_paths
503
- path.each do |part|
504
- t = t[part] || (raise("Invariant: #{part.inspect} not found in #{t}"))
505
- end
506
- t = t[:__type]
507
- t
510
+ @types_at_paths.fetch(path)
508
511
  end
509
512
 
510
513
  def set_type_at_path(path, type)
511
- types = @types_at_paths
512
- path.each do |part|
513
- types = types[part] ||= {}
514
- end
515
- # Use this magic key so that the hash contains:
516
- # - string keys for nested fields
517
- # - :__type for the object type of a selection
518
- types[:__type] ||= type
514
+ @types_at_paths[path] = type
519
515
  nil
520
516
  end
521
517
 
@@ -545,7 +541,7 @@ module GraphQL
545
541
 
546
542
  def resolve_type(type, value, path)
547
543
  trace_payload = { context: context, type: type, object: value, path: path }
548
- resolved_type = query.trace("resolve_type", trace_payload) do
544
+ resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
549
545
  query.resolve_type(type, value)
550
546
  end
551
547
 
@@ -556,7 +552,7 @@ module GraphQL
556
552
  end
557
553
  end
558
554
  else
559
- resolved_type
555
+ [resolved_type, resolved_value]
560
556
  end
561
557
  end
562
558
 
@@ -207,6 +207,10 @@ module GraphQL
207
207
  metadata[:type_class]
208
208
  end
209
209
 
210
+ def get_argument(argument_name)
211
+ arguments[argument_name]
212
+ end
213
+
210
214
  private
211
215
 
212
216
  def build_default_resolver
@@ -58,6 +58,10 @@ module GraphQL
58
58
  result
59
59
  end
60
60
 
61
+ def get_argument(argument_name)
62
+ arguments[argument_name]
63
+ end
64
+
61
65
  private
62
66
 
63
67
  def coerce_non_null_input(value, ctx)
@@ -88,7 +88,7 @@ module GraphQL
88
88
  interfaces.each do |iface|
89
89
  iface = BaseType.resolve_related_type(iface)
90
90
  if iface.is_a?(GraphQL::InterfaceType)
91
- type_memberships << iface.type_membership_class.new(iface, self, options)
91
+ type_memberships << iface.type_membership_class.new(iface, self, **options)
92
92
  end
93
93
  end
94
94
  end
@@ -1389,7 +1389,7 @@ module GraphQL
1389
1389
  # rubocop:disable Lint/DuplicateMethods
1390
1390
  module ResolveTypeWithType
1391
1391
  def resolve_type(type, obj, ctx)
1392
- first_resolved_type = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1392
+ first_resolved_type, resolved_value = if type.is_a?(Module) && type.respond_to?(:resolve_type)
1393
1393
  type.resolve_type(obj, ctx)
1394
1394
  else
1395
1395
  super
@@ -1397,7 +1397,11 @@ module GraphQL
1397
1397
 
1398
1398
  after_lazy(first_resolved_type) do |resolved_type|
1399
1399
  if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1400
- resolved_type
1400
+ if resolved_value
1401
+ [resolved_type, resolved_value]
1402
+ else
1403
+ resolved_type
1404
+ end
1401
1405
  else
1402
1406
  raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1403
1407
  end
@@ -1506,7 +1510,11 @@ module GraphQL
1506
1510
 
1507
1511
  # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1508
1512
  def error_handler
1509
- @error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
1513
+ if defined?(@error_handler)
1514
+ @error_handler
1515
+ else
1516
+ find_inherited_value(:error_handler, GraphQL::Execution::Errors::NullErrorHandler)
1517
+ end
1510
1518
  end
1511
1519
 
1512
1520
  def lazy_resolve(lazy_class, value_method)
@@ -154,6 +154,12 @@ module GraphQL
154
154
  raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace
155
155
  end
156
156
 
157
+ def statically_coercible?
158
+ return @statically_coercible if defined?(@statically_coercible)
159
+
160
+ @statically_coercible = !@prepare.is_a?(String) && !@prepare.is_a?(Symbol)
161
+ end
162
+
157
163
  # Apply the {prepare} configuration to `value`, using methods from `obj`.
158
164
  # Used by the runtime.
159
165
  # @api private
@@ -23,6 +23,9 @@ module GraphQL
23
23
  extend GraphQL::Schema::Member::AcceptsDefinition
24
24
  extend GraphQL::Schema::Member::ValidatesInput
25
25
 
26
+ class UnresolvedValueError < GraphQL::EnumType::UnresolvedValueError
27
+ end
28
+
26
29
  class << self
27
30
  # Define a value for this enum
28
31
  # @param graphql_name [String, Symbol] the GraphQL value for this, usually `SCREAMING_CASE`
@@ -94,7 +97,7 @@ module GraphQL
94
97
  if enum_value
95
98
  enum_value.graphql_name
96
99
  else
97
- raise(GraphQL::EnumType::UnresolvedValueError, "Can't resolve enum #{graphql_name} for #{value.inspect}")
100
+ raise(self::UnresolvedValueError, "Can't resolve enum #{graphql_name} for #{value.inspect}")
98
101
  end
99
102
  end
100
103
 
@@ -112,6 +115,11 @@ module GraphQL
112
115
  end
113
116
  end
114
117
 
118
+ def inherited(child_class)
119
+ child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
120
+ super
121
+ end
122
+
115
123
  private
116
124
 
117
125
  def own_values
@@ -556,34 +556,36 @@ module GraphQL
556
556
  begin
557
557
  # Unwrap the GraphQL object to get the application object.
558
558
  application_object = object.object
559
- if self.authorized?(application_object, args, ctx)
560
- # Apply field extensions
561
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
562
- field_receiver = if @resolver_class
563
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
564
- extended_obj.object
559
+ ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
560
+ if is_authorized
561
+ # Apply field extensions
562
+ with_extensions(object, args, ctx) do |extended_obj, extended_args|
563
+ field_receiver = if @resolver_class
564
+ resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
565
+ extended_obj.object
566
+ else
567
+ extended_obj
568
+ end
569
+ @resolver_class.new(object: resolver_obj, context: ctx, field: self)
565
570
  else
566
571
  extended_obj
567
572
  end
568
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
569
- else
570
- extended_obj
571
- end
572
573
 
573
- if field_receiver.respond_to?(@resolver_method)
574
- # Call the method with kwargs, if there are any
575
- if extended_args.any?
576
- field_receiver.public_send(@resolver_method, **extended_args)
574
+ if field_receiver.respond_to?(@resolver_method)
575
+ # Call the method with kwargs, if there are any
576
+ if extended_args.any?
577
+ field_receiver.public_send(@resolver_method, **extended_args)
578
+ else
579
+ field_receiver.public_send(@resolver_method)
580
+ end
577
581
  else
578
- field_receiver.public_send(@resolver_method)
582
+ resolve_field_method(field_receiver, extended_args, ctx)
579
583
  end
580
- else
581
- resolve_field_method(field_receiver, extended_args, ctx)
582
584
  end
585
+ else
586
+ err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
587
+ ctx.schema.unauthorized_field(err)
583
588
  end
584
- else
585
- err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
586
- ctx.schema.unauthorized_field(err)
587
589
  end
588
590
  rescue GraphQL::UnauthorizedFieldError => err
589
591
  err.field ||= self
@@ -672,6 +674,8 @@ module GraphQL
672
674
  else
673
675
  load_application_object(arg_defn, loads, value, field_ctx.query.context)
674
676
  end
677
+ elsif arg_defn.type.list? && value.is_a?(Array)
678
+ field_ctx.schema.after_any_lazies(value, &:itself)
675
679
  else
676
680
  value
677
681
  end
@@ -21,10 +21,8 @@ module GraphQL
21
21
  @ruby_style_hash = @arguments.to_kwargs
22
22
  end
23
23
  # Apply prepares, not great to have it duplicated here.
24
- @arguments_by_keyword = {}
25
24
  maybe_lazies = []
26
- self.class.arguments.each do |name, arg_defn|
27
- @arguments_by_keyword[arg_defn.keyword] = arg_defn
25
+ self.class.arguments.each_value do |arg_defn|
28
26
  ruby_kwargs_key = arg_defn.keyword
29
27
 
30
28
  if @ruby_style_hash.key?(ruby_kwargs_key)
@@ -168,10 +166,7 @@ module GraphQL
168
166
  return result
169
167
  end
170
168
 
171
- # We're not actually _using_ the coerced result, we're just
172
- # using these methods to make sure that the object will
173
- # behave like a hash below, when we call `each` on it.
174
- begin
169
+ input = begin
175
170
  input.to_h
176
171
  rescue
177
172
  begin
@@ -184,21 +179,25 @@ module GraphQL
184
179
  end
185
180
  end
186
181
 
187
- visible_arguments_map = warden.arguments(self).reduce({}) { |m, f| m[f.name] = f; m}
188
-
189
- # Items in the input that are unexpected
190
- input.each do |name, value|
191
- if visible_arguments_map[name].nil?
192
- result.add_problem("Field is not defined on #{self.graphql_name}", [name])
182
+ # Inject missing required arguments
183
+ missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)|
184
+ if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
185
+ m[argument_name] = nil
193
186
  end
187
+
188
+ m
194
189
  end
195
190
 
196
- # Items in the input that are expected, but have invalid values
197
- visible_arguments_map.map do |name, argument|
198
- argument_result = argument.type.validate_input(input[name], ctx)
199
- if !argument_result.valid?
200
- result.merge_result!(name, argument_result)
191
+ input.merge(missing_required_inputs).each do |argument_name, value|
192
+ argument = warden.get_argument(self, argument_name)
193
+ # Items in the input that are unexpected
194
+ unless argument
195
+ result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
196
+ next
201
197
  end
198
+ # Items in the input that are expected, but have invalid values
199
+ argument_result = argument.type.validate_input(value, ctx)
200
+ result.merge_result!(argument_name, argument_result) unless argument_result.valid?
202
201
  end
203
202
 
204
203
  result
@@ -14,6 +14,7 @@ module GraphQL
14
14
  include GraphQL::Schema::Member::RelayShortcuts
15
15
  include GraphQL::Schema::Member::Scoped
16
16
  include GraphQL::Schema::Member::HasAstNode
17
+ include GraphQL::Schema::Member::HasUnresolvedTypeError
17
18
 
18
19
  # Methods defined in this block will be:
19
20
  # - Added as class methods to this interface
@@ -74,6 +75,10 @@ module GraphQL
74
75
  if overridden_graphql_name
75
76
  child_class.graphql_name(overridden_graphql_name)
76
77
  end
78
+ # If interfaces are mixed into each other, only define this class once
79
+ if !child_class.const_defined?(:UnresolvedTypeError, false)
80
+ add_unresolved_type_error(child_class)
81
+ end
77
82
  elsif child_class < GraphQL::Schema::Object
78
83
  # This is being included into an object type, make sure it's using `implements(...)`
79
84
  backtrace_line = caller(0, 10).find { |line| line.include?("schema/object.rb") && line.include?("in `implements'")}
@@ -44,7 +44,8 @@ module GraphQL
44
44
  if value.nil?
45
45
  nil
46
46
  else
47
- ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) }
47
+ coerced = ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) }
48
+ ctx.schema.after_any_lazies(coerced, &:itself)
48
49
  end
49
50
  end
50
51
 
@@ -157,6 +157,7 @@ module GraphQL
157
157
  type: type_resolver.call(field_hash["type"]),
158
158
  description: field_hash["description"],
159
159
  null: true,
160
+ camelize: false,
160
161
  ) do
161
162
  if field_hash["args"].any?
162
163
  loader.build_arguments(self, field_hash["args"], type_resolver)
@@ -171,6 +172,8 @@ module GraphQL
171
172
  type: type_resolver.call(arg["type"]),
172
173
  description: arg["description"],
173
174
  required: false,
175
+ method_access: false,
176
+ camelize: false,
174
177
  }
175
178
 
176
179
  if arg["defaultValue"]
@@ -5,6 +5,7 @@ require 'graphql/schema/member/cached_graphql_definition'
5
5
  require 'graphql/schema/member/graphql_type_names'
6
6
  require 'graphql/schema/member/has_ast_node'
7
7
  require 'graphql/schema/member/has_path'
8
+ require 'graphql/schema/member/has_unresolved_type_error'
8
9
  require 'graphql/schema/member/relay_shortcuts'
9
10
  require 'graphql/schema/member/scoped'
10
11
  require 'graphql/schema/member/type_system_helpers'
@@ -58,6 +58,22 @@ module GraphQL
58
58
  end
59
59
  end
60
60
 
61
+ # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
62
+ def get_argument(argument_name)
63
+ a = own_arguments[argument_name]
64
+
65
+ if a || !self.is_a?(Class)
66
+ a
67
+ else
68
+ for ancestor in ancestors
69
+ if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
70
+ return a
71
+ end
72
+ end
73
+ nil
74
+ end
75
+ end
76
+
61
77
  # @param new_arg_class [Class] A class to use for building argument definitions
62
78
  def argument_class(new_arg_class = nil)
63
79
  self.class.argument_class(new_arg_class)
@@ -135,6 +151,12 @@ module GraphQL
135
151
  end
136
152
  end
137
153
 
154
+ def arguments_statically_coercible?
155
+ return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
156
+
157
+ @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?)
158
+ end
159
+
138
160
  module ArgumentClassAccessor
139
161
  def argument_class(new_arg_class = nil)
140
162
  if new_arg_class
@@ -47,7 +47,7 @@ module GraphQL
47
47
  # A list of GraphQL-Ruby keywords.
48
48
  #
49
49
  # @api private
50
- GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method]
50
+ GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method, :raw_value]
51
51
 
52
52
  # A list of field names that we should advise users to pick a different
53
53
  # resolve method name.
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ # Set up a type-specific error to make debugging & bug tracker integration better
7
+ module HasUnresolvedTypeError
8
+ private
9
+ def add_unresolved_type_error(child_class)
10
+ child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -71,6 +71,13 @@ module GraphQL
71
71
  end
72
72
 
73
73
  class << self
74
+ # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
75
+ # It should help with debugging and bug tracker integrations.
76
+ def inherited(child_class)
77
+ child_class.const_set(:InvalidNullError, Class.new(GraphQL::InvalidNullError))
78
+ super
79
+ end
80
+
74
81
  def implements(*new_interfaces, **options)
75
82
  new_memberships = []
76
83
  new_interfaces.each do |int|
@@ -3,8 +3,14 @@ module GraphQL
3
3
  class Schema
4
4
  class Union < GraphQL::Schema::Member
5
5
  extend GraphQL::Schema::Member::AcceptsDefinition
6
+ extend GraphQL::Schema::Member::HasUnresolvedTypeError
6
7
 
7
8
  class << self
9
+ def inherited(child_class)
10
+ add_unresolved_type_error(child_class)
11
+ super
12
+ end
13
+
8
14
  def possible_types(*types, context: GraphQL::Query::NullContext, **options)
9
15
  if types.any?
10
16
  types.each do |t|
@@ -106,6 +106,12 @@ module GraphQL
106
106
  @visible_parent_fields[parent_type][field_name]
107
107
  end
108
108
 
109
+ # @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible
110
+ def get_argument(parent_type, argument_name)
111
+ argument = parent_type.get_argument(argument_name)
112
+ return argument if argument && visible_argument?(argument)
113
+ end
114
+
109
115
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
110
116
  def possible_types(type_defn)
111
117
  @visible_possible_types ||= read_through { |type_defn|
@@ -166,7 +172,13 @@ module GraphQL
166
172
  end
167
173
 
168
174
  def visible_field?(owner_type, field_defn)
169
- visible?(field_defn) && visible_type?(field_defn.type.unwrap) && field_on_visible_interface?(field_defn, owner_type)
175
+ # This field is visible in its own right
176
+ visible?(field_defn) &&
177
+ # This field's return type is visible
178
+ visible_type?(field_defn.type.unwrap) &&
179
+ # This field is either defined on this object type,
180
+ # or the interface it's inherited from is also visible
181
+ ((field_defn.respond_to?(:owner) && field_defn.owner == owner_type) || field_on_visible_interface?(field_defn, owner_type))
170
182
  end
171
183
 
172
184
  # We need this to tell whether a field was inherited by an interface
@@ -95,16 +95,17 @@ module GraphQL
95
95
  def required_input_fields_are_present(type, ast_node)
96
96
  # TODO - would be nice to use these to create an error message so the caller knows
97
97
  # that required fields are missing
98
- required_field_names = @warden.arguments(type)
99
- .select { |f| f.type.kind.non_null? }
98
+ required_field_names = type.arguments.each_value
99
+ .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
100
100
  .map(&:name)
101
+
101
102
  present_field_names = ast_node.arguments.map(&:name)
102
103
  missing_required_field_names = required_field_names - present_field_names
103
104
  if @context.schema.error_bubbling
104
105
  missing_required_field_names.empty? ? @valid_response : @invalid_response
105
106
  else
106
107
  results = missing_required_field_names.map do |name|
107
- arg_type = @warden.arguments(type).find { |f| f.name == name }.type
108
+ arg_type = @warden.get_argument(type, name).type
108
109
  recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
109
110
  end
110
111
  merge_results(results)
@@ -112,13 +113,12 @@ module GraphQL
112
113
  end
113
114
 
114
115
  def present_input_field_values_are_valid(type, ast_node)
115
- field_map = @warden.arguments(type).reduce({}) { |m, f| m[f.name] = f; m}
116
116
  results = ast_node.arguments.map do |value|
117
- field = field_map[value.name]
117
+ field = @warden.get_argument(type, value.name)
118
118
  # we want to call validate on an argument even if it's an invalid one
119
119
  # so that our raise exception is on it instead of the entire InputObject
120
- type = field && field.type
121
- recursively_validate(value.value, type)
120
+ field_type = field && field.type
121
+ recursively_validate(value.value, field_type)
122
122
  end
123
123
  merge_results(results)
124
124
  end
@@ -5,7 +5,7 @@ module GraphQL
5
5
  def on_argument(node, parent)
6
6
  parent_defn = parent_definition(parent)
7
7
 
8
- if parent_defn && context.warden.arguments(parent_defn).any? { |arg| arg.name == node.name }
8
+ if parent_defn && context.warden.get_argument(parent_defn, node.name)
9
9
  super
10
10
  elsif parent_defn
11
11
  kind_of_node = node_type(parent)
@@ -17,8 +17,8 @@ module GraphQL
17
17
 
18
18
  def assert_required_args(ast_node, defn)
19
19
  present_argument_names = ast_node.arguments.map(&:name)
20
- required_argument_names = context.warden.arguments(defn)
21
- .select { |a| a.type.kind.non_null? && !a.default_value? }
20
+ required_argument_names = defn.arguments.each_value
21
+ .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
22
22
  .map(&:name)
23
23
 
24
24
  missing_names = required_argument_names - present_argument_names
@@ -26,8 +26,7 @@ module GraphQL
26
26
  context.field_definition
27
27
  end
28
28
 
29
- parent_type = context.warden.arguments(defn)
30
- .find{|f| f.name == parent_name(parent, defn) }
29
+ parent_type = context.warden.get_argument(defn, parent_name(parent, defn))
31
30
  parent_type ? parent_type.type.unwrap : nil
32
31
  end
33
32
 
@@ -40,7 +40,7 @@ module GraphQL
40
40
  # for the backend to register:
41
41
  event = Subscriptions::Event.new(
42
42
  name: field.name,
43
- arguments: arguments,
43
+ arguments: arguments_without_field_extras(arguments: arguments),
44
44
  context: context,
45
45
  field: field,
46
46
  )
@@ -48,7 +48,7 @@ module GraphQL
48
48
  value
49
49
  elsif context.query.subscription_topic == Subscriptions::Event.serialize(
50
50
  field.name,
51
- arguments,
51
+ arguments_without_field_extras(arguments: arguments),
52
52
  field,
53
53
  scope: (field.subscription_scope ? context[field.subscription_scope] : nil),
54
54
  )
@@ -60,6 +60,14 @@ module GraphQL
60
60
  context.skip
61
61
  end
62
62
  end
63
+
64
+ private
65
+
66
+ def arguments_without_field_extras(arguments:)
67
+ arguments.dup.tap do |event_args|
68
+ field.extras.each { |k| event_args.delete(k) }
69
+ end
70
+ end
63
71
  end
64
72
  end
65
73
  end
@@ -26,18 +26,7 @@ module GraphQL
26
26
  if key == "execute_query"
27
27
  set_this_txn_name = data[:query].context[:set_new_relic_transaction_name]
28
28
  if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
29
- query = data[:query]
30
- # Set the transaction name based on the operation type and name
31
- selected_op = query.selected_operation
32
- if selected_op
33
- op_type = selected_op.operation_type
34
- op_name = selected_op.name || "anonymous"
35
- else
36
- op_type = "query"
37
- op_name = "anonymous"
38
- end
39
-
40
- NewRelic::Agent.set_transaction_name("GraphQL/#{op_type}.#{op_name}")
29
+ NewRelic::Agent.set_transaction_name(transaction_name(data[:query]))
41
30
  end
42
31
  end
43
32
 
@@ -103,6 +103,20 @@ module GraphQL
103
103
  end
104
104
 
105
105
  private
106
+
107
+ # Get the transaction name based on the operation type and name
108
+ def transaction_name(query)
109
+ selected_op = query.selected_operation
110
+ if selected_op
111
+ op_type = selected_op.operation_type
112
+ op_name = selected_op.name || "anonymous"
113
+ else
114
+ op_type = "query"
115
+ op_name = "anonymous"
116
+ end
117
+ "GraphQL/#{op_type}.#{op_name}"
118
+ end
119
+
106
120
  attr_reader :options
107
121
 
108
122
  def platform_key_cache(ctx)
@@ -16,12 +16,23 @@ module GraphQL
16
16
  "execute_query_lazy" => "execute.graphql",
17
17
  }
18
18
 
19
+ # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
20
+ # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
21
+ # It can also be specified per-query with `context[:set_scout_transaction_name]`.
19
22
  def initialize(options = {})
20
23
  self.class.include ScoutApm::Tracer
24
+ @set_transaction_name = options.fetch(:set_transaction_name, false)
21
25
  super(options)
22
26
  end
23
27
 
24
28
  def platform_trace(platform_key, key, data)
29
+ if key == "execute_query"
30
+ set_this_txn_name = data[:query].context[:set_scout_transaction_name]
31
+ if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
32
+ ScoutApm::Transaction.rename(transaction_name(data[:query]))
33
+ end
34
+ end
35
+
25
36
  self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS) do
26
37
  yield
27
38
  end
@@ -15,7 +15,7 @@ module GraphQL
15
15
  class ISO8601Date < GraphQL::Schema::Scalar
16
16
  description "An ISO 8601-encoded date"
17
17
 
18
- # @param value [Date,DateTime,String]
18
+ # @param value [Date,Time,DateTime,String]
19
19
  # @return [String]
20
20
  def self.coerce_result(value, _ctx)
21
21
  Date.parse(value.to_s).iso8601
@@ -25,7 +25,7 @@ module GraphQL
25
25
  # @return [Date]
26
26
  def self.coerce_input(str_value, _ctx)
27
27
  Date.iso8601(str_value)
28
- rescue ArgumentError
28
+ rescue ArgumentError, TypeError
29
29
  # Invalid input
30
30
  nil
31
31
  end
@@ -1,7 +1,9 @@
1
+ require 'time'
2
+
1
3
  # frozen_string_literal: true
2
4
  module GraphQL
3
5
  module Types
4
- # This scalar takes `DateTime`s and transmits them as strings,
6
+ # This scalar takes `Time`s and transmits them as strings,
5
7
  # using ISO 8601 format.
6
8
  #
7
9
  # Use it for fields or arguments as follows:
@@ -29,31 +31,33 @@ module GraphQL
29
31
  @time_precision = value
30
32
  end
31
33
 
32
- # @param value [Date,DateTime,String]
34
+ # @param value [Time,Date,DateTime,String]
33
35
  # @return [String]
34
- def self.coerce_result(value, _ctx)\
36
+ def self.coerce_result(value, _ctx)
35
37
  case value
36
- when DateTime
37
- return value.iso8601(time_precision)
38
38
  when Date
39
- return DateTime.parse(value.to_s).iso8601(time_precision)
39
+ return value.to_time.iso8601(time_precision)
40
40
  when ::String
41
- return DateTime.parse(value).iso8601(time_precision)
41
+ return Time.parse(value).iso8601(time_precision)
42
42
  else
43
- # In case some other API-compliant thing is given:
43
+ # Time, DateTime or compatible is given:
44
44
  return value.iso8601(time_precision)
45
- end
45
+ end
46
46
  rescue StandardError => error
47
- raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only Dates, DateTimes, and well-formatted Strings are used with this type. (#{error.message})"
47
+ raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only Times, Dates, DateTimes, and well-formatted Strings are used with this type. (#{error.message})"
48
48
  end
49
49
 
50
50
  # @param str_value [String]
51
- # @return [DateTime]
51
+ # @return [Time]
52
52
  def self.coerce_input(str_value, _ctx)
53
- DateTime.iso8601(str_value)
54
- rescue ArgumentError
55
- # Invalid input
56
- nil
53
+ Time.iso8601(str_value)
54
+ rescue ArgumentError, TypeError
55
+ begin
56
+ Date.iso8601(str_value).to_time
57
+ rescue ArgumentError, TypeError
58
+ # Invalid input
59
+ nil
60
+ end
57
61
  end
58
62
  end
59
63
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.10.9"
3
+ VERSION = "1.10.14"
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: 1.10.9
4
+ version: 1.10.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-04 00:00:00.000000000 Z
11
+ date: 2020-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -582,6 +582,7 @@ files:
582
582
  - lib/graphql/schema/member/has_ast_node.rb
583
583
  - lib/graphql/schema/member/has_fields.rb
584
584
  - lib/graphql/schema/member/has_path.rb
585
+ - lib/graphql/schema/member/has_unresolved_type_error.rb
585
586
  - lib/graphql/schema/member/instrumentation.rb
586
587
  - lib/graphql/schema/member/relay_shortcuts.rb
587
588
  - lib/graphql/schema/member/scoped.rb