graphql 1.10.8 → 1.10.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13caa4920df03f81232a23cf9fe5707ee3602a6c42bedace93870645f35d1248
4
- data.tar.gz: 597cbe2fd51c36a0e0de158d51c70d1d057a2712d44a34c71872ef0586a9c3d9
3
+ metadata.gz: 617464390cac424fae4c09fe3db0abbaa6e0d980cb349a9b8e51c4d84b2cb662
4
+ data.tar.gz: 4abf2fbac2606f6e8b493ca379a11569597500433bba51c8acbc7fdb891a3272
5
5
  SHA512:
6
- metadata.gz: ca5468c4b6632a4028f4bebf226c7a3aee5bb6786096b1b5a843b2ae476043160b1fe52111ae70d62c5a35cc66f9edd2d903128ebb2412c87054b9040f2a55aa
7
- data.tar.gz: e9ad2d87cb3898d849266bb743251d922024541ee9d08090cb4f7898331fa15962697a87f386011b7450ca9af3c730c23ff74ca4d2191af6c319d3168add1bdd
6
+ metadata.gz: de0cca8810127cf5649e68f6968e970131d7f34a0b1cd33230b79e56ae7e311134f96f588820caf273ef529c89e12a8df8c9afe213dbb9477cb798923044beb7
7
+ data.tar.gz: 30014092e85f256ace16b3635ed9e4e2c8a081904bca4e1e6197d0fff07f13d7997968ffc8aaee9de576d5372e219d432a8a62ae3406d81a61f9435debeaa247
@@ -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
@@ -89,12 +89,15 @@ require "graphql/name_validator"
89
89
  require "graphql/language"
90
90
  require "graphql/analysis"
91
91
  require "graphql/tracing"
92
- require "graphql/execution"
93
92
  require "graphql/dig"
93
+ require "graphql/execution"
94
94
  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"
@@ -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
@@ -8,6 +8,7 @@ module GraphQL
8
8
  # @see GraphQL::Query#arguments_for to get access to these objects.
9
9
  class Arguments
10
10
  extend Forwardable
11
+ include GraphQL::Dig
11
12
 
12
13
  # The Ruby-style arguments hash, ready for a resolver.
13
14
  # This hash is the one used at runtime.
@@ -20,15 +21,11 @@ module GraphQL
20
21
  @argument_values = argument_values
21
22
  end
22
23
 
23
- # Yields `ArgumentValue` instances which contain detailed metadata about each argument.
24
- def each_value
25
- argument_values.each { |arg_v| yield(arg_v) }
26
- end
27
-
28
24
  # @return [Hash{Symbol => ArgumentValue}]
29
25
  attr_reader :argument_values
30
26
 
31
27
  def_delegators :@keyword_arguments, :key?, :[], :keys, :each, :values
28
+ def_delegators :@argument_values, :each_value
32
29
 
33
30
  def inspect
34
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,13 @@ 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 are requested since extras mutate the argument data structure
464
+ if arg_owner.arguments_statically_coercible? && (!arg_owner.is_a?(GraphQL::Schema::Field) || arg_owner.extras.empty?)
465
+ query.arguments_for(ast_node, arg_owner)
466
+ else
467
+ # The arguments must be prepared in the context of the given object
468
+ query.arguments_for(ast_node, arg_owner, parent_object: graphql_object)
469
+ end
463
470
  end
464
471
 
465
472
  def write_invalid_null_in_response(path, invalid_null_error)
@@ -499,23 +506,11 @@ module GraphQL
499
506
  # at previous parts of the response.
500
507
  # This hash matches the response
501
508
  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
509
+ @types_at_paths.fetch(path)
508
510
  end
509
511
 
510
512
  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
513
+ @types_at_paths[path] = type
519
514
  nil
520
515
  end
521
516
 
@@ -545,7 +540,7 @@ module GraphQL
545
540
 
546
541
  def resolve_type(type, value, path)
547
542
  trace_payload = { context: context, type: type, object: value, path: path }
548
- resolved_type = query.trace("resolve_type", trace_payload) do
543
+ resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
549
544
  query.resolve_type(type, value)
550
545
  end
551
546
 
@@ -556,7 +551,7 @@ module GraphQL
556
551
  end
557
552
  end
558
553
  else
559
- resolved_type
554
+ [resolved_type, resolved_value]
560
555
  end
561
556
  end
562
557
 
@@ -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,20 @@ 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
+ if (a = own_arguments[argument_name])
64
+ a
65
+ else
66
+ for ancestor in ancestors
67
+ if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
68
+ return a
69
+ end
70
+ end
71
+ nil
72
+ end
73
+ end
74
+
61
75
  # @param new_arg_class [Class] A class to use for building argument definitions
62
76
  def argument_class(new_arg_class = nil)
63
77
  self.class.argument_class(new_arg_class)
@@ -135,6 +149,12 @@ module GraphQL
135
149
  end
136
150
  end
137
151
 
152
+ def arguments_statically_coercible?
153
+ return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
154
+
155
+ @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?)
156
+ end
157
+
138
158
  module ArgumentClassAccessor
139
159
  def argument_class(new_arg_class = nil)
140
160
  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
@@ -8,6 +8,7 @@ module GraphQL
8
8
  #
9
9
  # - No queueing system; ActiveJob should be added
10
10
  # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?})
11
+ # - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console.
11
12
  #
12
13
  # @example Adding ActionCableSubscriptions to your schema
13
14
  # class MySchema < GraphQL::Schema
@@ -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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class StatsdTracing < PlatformTracing
6
+ self.platform_keys = {
7
+ 'lex' => "graphql.lex",
8
+ 'parse' => "graphql.parse",
9
+ 'validate' => "graphql.validate",
10
+ 'analyze_query' => "graphql.analyze_query",
11
+ 'analyze_multiplex' => "graphql.analyze_multiplex",
12
+ 'execute_multiplex' => "graphql.execute_multiplex",
13
+ 'execute_query' => "graphql.execute_query",
14
+ 'execute_query_lazy' => "graphql.execute_query",
15
+ }
16
+
17
+ # @param statsd [Object] A statsd client
18
+ def initialize(statsd:, **rest)
19
+ @statsd = statsd
20
+ super(**rest)
21
+ end
22
+
23
+
24
+ def platform_trace(platform_key, key, data)
25
+ @statsd.time(platform_key) do
26
+ yield
27
+ end
28
+ end
29
+
30
+ def platform_field_key(type, field)
31
+ "graphql.#{type.graphql_name}.#{field.graphql_name}"
32
+ end
33
+
34
+ def platform_authorized_key(type)
35
+ "graphql.authorized.#{type.graphql_name}"
36
+ end
37
+
38
+ def platform_resolve_type_key(type)
39
+ "graphql.resolve_type.#{type.graphql_name}"
40
+ end
41
+ end
42
+ 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.8"
3
+ VERSION = "1.10.13"
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.8
4
+ version: 1.10.13
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-04-27 00:00:00.000000000 Z
11
+ date: 2020-06-16 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
@@ -695,6 +696,7 @@ files:
695
696
  - lib/graphql/tracing/prometheus_tracing/graphql_collector.rb
696
697
  - lib/graphql/tracing/scout_tracing.rb
697
698
  - lib/graphql/tracing/skylight_tracing.rb
699
+ - lib/graphql/tracing/statsd_tracing.rb
698
700
  - lib/graphql/type_kinds.rb
699
701
  - lib/graphql/types.rb
700
702
  - lib/graphql/types/big_int.rb