graphql 2.5.11 → 2.6.10

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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/field_extractor.rb +17 -1
  4. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  5. data/lib/generators/graphql/templates/schema.erb +2 -1
  6. data/lib/graphql/analysis/query_complexity.rb +31 -15
  7. data/lib/graphql/analysis.rb +20 -13
  8. data/lib/graphql/backtrace/table.rb +10 -1
  9. data/lib/graphql/current.rb +7 -1
  10. data/lib/graphql/dashboard/application_controller.rb +45 -0
  11. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  12. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  13. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  20. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  21. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  22. data/lib/graphql/dashboard.rb +14 -73
  23. data/lib/graphql/dataloader/async_dataloader.rb +336 -60
  24. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  25. data/lib/graphql/dataloader/source.rb +32 -19
  26. data/lib/graphql/dataloader.rb +107 -39
  27. data/lib/graphql/date_encoding_error.rb +1 -1
  28. data/lib/graphql/execution/directive_checks.rb +2 -0
  29. data/lib/graphql/execution/field_resolve_step.rb +804 -0
  30. data/lib/graphql/execution/finalize.rb +229 -0
  31. data/lib/graphql/execution/input_values.rb +333 -0
  32. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  33. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  34. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +60 -40
  37. data/lib/graphql/execution/interpreter.rb +17 -23
  38. data/lib/graphql/execution/lazy.rb +1 -1
  39. data/lib/graphql/execution/load_argument_step.rb +102 -0
  40. data/lib/graphql/execution/multiplex.rb +1 -1
  41. data/lib/graphql/execution/next.rb +98 -0
  42. data/lib/graphql/execution/prepare_object_step.rb +151 -0
  43. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  44. data/lib/graphql/execution/runner.rb +457 -0
  45. data/lib/graphql/execution/selections_step.rb +95 -0
  46. data/lib/graphql/execution.rb +15 -4
  47. data/lib/graphql/execution_error.rb +17 -12
  48. data/lib/graphql/float_decoding_error.rb +13 -0
  49. data/lib/graphql/float_encoding_error.rb +28 -0
  50. data/lib/graphql/introspection/directive_type.rb +7 -3
  51. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  52. data/lib/graphql/introspection/entry_points.rb +11 -3
  53. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  54. data/lib/graphql/introspection/field_type.rb +13 -5
  55. data/lib/graphql/introspection/input_value_type.rb +21 -13
  56. data/lib/graphql/introspection/schema_type.rb +6 -2
  57. data/lib/graphql/introspection/type_type.rb +64 -28
  58. data/lib/graphql/invalid_null_error.rb +11 -5
  59. data/lib/graphql/language/block_string.rb +6 -11
  60. data/lib/graphql/language/cache.rb +84 -14
  61. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  62. data/lib/graphql/language/lexer.rb +27 -17
  63. data/lib/graphql/language/nodes.rb +2 -1
  64. data/lib/graphql/language/parser.rb +1 -1
  65. data/lib/graphql/language.rb +32 -17
  66. data/lib/graphql/pagination/connection.rb +2 -0
  67. data/lib/graphql/pagination/connections.rb +30 -0
  68. data/lib/graphql/pagination/relation_connection.rb +10 -1
  69. data/lib/graphql/query/context.rb +10 -3
  70. data/lib/graphql/query/null_context.rb +9 -3
  71. data/lib/graphql/query/partial.rb +18 -3
  72. data/lib/graphql/query/variable_validation_error.rb +16 -1
  73. data/lib/graphql/query/variables.rb +1 -1
  74. data/lib/graphql/query.rb +12 -3
  75. data/lib/graphql/railtie.rb +2 -1
  76. data/lib/graphql/runtime_error.rb +6 -0
  77. data/lib/graphql/schema/argument.rb +13 -1
  78. data/lib/graphql/schema/build_from_definition.rb +28 -1
  79. data/lib/graphql/schema/directive/feature.rb +4 -0
  80. data/lib/graphql/schema/directive/transform.rb +20 -0
  81. data/lib/graphql/schema/directive.rb +48 -13
  82. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  83. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  84. data/lib/graphql/schema/field.rb +79 -48
  85. data/lib/graphql/schema/field_extension.rb +11 -8
  86. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  87. data/lib/graphql/schema/input_object.rb +6 -2
  88. data/lib/graphql/schema/interface.rb +26 -0
  89. data/lib/graphql/schema/introspection_system.rb +6 -21
  90. data/lib/graphql/schema/list.rb +5 -1
  91. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  92. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  93. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  94. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  95. data/lib/graphql/schema/member/has_fields.rb +86 -5
  96. data/lib/graphql/schema/member.rb +5 -0
  97. data/lib/graphql/schema/non_null.rb +1 -1
  98. data/lib/graphql/schema/object.rb +1 -0
  99. data/lib/graphql/schema/printer.rb +1 -1
  100. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  101. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  102. data/lib/graphql/schema/resolver.rb +76 -1
  103. data/lib/graphql/schema/subscription.rb +53 -10
  104. data/lib/graphql/schema/timeout.rb +2 -2
  105. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  106. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  107. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  108. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  109. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  110. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  111. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  112. data/lib/graphql/schema/validator/required_validator.rb +38 -5
  113. data/lib/graphql/schema/validator.rb +9 -0
  114. data/lib/graphql/schema/visibility/profile.rb +74 -53
  115. data/lib/graphql/schema/visibility/visit.rb +1 -1
  116. data/lib/graphql/schema/visibility.rb +32 -24
  117. data/lib/graphql/schema/wrapper.rb +11 -1
  118. data/lib/graphql/schema.rb +93 -27
  119. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  120. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  121. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  122. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  123. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  126. data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
  127. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  128. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  129. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  130. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  131. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  132. data/lib/graphql/static_validation/validation_context.rb +1 -1
  133. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  134. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
  135. data/lib/graphql/subscriptions/serialize.rb +10 -4
  136. data/lib/graphql/subscriptions.rb +36 -1
  137. data/lib/graphql/testing/helpers.rb +12 -9
  138. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  139. data/lib/graphql/testing.rb +1 -0
  140. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  141. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  142. data/lib/graphql/tracing/perfetto_trace.rb +213 -81
  143. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  144. data/lib/graphql/tracing/trace.rb +6 -0
  145. data/lib/graphql/types/float.rb +18 -4
  146. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  147. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  148. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  149. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  150. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  151. data/lib/graphql/unauthorized_error.rb +9 -1
  152. data/lib/graphql/version.rb +1 -1
  153. data/lib/graphql.rb +8 -2
  154. metadata +23 -59
