graphql 1.10.6 → 1.10.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +50 -8
  3. data/lib/graphql.rb +4 -4
  4. data/lib/graphql/analysis/ast/query_complexity.rb +1 -1
  5. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  6. data/lib/graphql/execution/instrumentation.rb +1 -1
  7. data/lib/graphql/execution/interpreter.rb +2 -0
  8. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  9. data/lib/graphql/execution/interpreter/arguments.rb +36 -0
  10. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -7
  11. data/lib/graphql/execution/interpreter/runtime.rb +47 -38
  12. data/lib/graphql/execution/lookahead.rb +3 -1
  13. data/lib/graphql/internal_representation/scope.rb +2 -2
  14. data/lib/graphql/internal_representation/visit.rb +2 -2
  15. data/lib/graphql/language/document_from_schema_definition.rb +42 -23
  16. data/lib/graphql/object_type.rb +1 -1
  17. data/lib/graphql/relay/base_connection.rb +0 -2
  18. data/lib/graphql/schema.rb +28 -13
  19. data/lib/graphql/schema/argument.rb +6 -0
  20. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  21. data/lib/graphql/schema/enum.rb +9 -1
  22. data/lib/graphql/schema/field.rb +30 -21
  23. data/lib/graphql/schema/input_object.rb +9 -6
  24. data/lib/graphql/schema/interface.rb +5 -0
  25. data/lib/graphql/schema/list.rb +7 -1
  26. data/lib/graphql/schema/loader.rb +110 -103
  27. data/lib/graphql/schema/member.rb +1 -0
  28. data/lib/graphql/schema/member/has_arguments.rb +33 -13
  29. data/lib/graphql/schema/member/has_fields.rb +1 -1
  30. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  31. data/lib/graphql/schema/non_null.rb +5 -0
  32. data/lib/graphql/schema/object.rb +10 -3
  33. data/lib/graphql/schema/printer.rb +0 -14
  34. data/lib/graphql/schema/resolver.rb +1 -1
  35. data/lib/graphql/schema/union.rb +6 -0
  36. data/lib/graphql/schema/warden.rb +7 -1
  37. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  38. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  39. data/lib/graphql/tracing.rb +5 -4
  40. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  41. data/lib/graphql/tracing/platform_tracing.rb +14 -0
  42. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  43. data/lib/graphql/types/iso_8601_date.rb +3 -3
  44. data/lib/graphql/types/iso_8601_date_time.rb +18 -8
  45. data/lib/graphql/version.rb +1 -1
  46. metadata +6 -3
@@ -23,11 +23,11 @@ module GraphQL
23
23
 
24
24
  # Traverse a node in a rewritten query tree,
25
25
  # visiting the node itself and each of its typed children.
26
- def each_node(node)
26
+ def each_node(node, &block)
27
27
  yield(node)
28
28
  node.typed_children.each do |obj_type, children|
29
29
  children.each do |name, node|
30
- each_node(node) { |n| yield(n) }
30
+ each_node(node, &block)
31
31
  end
32
32
  end
33
33
  end
@@ -49,7 +49,7 @@ module GraphQL
49
49
  subscription: (s = warden.root_type_for_operation("subscription")) && s.graphql_name,
50
50
  # This only supports directives from parsing,
51
51
  # use a custom printer to add to this list.
52
- directives: @schema.ast_node ? @schema.ast_node.directives : [],
52
+ directives: ast_directives(@schema),
53
53
  )
54
54
  end
55
55
 
@@ -59,32 +59,26 @@ module GraphQL
59
59
  interfaces: warden.interfaces(object_type).sort_by(&:graphql_name).map { |iface| build_type_name_node(iface) },
60
60
  fields: build_field_nodes(warden.fields(object_type)),
61
61
  description: object_type.description,
62
+ directives: ast_directives(object_type),
62
63
  )
63
64
  end
64
65
 
65
66
  def build_field_node(field)
