graphql 1.10.6 → 1.10.11

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 (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'")}