@@ -15,7 +15,8 @@ module GraphQL
15
15
  initializer("graphql.cache") do |app|
16
16
  if config.graphql.parser_cache
17
17
  Language::Parser.cache ||= Language::Cache.new(
18
- app.root.join("tmp/cache/graphql")
18
+ app.root.join("tmp/cache/graphql"),
19
+ secret: app.secret_key_base
19
20
  )
20
21
  end
21
22
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class RuntimeError < Error
4
+ include GraphQL::Execution::Finalizer
5
+ end
6
+ end
@@ -4,6 +4,7 @@ module GraphQL
4
4
  class Argument
5
5
  include GraphQL::Schema::Member::HasPath
6
6
  include GraphQL::Schema::Member::HasAstNode
7
+ include GraphQL::Schema::Member::HasAuthorization
7
8
  include GraphQL::Schema::Member::HasDirectives
8
9
  include GraphQL::Schema::Member::HasDeprecationReason
9
10
  include GraphQL::Schema::Member::HasValidators
@@ -39,9 +40,14 @@ module GraphQL
39
40
  # @param arg_name [Symbol]
40
41
  # @param type_expr
41
42
  # @param desc [String]
43
+ # @param type [Class, Array<Class>] Input type; positional argument also accepted
44
+ # @param name [Symbol] positional argument also accepted # @param loads [Class, Array<Class>] A GraphQL type to load for the given ID when one is present
45
+ # @param definition_block [Proc] Called with the newly-created {Argument}
46
+ # @param owner [Class] Private, used by GraphQL-Ruby during schema definition
42
47
  # @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
