graphql 1.10.7 → 1.10.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) 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 +38 -35
  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/object_type.rb +1 -1
  16. data/lib/graphql/relay/base_connection.rb +0 -2
  17. data/lib/graphql/schema.rb +17 -11
  18. data/lib/graphql/schema/argument.rb +6 -0
  19. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  20. data/lib/graphql/schema/enum.rb +9 -1
  21. data/lib/graphql/schema/field.rb +30 -21
  22. data/lib/graphql/schema/input_object.rb +10 -9
  23. data/lib/graphql/schema/interface.rb +5 -0
  24. data/lib/graphql/schema/list.rb +2 -1
  25. data/lib/graphql/schema/loader.rb +3 -0
  26. data/lib/graphql/schema/member.rb +1 -0
  27. data/lib/graphql/schema/member/has_arguments.rb +33 -13
  28. data/lib/graphql/schema/member/has_fields.rb +1 -1
  29. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  30. data/lib/graphql/schema/object.rb +9 -2
  31. data/lib/graphql/schema/resolver.rb +1 -1
  32. data/lib/graphql/schema/union.rb +6 -0
  33. data/lib/graphql/schema/warden.rb +7 -1
  34. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  35. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  36. data/lib/graphql/tracing.rb +5 -4
  37. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  38. data/lib/graphql/tracing/platform_tracing.rb +14 -0
  39. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  40. data/lib/graphql/types/iso_8601_date.rb +2 -2
  41. data/lib/graphql/types/iso_8601_date_time.rb +19 -15
  42. data/lib/graphql/version.rb +1 -1
  43. metadata +6 -3
@@ -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
@@ -872,8 +870,8 @@ module GraphQL
872
870
  # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}.
873
871
  # @see {#as_json}
874
872
  # @return [String]
875
- def to_json(*args)
876
- JSON.pretty_generate(as_json(*args))
873
+ def to_json(**args)
874
+ JSON.pretty_generate(as_json(**args))
877
875
  end
878
876
 
879
877
  # Return the Hash response of {Introspection::INTROSPECTION_QUERY}.
@@ -1391,7 +1389,7 @@ module GraphQL
1391
1389
  # rubocop:disable Lint/DuplicateMethods
1392
1390
  module ResolveTypeWithType
1393
1391
  def resolve_type(type, obj, ctx)
1394
- 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)
1395
1393
  type.resolve_type(obj, ctx)
1396
1394
  else
1397
1395
  super
@@ -1399,7 +1397,11 @@ module GraphQL
1399
1397
 
1400
1398
  after_lazy(first_resolved_type) do |resolved_type|
1401
1399
  if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) || resolved_type.is_a?(GraphQL::BaseType)
1402
- resolved_type
1400
+ if resolved_value
1401
+ [resolved_type, resolved_value]
1402
+ else
1403
+ resolved_type
1404
+ end
1403
1405
  else