66
- field_node = GraphQL::Language::Nodes::FieldDefinition.new(
67
+ GraphQL::Language::Nodes::FieldDefinition.new(
67
68
  name: field.graphql_name,
68
69
  arguments: build_argument_nodes(warden.arguments(field)),
69
70
  type: build_type_name_node(field.type),
70
71
  description: field.description,
72
+ directives: ast_directives(field),
71
73
  )
72
-
73
- if field.deprecation_reason
74
- field_node = field_node.merge_directive(
75
- name: GraphQL::Directive::DeprecatedDirective.graphql_name,
76
- arguments: [GraphQL::Language::Nodes::Argument.new(name: "reason", value: field.deprecation_reason)]
77
- )
78
- end
79
-
80
- field_node
81
74
  end
82
75
 
83
76
  def build_union_type_node(union_type)
84
77
  GraphQL::Language::Nodes::UnionTypeDefinition.new(
85
78
  name: union_type.graphql_name,
86
79
  description: union_type.description,
87
- types: warden.possible_types(union_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) }
80
+ types: warden.possible_types(union_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) },
81
+ directives: ast_directives(union_type),
88
82
  )
89
83
  end
90
84
 
@@ -92,7 +86,8 @@ module GraphQL
92
86
  GraphQL::Language::Nodes::InterfaceTypeDefinition.new(
93
87
  name: interface_type.graphql_name,
94
88
  description: interface_type.description,
95
- fields: build_field_nodes(warden.fields(interface_type))
89
+ fields: build_field_nodes(warden.fields(interface_type)),
90
+ directives: ast_directives(interface_type),
96
91
  )
97
92
  end
98
93
 
@@ -103,29 +98,23 @@ module GraphQL
103
98
  build_enum_value_node(enum_value)
104
99
  end,
105
100
  description: enum_type.description,
101
+ directives: ast_directives(enum_type),
106
102
  )
107
103
  end
108
104
 
109
105
  def build_enum_value_node(enum_value)
110
- enum_value_node = GraphQL::Language::Nodes::EnumValueDefinition.new(
106
+ GraphQL::Language::Nodes::EnumValueDefinition.new(
111
107
  name: enum_value.graphql_name,
112
108
  description: enum_value.description,
109
+ directives: ast_directives(enum_value),
113
110
  )
114
-
115
- if enum_value.deprecation_reason
116
- enum_value_node = enum_value_node.merge_directive(
117
- name: GraphQL::Directive::DeprecatedDirective.graphql_name,
118
- arguments: [GraphQL::Language::Nodes::Argument.new(name: "reason", value: enum_value.deprecation_reason)]
119
- )
120
- end
121
-
122
- enum_value_node
123
111
  end
124
112
 
125
113
  def build_scalar_type_node(scalar_type)
126
114
  GraphQL::Language::Nodes::ScalarTypeDefinition.new(
127
115
  name: scalar_type.graphql_name,
128
116
  description: scalar_type.description,
117
+ directives: ast_directives(scalar_type),
129
118
  )
130
119
  end
131
120
 
@@ -141,6 +130,7 @@ module GraphQL
141
130
  description: argument.description,
142
131
  type: build_type_name_node(argument.type),
143
132
  default_value: default_value,
133
+ directives: ast_directives(argument),
144
134
  )
145
135
 
146
136
  argument_node
@@ -151,6 +141,7 @@ module GraphQL
151
141
  name: input_object.graphql_name,
152
142
  fields: build_argument_nodes(warden.arguments(input_object)),
153
143
  description: input_object.description,
144
+ directives: ast_directives(input_object),
154
145
  )
155
146
  end
156
147
 
@@ -292,6 +283,34 @@ module GraphQL
292
283
  (schema.subscription.nil? || schema.subscription.graphql_name == 'Subscription')
293
284
  end
294
285
 
286
+ def ast_directives(member)
287
+ ast_directives = member.ast_node ? member.ast_node.directives : []
288
+
289
+ # If this schema was built from IDL, it will already have `@deprecated` in `ast_node.directives`
290
+ if member.respond_to?(:deprecation_reason) &&
291
+ (reason = member.deprecation_reason) &&
292
+ ast_directives.none? { |d| d.name == "deprecated" }
293
+
294
+ arguments = []
295
+
296
+ if reason != GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON
297
+ arguments << GraphQL::Language::Nodes::Argument.new(
298
+ name: "reason",
299
+ value: reason
300
+ )
301
+ end
302
+
303
+ ast_directives += [
304
+ GraphQL::Language::Nodes::Directive.new(
305
+ name: GraphQL::Directive::DeprecatedDirective.graphql_name,
306
+ arguments: arguments
307
+ )
308
+ ]
309
+ end
310
+
311
+ ast_directives
312
+ end
313
+
295
314
  attr_reader :schema, :warden, :always_include_schema,