43
48
  # @param description [String]
44
49
  # @param default_value [Object]
50
+ # @param loads [Class, Array<Class>] A GraphQL type to load for the given ID when one is present
45
51
  # @param as [Symbol] Override the keyword name when passed to a method
46
52
  # @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution
47
53
  # @param camelize [Boolean] if true, the name will be camelized when building the schema
@@ -50,6 +56,8 @@ module GraphQL
50
56
  # @param deprecation_reason [String]
51
57
  # @param validates [Hash, nil] Options for building validators, if any should be applied
52
58
  # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
59
+ # @param comment [String] Private, used by GraphQL-Ruby when parsing GraphQL schema files
60
+ # @param ast_node [GraphQL::Language::Nodes::InputValueDefinition] Private, used by GraphQL-Ruby when parsing schema files
53
61
  def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
54
62
  arg_name ||= name
55
63
  @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
@@ -86,7 +94,7 @@ module GraphQL
86
94
  end
87
95
 
88
96
  if required == :nullable
89
- self.owner.validates(required: { argument: arg_name })
97
+ self.owner.validates(required: { argument: @keyword })
90
98
  end
91
99
 
92
100
  if definition_block
@@ -157,6 +165,10 @@ module GraphQL
157
165
  true
158
166
  end
159
167
 
168
+ def authorizes?(context)
169
+ self.method(:authorized?).owner != GraphQL::Schema::Argument || type.unwrap.authorizes?(context)
170
+ end
171
+
160
172
  def authorized?(obj, value, ctx)
161
173
  authorized_as_type?(obj, value, ctx, as_type: type)
162
174
  end
@@ -82,6 +82,7 @@ module GraphQL
82
82
  replace_late_bound_types_with_built_in(types)
83
83
 
84
84
  schema_extensions = nil
85
+ definitions_by_name = nil
85
86
  document.definitions.each do |definition|
86
87
  case definition
87
88
  when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
@@ -99,6 +100,23 @@ module GraphQL
99
100
  # It's possible that this was already loaded by the directives
100
101
  prev_type = types[definition.name]
101
102
  if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
103
+ if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
104
+ interface_names = definition.interfaces.map(&:name)
105
+ if !interface_names.empty?
106
+ definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name|
107
+ by_name[d.name] ||= d if d.respond_to?(:name)
108
+ end
109
+ transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) }
110
+ transitive_names.flatten!
111
+ transitive_names.compact!
112
+ else
113
+ transitive_names = interface_names
114
+ end
115
+ if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
116
+ raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
117
+ end
118
+ end
119
+
102
120
  types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve, base_types)
103
121
  end
104
122
  end
@@ -266,6 +284,8 @@ module GraphQL
266
284
  build_scalar_type(definition, type_resolver, base_types[:scalar], default_resolve: default_resolve)
267
285
  when GraphQL::Language::Nodes::InputObjectTypeDefinition
268
286
  build_input_object_type(definition, type_resolver, base_types[:input_object])
287
+ when GraphQL::Language::Nodes::DirectiveDefinition
288
+ build_directive(definition, type_resolver)
269
289
  end
270
290
  end
271
291
 
@@ -510,6 +530,7 @@ module GraphQL
510
530
  camelize: false,
511
531
  directives: prepare_directives(field_definition, type_resolver),
512
532
  resolver_method: resolve_method_name,
533
+ resolve_batch: resolve_method_name,
513
534
  )
514
535
 
515
536
  builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)
@@ -526,6 +547,12 @@ module GraphQL
526
547
  field_instance = context.types.field(owner, field_name)
527
548
  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
528
549
  }