1404
1406
  raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`"
1405
1407
  end
@@ -1508,7 +1510,11 @@ module GraphQL
1508
1510
 
1509
1511
  # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1510
1512
  def error_handler
1511
- @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
1512
1518
  end
1513
1519
 
1514
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,20 +10,19 @@ 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
22
23
  # Apply prepares, not great to have it duplicated here.
23
- @arguments_by_keyword = {}
24
24
  maybe_lazies = []
25
- self.class.arguments.each do |name, arg_defn|
26
- @arguments_by_keyword[arg_defn.keyword] = arg_defn
25
+ self.class.arguments.each_value do |arg_defn|
27
26
  ruby_kwargs_key = arg_defn.keyword
28
27
 
29
28
  if @ruby_style_hash.key?(ruby_kwargs_key)
@@ -56,7 +55,7 @@ module GraphQL
56
55
  # @return [GraphQL::Query::Context] The context for this query
57
56
  attr_reader :context
58
57
 
59
- # @return [GraphQL::Query::Arguments] The underlying arguments instance
58
+ # @return [GraphQL::Query::Arguments, GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance
60
59
  attr_reader :arguments
61
60
 
62
61
  # Ruby-like hash behaviors, read-only
@@ -208,10 +207,12 @@ module GraphQL
208
207
  return nil
209
208
  end
210
209
 
211
- input_values = coerce_arguments(nil, value, ctx)
210
+ arguments = coerce_arguments(nil, value, ctx)
212
211
 
213
- input_obj_instance = self.new(ruby_kwargs: input_values, context: ctx, defaults_used: nil)
214
- input_obj_instance.prepare
212
+ ctx.schema.after_lazy(arguments) do |resolved_arguments|
213
+ input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
214
+ input_obj_instance.prepare
215
+ end
215
216
  end
216
217
 
217
218
  # 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'")}
@@ -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'
@@ -63,10 +63,12 @@ module GraphQL
63
63
  self.class.argument_class(new_arg_class)
64
64
  end
65
65
 
66
+ # @api private
66
67
  # @param values [Hash<String, Object>]
67
68
  # @param context [GraphQL::Query::Context]
68
- # @return Hash<Symbol, Object>
69
+ # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
69
70
  def coerce_arguments(parent_object, values, context)
71
+ argument_values = {}
70
72
  kwarg_arguments = {}
71
73
  # Cache this hash to avoid re-merging it
72
74
  arg_defns = self.arguments
@@ -75,6 +77,7 @@ module GraphQL
75
77
  arg_lazies = arg_defns.map do |arg_name, arg_defn|
76
78
  arg_key = arg_defn.keyword
77
79
  has_value = false
80
+ default_used = false
78
81
  if values.key?(arg_name)
79
82
  has_value = true
80
83
  value = values[arg_name]
@@ -84,6 +87,7 @@ module GraphQL
84
87
  elsif arg_defn.default_value?
85
88
  has_value = true
86
89
  value = arg_defn.default_value
90
+ default_used = true
87
91
  end
88
92
 
89
93
  if has_value
@@ -91,36 +95,52 @@ module GraphQL
91
95
  loaded_value = nil
92
96
  if loads && !arg_defn.from_resolver?
93
97
  loaded_value = if arg_defn.type.list?
94
- value.map { |val| load_application_object(arg_defn, loads, val, context) }
98
+ loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
99
+ context.schema.after_any_lazies(loaded_values) { |result| result }
95
100
  else
96
101
  load_application_object(arg_defn, loads, value, context)
97
102
  end
98
103
  end
99
104
 
100
- context.schema.after_lazy(loaded_value) do |loaded_value|
101
- coerced_value = nil
102
- prepared_value = context.schema.error_handler.with_error_handling(context) do
103
-
104
- coerced_value = if loaded_value
105
- loaded_value
106
- else
107
- arg_defn.type.coerce_input(value, context)
108
- end
105
+ coerced_value = if loaded_value
106
+ loaded_value
107
+ else
108
+ context.schema.error_handler.with_error_handling(context) do
109
+ arg_defn.type.coerce_input(value, context)
110
+ end
111
+ end
109
112
 
113
+ context.schema.after_lazy(coerced_value) do |coerced_value|
114
+ prepared_value = context.schema.error_handler.with_error_handling(context) do
110
115
  arg_defn.prepare_value(parent_object, coerced_value, context: context)
111
116
  end
112
117
 
113
- kwarg_arguments[arg_defn.keyword] = prepared_value
118
+ kwarg_arguments[arg_key] = prepared_value
119
+ # TODO code smell to access such a deeply-nested constant in a distant module
120
+ argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
121
+ value: prepared_value,
122
+ definition: arg_defn,
123
+ default_used: default_used,
124
+ )
114
125
  end
115
126
  end
116
127
  end
117
128
 
118
129
  maybe_lazies.concat(arg_lazies)
119
130
  context.schema.after_any_lazies(maybe_lazies) do
120
- kwarg_arguments
131
+ GraphQL::Execution::Interpreter::Arguments.new(
132
+ keyword_arguments: kwarg_arguments,
133
+ argument_values: argument_values,
134
+ )
121
135
  end
122
136
  end
123
137
 
138
+ def arguments_statically_coercible?
139
+ return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
140
+
141
+ @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?)
142
+ end
143
+
124
144
  module ArgumentClassAccessor
125
145
  def argument_class(new_arg_class = nil)
126
146
  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