296
315
  :include_introspection_types, :include_built_in_directives, :include_built_in_scalars
297
316
  end
@@ -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
@@ -74,8 +74,6 @@ module GraphQL
74
74
 
75
75
  def decode(data)
76
76
  @encoder.decode(data, nonce: true)
77
- rescue ArgumentError
78
- raise GraphQL::ExecutionError, "Invalid cursor: #{data.inspect}"
79
77
  end
80
78
 
81
79
  # The value passed as `first:`, if there was one. Negative numbers become `0`.
@@ -101,14 +101,12 @@ module GraphQL
101
101
  # - Right away, if `value` is not registered with `lazy_resolve`
102
102
  # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`)
103
103
  # @api private
104
- def after_lazy(value)
104
+ def after_lazy(value, &block)
105
105
  if lazy?(value)
106
106
  GraphQL::Execution::Lazy.new do
107
107
  result = sync_lazy(value)
108
108
  # The returned result might also be lazy, so check it, too
109
- after_lazy(result) do |final_result|
110
- yield(final_result) if block_given?
111
- end
109
+ after_lazy(result, &block)
112
110
  end
113
111
  else
114
112
  yield(value) if block_given?
@@ -146,10 +144,10 @@ module GraphQL
146
144
  def after_any_lazies(maybe_lazies)
147
145
  if maybe_lazies.any? { |l| lazy?(l) }
148
146
  GraphQL::Execution::Lazy.all(maybe_lazies).then do |result|
149
- yield
147
+ yield result
150
148
  end
151
149
  else
152
- yield
150
+ yield maybe_lazies
153
151
  end
154
152
  end
155
153
  end
@@ -555,8 +553,17 @@ module GraphQL
555
553
  # @param context [GraphQL::Query::Context] The context for the current query
556
554
  # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
557
555
  def possible_types(type_defn, context = GraphQL::Query::NullContext)
558
- @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
559
- @possible_types.possible_types(type_defn, context)
556
+ if context == GraphQL::Query::NullContext
557
+ @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
558
+ @possible_types.possible_types(type_defn, context)
559
+ else
560
+ # Use the incoming context to cache this instance --
561
+ # if it were cached on the schema, we'd have a memory leak
562
+ # https://github.com/rmosolgo/graphql-ruby/issues/2878
563
+ ns = context.namespace(:possible_types)
564
+ per_query_possible_types = ns[:possible_types] ||= GraphQL::Schema::PossibleTypes.new(self)
565
+ per_query_possible_types.possible_types(type_defn, context)
566
+ end
560
567
  end
561
568
 
562
569
  # @see [GraphQL::Schema::Warden] Resticted access to root types
@@ -863,8 +870,8 @@ module GraphQL
863
870
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
864
871
  # @see {#as_json}
865
872
  # @return [String]
866
- def to_json(*args)
867
- JSON.pretty_generate(as_json(*args))
873
+ def to_json(**args)
874
+ JSON.pretty_generate(as_json(**args))
868
875
  end
869
876
 
870
877
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
@@ -1382,7 +1389,7 @@ module GraphQL
1382
1389
  # rubocop:disable Lint/DuplicateMethods
1383
1390
  module ResolveTypeWithType
1384
1391
  def resolve_type(type, obj, ctx)
1385
- 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)
1386
1393
  type.resolve_type(obj, ctx)
1387
1394
  else
1388
1395
  super
@@ -1390,7 +1397,11 @@ module GraphQL
1390
1397
 
1391
1398
  after_lazy(first_resolved_type) do |resolved_type|
1392
1399
  if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1393
- resolved_type
1400
+ if resolved_value
1401
+ [resolved_type, resolved_value]
1402
+ else
1403
+ resolved_type
1404
+ end
1394
1405
  else
1395
1406
  raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1396
1407
  end
@@ -1499,7 +1510,11 @@ module GraphQL
1499
1510
 
1500
1511
  # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1501
1512
  def error_handler
1502
- @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
1503
1518
  end
1504
1519
 
1505
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
@@ -13,6 +13,8 @@ module GraphQL
13
13
  def self.decode(encoded_text, nonce: false)
14
14
  # urlsafe_decode64 is for forward compatibility
15
15
  Base64Bp.urlsafe_decode64(encoded_text)
16
+ rescue ArgumentError
17
+ raise GraphQL::ExecutionError, "Invalid input: #{encoded_text.inspect}"
16
18
  end