550
+ owner.define_singleton_method(method_name) { |objects, context, **args|
551
+ field_instance = context.types.field(owner, field_name)
552
+ objects.map do |object|
553
+ context.schema.definition_default_resolve.call(self, field_instance, object, args, context)
554
+ end
555
+ }
529
556
  end
530
557
 
531
558
  def build_resolve_type(lookup_hash, directives, missing_type_handler)
@@ -544,7 +571,7 @@ module GraphQL
544
571
  when GraphQL::Language::Nodes::ListType
545
572
  resolve_type_proc.call(ast_node.of_type).to_list_type
546
573
  when String
547
- directives[ast_node]
574
+ directives[ast_node] ||= missing_type_handler.call(ast_node)
548
575
  else
549
576
  raise "Unexpected ast_node: #{ast_node.inspect}"
550
577
  end
@@ -60,6 +60,10 @@ module GraphQL
60
60
  def self.enabled?(flag_name, object, context)
61
61
  raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})"
62
62
  end
63
+
64
+ def self.resolve_field(...); end
65
+ def self.resolve_fragment_spread(...); end
66
+ def self.resolve_inline_fragment(...); end
63
67
  end
64
68
  end
65
69
  end
@@ -54,6 +54,26 @@ module GraphQL
54
54
  nil
55
55
  end
56
56
  end
57
+
58
+ def self.resolve_field(ast_nodes, parent_type, field_defn, objects, arguments, context)
59
+ transform_name = arguments[:by]
60
+ if TRANSFORMS.include?(transform_name)
61
+ Transformer.new(transform_name)
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ class Transformer
68
+ include Execution::PostProcessor
69
+ def initialize(transform)
70
+ @transform = transform
71
+ end
72
+ def after_resolve(field_results)
73
+ field_results.map! { |r| r.respond_to?(@transform) ? r.public_send(@transform) : r }
74
+ field_results
75
+ end
76
+ end
57
77
  end
58
78
  end
59
79
  end
@@ -31,17 +31,25 @@ module GraphQL
31
31
 
32
32
  def locations(*new_locations)
33
33
  if !new_locations.empty?
34
+ is_runtime = false
34
35
  new_locations.each do |new_loc|
