graphql 1.10.9 → 1.10.14

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 (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