17
19
  end
18
20
  end
@@ -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
@@ -47,6 +47,11 @@ module GraphQL
47
47
  @resolver_class
48
48
  end
49
49
 
50
+ # @return [Boolean] Is this field a predefined introspection field?
51
+ def introspection?
52
+ @introspection
53
+ end
54
+
50
55
  alias :mutation :resolver
51
56
 
52
57
  # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value)
@@ -551,34 +556,36 @@ module GraphQL
551
556
  begin
552
557
  # Unwrap the GraphQL object to get the application object.
553
558
  application_object = object.object
554
- if self.authorized?(application_object, args, ctx)
555
- # Apply field extensions
556
- with_extensions(object, args, ctx) do |extended_obj, extended_args|
557
- field_receiver = if @resolver_class
558
- resolver_obj = if extended_obj.is_a?(GraphQL::Schema::Object)
559
- 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)
560
570
  else
561
571
  extended_obj
562
572
  end
563
- @resolver_class.new(object: resolver_obj, context: ctx, field: self)
564
- else
565
- extended_obj
566
- end
567
573
 
568
- if field_receiver.respond_to?(@resolver_method)
569
- # Call the method with kwargs, if there are any
570
- if extended_args.any?
571
- 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
572
581
  else
573
- field_receiver.public_send(@resolver_method)
582
+ resolve_field_method(field_receiver, extended_args, ctx)
574
583
  end
575
- else
576
- resolve_field_method(field_receiver, extended_args, ctx)
577
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)
578
588
  end
579
- else
580
- err = GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: ctx, field: self)
581
- ctx.schema.unauthorized_field(err)
582
589
  end
583
590
  rescue GraphQL::UnauthorizedFieldError => err
584
591
  err.field ||= self
@@ -663,10 +670,12 @@ module GraphQL
663
670
  loaded_value = if loads && !arg_defn.from_resolver?
664
671
  if arg_defn.type.list?
665
672
  loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, field_ctx.query.context) }
666
- maybe_lazies.concat(loaded_values)
673
+ field_ctx.schema.after_any_lazies(loaded_values) { |result| result }
667
674
  else
668
675
  load_application_object(arg_defn, loads, value, field_ctx.query.context)
669
676
  end
677
+ elsif arg_defn.type.list? && value.is_a?(Array)
678
+ field_ctx.schema.after_any_lazies(value, &:itself)
670
679
  else
671
680
  value
672
681
  end
@@ -10,12 +10,13 @@ module GraphQL
10
10
 
11
11
  include GraphQL::Dig
12
12
 
13
- def initialize(values = nil, ruby_kwargs: nil, context:, defaults_used:)
13
+ def initialize(arguments = nil, ruby_kwargs: nil, context:, defaults_used:)
14
14
  @context = context
15
15
  if ruby_kwargs
16
16
  @ruby_style_hash = ruby_kwargs
17
+ @arguments = arguments
17
18
  else
18
- @arguments = self.class.arguments_class.new(values, context: context, defaults_used: defaults_used)
19
+ @arguments = self.class.arguments_class.new(arguments, context: context, defaults_used: defaults_used)
19
20
  # Symbolized, underscored hash:
20
21
  @ruby_style_hash = @arguments.to_kwargs
21
22
  end
@@ -56,7 +57,7 @@ module GraphQL
56
57
  # @return [GraphQL::Query::Context] The context for this query
57
58
  attr_reader :context
58
59
 
59
- # @return [GraphQL::Query::Arguments] The underlying arguments instance
60
+ # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
60
61
  attr_reader :arguments
61
62
 
62
63
  # Ruby-like hash behaviors, read-only
@@ -208,10 +209,12 @@ module GraphQL
208
209
  return nil
209
210
  end
210
211
 
211
- input_values = coerce_arguments(nil, value, ctx)
212
+ arguments = coerce_arguments(nil, value, ctx)
212
213
 
213
- input_obj_instance = self.new(ruby_kwargs: input_values, context: ctx, defaults_used: nil)
214
- input_obj_instance.prepare
214
+ ctx.schema.after_lazy(arguments) do |resolved_arguments|
215
+ input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
216
+ input_obj_instance.prepare
217
+ end
215
218
  end
216
219
 
217
220
  # It's funny to think of a _result_ of an input object.
@@ -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'")}