35
- if !LOCATIONS.include?(new_loc.to_sym)
36
+ loc_sym = new_loc.to_sym
37
+ if !LOCATIONS.include?(loc_sym)
36
38
  raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` "
37
39
  end
40
+ is_runtime ||= RUNTIME_LOCATIONS.include?(loc_sym)
38
41
  end
39
42
  @locations = new_locations
43
+ @is_runtime = is_runtime
40
44
  else
41
45
  @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : [])
42
46
  end
43
47
  end
44
48
 
49
+ def runtime?
50
+ @is_runtime
51
+ end
52
+
45
53
  def default_directive(new_default_directive = nil)
46
54
  if new_default_directive != nil
47
55
  @default_directive = new_default_directive
@@ -104,8 +112,12 @@ module GraphQL
104
112
 
105
113
  def inherited(subclass)
106
114
  super
115
+ parent_class = self
107
116
  subclass.class_exec do
108
117
  @default_graphql_name ||= nil
118
+ @locations = parent_class.locations
119
+ @is_runtime = parent_class.runtime?
120
+ @repeatable = false
109
121
  end
110
122
  end
111
123
  end
@@ -129,20 +141,41 @@ module GraphQL
129
141
  # not runtime arguments.
130
142
  context = Query::NullContext.instance
131
143
  self.class.all_argument_definitions.each do |arg_defn|
132
- if arguments.key?(arg_defn.keyword)
133
- value = arguments[arg_defn.keyword]
144
+ keyword = arg_defn.keyword
145
+ arg_type = arg_defn.type
146
+ if arguments.key?(keyword)
147
+ value = arguments[keyword]
134
148
  # This is a Ruby-land value; convert it to graphql for validation
135
149
  graphql_value = begin
136
- arg_defn.type.unwrap.coerce_isolated_result(value)
150
+ coerce_value = value
151
+ if arg_type.list? && (!coerce_value.nil?) && (!coerce_value.is_a?(Array))
152
+ # When validating inputs, GraphQL accepts a single item
153
+ # and implicitly converts it to a one-item list.
154
+ # However, we're using result coercion here to go from Ruby value
155
+ # to GraphQL value, so it doesn't have that feature.
156
+ # Keep the GraphQL-type behavior but implement it manually:
157
+ wrap_type = arg_type
158
+ while wrap_type.list?
159
+ if wrap_type.non_null?
160
+ wrap_type = wrap_type.of_type
161
+ end
162
+ wrap_type = wrap_type.of_type
163
+ coerce_value = [coerce_value]
164
+ end
165
+ end
166
+ arg_type.coerce_isolated_result(coerce_value)
137
167
  rescue GraphQL::Schema::Enum::UnresolvedValueError
138
168
  # Let validation handle this
139
169
  value
140
170
  end
171
+ elsif arg_defn.default_value?
172
+ value = arg_defn.default_value
173
+ graphql_value = arg_type.coerce_isolated_result(value) unless value.nil?
141
174
  else
142
175
  value = graphql_value = nil
143
176
  end
144
177
 
145
- result = arg_defn.type.validate_input(graphql_value, context)
178
+ result = arg_type.validate_input(graphql_value, context)
146
179
  if !result.valid?
147
180
  raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}"
148
181
  end
@@ -159,13 +192,16 @@ module GraphQL
159
192
  end
160
193
 
161
194
  LOCATIONS = [
162
- QUERY = :QUERY,
163
- MUTATION = :MUTATION,
164
- SUBSCRIPTION = :SUBSCRIPTION,
165
- FIELD = :FIELD,
166
- FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION,
167
- FRAGMENT_SPREAD = :FRAGMENT_SPREAD,
168
- INLINE_FRAGMENT = :INLINE_FRAGMENT,
195
+ *(RUNTIME_LOCATIONS = [
196
+ QUERY = :QUERY,
197
+ MUTATION = :MUTATION,
198
+ SUBSCRIPTION = :SUBSCRIPTION,
199
+ FIELD = :FIELD,
200
+ FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION,
201
+ FRAGMENT_SPREAD = :FRAGMENT_SPREAD,
202
+ INLINE_FRAGMENT = :INLINE_FRAGMENT,
203
+ VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
204
+ ]),
169
205
  SCHEMA = :SCHEMA,
170
206
  SCALAR = :SCALAR,
171
207
  OBJECT = :OBJECT,
@@ -177,7 +213,6 @@ module GraphQL
177
213
  ENUM_VALUE = :ENUM_VALUE,
178
214
  INPUT_OBJECT = :INPUT_OBJECT,
179
215
  INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
180
- VARIABLE_DEFINITION = :VARIABLE_DEFINITION,
181
216
  ]
182
217
 
183
218
  DEFAULT_DEPRECATION_REASON = 'No longer supported'
@@ -12,52 +12,19 @@ module GraphQL
12
12
  end
13
13
 
14
14
  # Remove pagination args before passing it to a user method
15
- def resolve(object:, arguments:, context:)
15
+ def resolve(object: nil, objects: nil, arguments:, context:)
16
16
  next_args = arguments.dup
17
17
  next_args.delete(:first)
18
18
  next_args.delete(:last)
19
19
  next_args.delete(:before)
20
20
  next_args.delete(:after)
21
- yield(object, next_args, arguments)
21
+ yield(object || objects, next_args, arguments)
22
22
  end
23
23
 
24
24
  def after_resolve(value:, object:, arguments:, context:, memo:)
25
25
  original_arguments = memo
26
- # rename some inputs to avoid conflicts inside the block
27
- maybe_lazy = value
28
- value = nil
29
- context.query.after_lazy(maybe_lazy) do |resolved_value|
30
- value = resolved_value
31
- if value.is_a? GraphQL::ExecutionError
32
- # This isn't even going to work because context doesn't have ast_node anymore
33
- context.add_error(value)
34
- nil
35
- elsif value.nil?
36
- nil
37
- elsif value.is_a?(GraphQL::Pagination::Connection)
38
- # update the connection with some things that may not have been provided
39
- value.context ||= context
40
- value.parent ||= object.object
41
- value.first_value ||= original_arguments[:first]
42
- value.after_value ||= original_arguments[:after]
43
- value.last_value ||= original_arguments[:last]
44
- value.before_value ||= original_arguments[:before]
45
- value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method
46
- value.field ||= field
47
- if field.has_max_page_size? && !value.has_max_page_size_override?
48
- value.max_page_size = field.max_page_size
49
- end
50
- if field.has_default_page_size? && !value.has_default_page_size_override?
51
- value.default_page_size = field.default_page_size
52
- end
53
- if (custom_t = context.schema.connections.edge_class_for_field(@field))
54
- value.edge_class = custom_t
55
- end
56
- value
57
- else
58
- context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
59
- context.schema.connections.wrap(field, object.object, value, original_arguments, context)
60
- end
26
+ context.query.after_lazy(value) do |resolved_value|
27
+ context.schema.connections.populate_connection(field, object.object, resolved_value, original_arguments, context)
61
28
  end
62
29
  end
63
30
  end
@@ -5,22 +5,27 @@ module GraphQL
5
5
  class Field
6
6
  class ScopeExtension < GraphQL::Schema::FieldExtension
7
7
  def after_resolve(object:, arguments:, context:, value:, memo:)
8
- if value.nil?
9
- value
10
- else
11
- ret_type = @field.type.unwrap
12
- if ret_type.respond_to?(:scope_items)
13
- scoped_items = ret_type.scope_items(value, context)
14
- if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
15
- if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
16
- (query_runtime_state = current_runtime_state[context.query])
17
- query_runtime_state.was_authorized_by_scope_items = true
8
+ if object.is_a?(GraphQL::Schema::Object)
9
+ if value.nil?
10
+ value
11
+ else
12
+ return_type = field.type.unwrap
13
+ if return_type.respond_to?(:scope_items)
14
+ scoped_items = return_type.scope_items(value, context)
15
+ if !scoped_items.equal?(value) && !return_type.reauthorize_scoped_objects
16
+ if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
17
+ (query_runtime_state = current_runtime_state[context.query])
18
+ query_runtime_state.was_authorized_by_scope_items = true
19
+ end
18
20
  end
21
+ scoped_items
22
+ else
23
+ value
19
24
  end
20
- scoped_items
21
- else
22
- value
23
25
  end
26
+ else
27
+ # TODO skip this entirely?
28
+ value
24
29
  end
25
30
  end
26
31
  end
@@ -8,6 +8,7 @@ module GraphQL
8
8
  include GraphQL::Schema::Member::HasArguments
9
9
  include GraphQL::Schema::Member::HasArguments::FieldConfigured
10
10
  include GraphQL::Schema::Member::HasAstNode
11
+ include GraphQL::Schema::Member::HasAuthorization
11
12
  include GraphQL::Schema::Member::HasPath
12
13
  include GraphQL::Schema::Member::HasValidators
13
14
  extend GraphQL::Schema::FindInheritedValue
@@ -109,52 +110,6 @@ module GraphQL
109
110
  end
110
111
  attr_writer :subscription_scope
111
112
 
112
- # Create a field instance from a list of arguments, keyword arguments, and a block.
113
- #
114
- # This method implements prioritization between the `resolver` or `mutation` defaults
115
- # and the local overrides via other keywords.
116
- #
117
- # It also normalizes positional arguments into keywords for {Schema::Field#initialize}.
118
- # @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration
119
- # @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration
120
- # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
121
- # @return [GraphQL::Schema:Field] an instance of `self`
122
- # @see {.initialize} for other options
123
- def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
124
- if (resolver_class = resolver || mutation || subscription)
125
- # Add a reference to that parent class
126
- kwargs[:resolver_class] = resolver_class
127
- end
128
-
129
- if name
130
- kwargs[:name] = name
131
- end
132
-
133
- if comment
134
- kwargs[:comment] = comment
135
- end
136
-
137
- if !type.nil?
138
- if desc
139
- if kwargs[:description]
140
- raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{kwargs[:description].inspect})"
141
- end
142
-
143
- kwargs[:description] = desc
144
- kwargs[:type] = type
145
- elsif (resolver || mutation) && type.is_a?(String)
146
- # The return type should be copied from the resolver, and the second positional argument is the description
147
- kwargs[:description] = type
148
- else
149
- kwargs[:type] = type
150
- end
151
- if type.is_a?(Class) && type < GraphQL::Schema::Mutation
152
- raise ArgumentError, "Use `field #{name.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
153
- end
154
- end
155
- new(**kwargs, &block)
156
- end
157
-
158
113
  # Can be set with `connection: true|false` or inferred from a type name ending in `*Connection`
159
114
  # @return [Boolean] if true, this field will be wrapped with Relay connection behavior
160
115
  def connection?
@@ -238,6 +193,11 @@ module GraphQL
238
193
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
239
194
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
240
195
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
196
+ # @param resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
197
+ # @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
198
+ # @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
199
+ # @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
200
+ # @param dataload [Class, Hash] Shorthand for making dataloader calls
241
201
  # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
242
202
  # @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
243
203
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
@@ -255,7 +215,12 @@ module GraphQL
255
215
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
256
216
  # @param validates [Array<Hash>] Configurations for validating this field
257
217
  # @param fallback_value [Object] A fallback value if the method is not defined
258
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
218
+ # @param dynamic_introspection [Boolean] (Private, used by GraphQL-Ruby)
219
+ # @param relay_node_field [Boolean] (Private, used by GraphQL-Ruby)
220
+ # @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
221
+ # @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field
222
+ # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
223
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
259
224
  if name.nil?
260
225
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
261
226
  end
@@ -303,6 +268,36 @@ module GraphQL
303
268
  @method_str = -method_name.to_s
304
269
  @method_sym = method_name.to_sym
305
270
  @resolver_method = (resolver_method || name_s).to_sym
271
+
272
+ if resolve_static
273
+ @execution_mode = :resolve_static
274
+ @execution_mode_key = resolve_static == true ? @method_sym : resolve_static
275
+ elsif resolve_batch
276
+ @execution_mode = :resolve_batch
277
+ @execution_mode_key = resolve_batch == true ? @method_sym : resolve_batch
278
+ elsif resolve_each
279
+ @execution_mode = :resolve_each
280
+ @execution_mode_key = resolve_each == true ? @method_sym : resolve_each
281
+ elsif hash_key
282
+ @execution_mode = :hash_key
283
+ @execution_mode_key = hash_key
284
+ elsif dig
285
+ @execution_mode = :dig
286
+ @execution_mode_key = dig
287
+ elsif resolver_class
288
+ @execution_mode = :resolver_class
289
+ @execution_mode_key = resolver_class
290
+ elsif resolve_legacy_instance_method
291
+ @execution_mode = :resolve_legacy_instance_method
292
+ @execution_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
293
+ elsif dataload
294
+ @execution_mode = :dataload
295
+ @execution_mode_key = dataload
296
+ else
297
+ @execution_mode = :direct_send
298
+ @execution_mode_key = @method_sym
299
+ end
300
+
306
301
  @complexity = complexity
307
302
  @dynamic_introspection = dynamic_introspection
308
303
  @return_type_expr = type
@@ -347,7 +342,7 @@ module GraphQL
347
342
 
348
343
  @extensions = EMPTY_ARRAY
349
344
  @call_after_define = false
350
- set_pagination_extensions(connection_extension: connection_extension)
345
+ set_pagination_extensions(connection_extension: NOT_CONFIGURED.equal?(connection_extension) ? self.class.connection_extension : connection_extension)
351
346
  # Do this last so we have as much context as possible when initializing them:
352
347
  if !extensions.empty?
353
348
  self.extensions(extensions)
@@ -373,6 +368,9 @@ module GraphQL
373
368
  end
374
369
  end
375
370
 
371
+ # @api private
372
+ attr_reader :execution_mode_key, :execution_mode
373
+
376
374
  # Calls the definition block, if one was given.
377
375
  # This is deferred so that references to the return type
378
376
  # can be lazily evaluated, reducing Rails boot time.
@@ -666,6 +664,12 @@ module GraphQL
666
664
  end
667
665
  end
668
666
 
667
+ def authorizes?(context)
668
+ method(:authorized?).owner != GraphQL::Schema::Field ||
669
+ ((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) ||
670
+ (@resolver_class&.authorizes?(context)) || false
671
+ end
672
+
669
673
  def authorized?(object, args, context)
670
674
  if @resolver_class
671
675
  # The resolver _instance_ will check itself during `resolve()`
@@ -920,6 +924,33 @@ ERR
920
924
  end
921
925
  end
922
926
 
927
+ public
928
+
929
+ def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
930
+ extension = @extensions[idx]
931
+ if extension
932
+ extension.resolve(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
933
+ if memo
934
+ memos = extended.memos ||= {}
935
+ memos[idx] = memo
936
+ end
937
+
938
+ if (extras = extension.added_extras)
939
+ ae = extended.added_extras ||= []
940
+ ae.concat(extras)
941
+ end
942
+
943
+ extended.object = extended_objs
944
+ extended.arguments = extended_args
945
+ run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block)
946
+ end
947
+ else
948
+ yield(objs, args)
949
+ end
950
+ end
951
+
952
+ private
953
+
923
954
  def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
924
955
  extension = @extensions[idx]
925
956
  if extension
@@ -123,15 +123,16 @@ module GraphQL
123
123
  #
124
124
  # Whatever this method returns will be used for execution.
125
125
  #
126
- # @param object [Object] The object the field is being resolved on
126
+ # @param object [Object] The object the field is being resolved on (not passed by new execution)
127
+ # @param objects [Array<Object>] The objects the field is being resolved on (passed by new execution)
127
128
  # @param arguments [Hash] Ruby keyword arguments for resolving this field
128
129
  # @param context [Query::Context] the context for this query
129
- # @yieldparam object [Object] The object to continue resolving the field on
130
+ # @yieldparam object_or_objects [Object, Array<Object>] The object or objects (new execution) to continue resolving the field on
130
131
  # @yieldparam arguments [Hash] The keyword arguments to continue resolving with
131
132
  # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
132
133
  # @return [Object] The return value for this field.
133
- def resolve(object:, arguments:, context:)
134
- yield(object, arguments, nil)
134
+ def resolve(object: nil, objects: nil, arguments:, context:)
135
+ yield(object.nil? ? objects : object, arguments, nil)
135
136
  end
136
137
 
137
138
  # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
@@ -139,14 +140,16 @@ module GraphQL
139
140
  #
140
141
  # Whatever this hook returns will be used as the return value.
141
142
  #
142
- # @param object [Object] The object the field is being resolved on
143
+ # @param object [Object] The object the field is being resolved on (not passed by new execution)
144
+ # @param objects [Array<Object>] The object the field is being resolved on (passed by new execution)
143
145
  # @param arguments [Hash] Ruby keyword arguments for resolving this field
144
146
  # @param context [Query::Context] the context for this query
145
- # @param value [Object] Whatever the field previously returned
147
+ # @param value [Object] Whatever the field previously returned (not passed by new execution)
148
+ # @param values [Array<Object>] Whatever the field previously returned (passed by new execution)
146
149
  # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
147
150
  # @return [Object] The return value for this field.
148
- def after_resolve(object:, arguments:, context:, value:, memo:)
149
- value
151
+ def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:)
152
+ value.nil? ? values : value
150
153
  end
151
154
  end
152
155
  end