graphql 1.11.11 → 1.12.0

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/relay_generator.rb +63 -0
  4. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  5. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  6. data/lib/generators/graphql/templates/node_type.erb +9 -0
  7. data/lib/generators/graphql/templates/object.erb +1 -1
  8. data/lib/generators/graphql/templates/query_type.erb +1 -3
  9. data/lib/generators/graphql/templates/schema.erb +8 -35
  10. data/lib/graphql/analysis/analyze_query.rb +7 -0
  11. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  12. data/lib/graphql/analysis/ast.rb +11 -2
  13. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  14. data/lib/graphql/backtrace/table.rb +22 -2
  15. data/lib/graphql/backtrace/tracer.rb +40 -9
  16. data/lib/graphql/backtrace.rb +28 -19
  17. data/lib/graphql/backwards_compatibility.rb +1 -0
  18. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  19. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  20. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  21. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  22. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  23. data/lib/graphql/dataloader/request.rb +24 -0
  24. data/lib/graphql/dataloader/request_all.rb +22 -0
  25. data/lib/graphql/dataloader/source.rb +93 -0
  26. data/lib/graphql/dataloader.rb +197 -0
  27. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  28. data/lib/graphql/define/instance_definable.rb +32 -2
  29. data/lib/graphql/define/type_definer.rb +5 -5
  30. data/lib/graphql/deprecated_dsl.rb +5 -0
  31. data/lib/graphql/enum_type.rb +2 -0
  32. data/lib/graphql/execution/errors.rb +4 -0
  33. data/lib/graphql/execution/execute.rb +7 -0
  34. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  35. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  36. data/lib/graphql/execution/interpreter/runtime.rb +210 -124
  37. data/lib/graphql/execution/interpreter.rb +10 -6
  38. data/lib/graphql/execution/multiplex.rb +20 -6
  39. data/lib/graphql/function.rb +4 -0
  40. data/lib/graphql/input_object_type.rb +2 -0
  41. data/lib/graphql/interface_type.rb +3 -1
  42. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  43. data/lib/graphql/language/nodes.rb +0 -5
  44. data/lib/graphql/language/visitor.rb +0 -1
  45. data/lib/graphql/object_type.rb +2 -0
  46. data/lib/graphql/pagination/connection.rb +5 -1
  47. data/lib/graphql/pagination/connections.rb +6 -16
  48. data/lib/graphql/query/context.rb +4 -0
  49. data/lib/graphql/query/serial_execution.rb +1 -0
  50. data/lib/graphql/query.rb +2 -0
  51. data/lib/graphql/relay/base_connection.rb +7 -0
  52. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  53. data/lib/graphql/relay/connection_type.rb +1 -1
  54. data/lib/graphql/relay/mutation.rb +1 -0
  55. data/lib/graphql/relay/node.rb +3 -0
  56. data/lib/graphql/relay/type_extensions.rb +2 -0
  57. data/lib/graphql/scalar_type.rb +2 -0
  58. data/lib/graphql/schema/argument.rb +30 -10
  59. data/lib/graphql/schema/build_from_definition.rb +145 -58
  60. data/lib/graphql/schema/directive/flagged.rb +57 -0
  61. data/lib/graphql/schema/directive.rb +76 -0
  62. data/lib/graphql/schema/enum.rb +3 -0
  63. data/lib/graphql/schema/enum_value.rb +13 -7
  64. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  65. data/lib/graphql/schema/field.rb +28 -10
  66. data/lib/graphql/schema/input_object.rb +36 -28
  67. data/lib/graphql/schema/interface.rb +1 -0
  68. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  69. data/lib/graphql/schema/member/build_type.rb +3 -3
  70. data/lib/graphql/schema/member/has_arguments.rb +24 -6
  71. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  72. data/lib/graphql/schema/member/has_directives.rb +98 -0
  73. data/lib/graphql/schema/member/has_validators.rb +31 -0
  74. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  75. data/lib/graphql/schema/member.rb +4 -0
  76. data/lib/graphql/schema/object.rb +11 -0
  77. data/lib/graphql/schema/printer.rb +5 -4
  78. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  79. data/lib/graphql/schema/resolver.rb +7 -0
  80. data/lib/graphql/schema/subscription.rb +19 -1
  81. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  82. data/lib/graphql/schema/validation.rb +2 -0
  83. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  84. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  85. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  86. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  87. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  88. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  89. data/lib/graphql/schema/validator.rb +163 -0
  90. data/lib/graphql/schema.rb +61 -23
  91. data/lib/graphql/static_validation/validator.rb +2 -0
  92. data/lib/graphql/subscriptions.rb +17 -20
  93. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  94. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  95. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  96. data/lib/graphql/tracing.rb +2 -2
  97. data/lib/graphql/types/relay/base_connection.rb +2 -92
  98. data/lib/graphql/types/relay/base_edge.rb +2 -35
  99. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  100. data/lib/graphql/types/relay/default_relay.rb +27 -0
  101. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  102. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  103. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  104. data/lib/graphql/types/relay/node.rb +2 -4
  105. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  106. data/lib/graphql/types/relay/node_field.rb +1 -19
  107. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  108. data/lib/graphql/types/relay/page_info.rb +2 -14
  109. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  110. data/lib/graphql/types/relay.rb +11 -3
  111. data/lib/graphql/union_type.rb +2 -0
  112. data/lib/graphql/upgrader/member.rb +1 -0
  113. data/lib/graphql/upgrader/schema.rb +1 -0
  114. data/lib/graphql/version.rb +1 -1
  115. data/lib/graphql.rb +38 -4
  116. metadata +34 -9
  117. data/lib/graphql/types/relay/base_field.rb +0 -22
  118. data/lib/graphql/types/relay/base_interface.rb +0 -29
  119. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -15,8 +15,11 @@ module GraphQL
15
15
  include GraphQL::Schema::Member::HasArguments
16
16
  include GraphQL::Schema::Member::HasAstNode
17
17
  include GraphQL::Schema::Member::HasPath
18
+ include GraphQL::Schema::Member::HasValidators
18
19
  extend GraphQL::Schema::FindInheritedValue
19
20
  include GraphQL::Schema::FindInheritedValue::EmptyObjects
21
+ include GraphQL::Schema::Member::HasDirectives
22
+ include GraphQL::Schema::Member::HasDeprecationReason
20
23
 
21
24
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
22
25
  attr_reader :name
@@ -24,9 +27,6 @@ module GraphQL
24
27
 
25
28
  attr_writer :description
26
29
 
27
- # @return [String, nil] If present, the field is marked as deprecated with this documentation
28
- attr_accessor :deprecation_reason
29
-
30
30
  # @return [Symbol] Method or hash key on the underlying object to look up
31
31
  attr_reader :method_sym
32
32
 
@@ -82,10 +82,10 @@ module GraphQL
82
82
  # @see {.initialize} for other options
83
83
  def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
84
84
  if kwargs[:field]
85
- if kwargs[:field] == GraphQL::Relay::Node.field
85
+ if kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodeField.graphql_definition
86
86
  warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
87
87
  return GraphQL::Types::Relay::NodeField
88
- elsif kwargs[:field] == GraphQL::Relay::Node.plural_field
88
+ elsif kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodesField.graphql_definition
89
89
  warn("Legacy-style `GraphQL::Relay::Node.plural_field` is being added to a class-based type. See `GraphQL::Types::Relay::NodesField` for a replacement.")
90
90
  return GraphQL::Types::Relay::NodesField
91
91
  end
@@ -199,11 +199,14 @@ module GraphQL
199
199
  # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value
200
200
  # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads
201
201
  # @param extensions [Array<Class, Hash<Class => Object>>] Named extensions to apply to this field (see also {#extension})
202
+ # @param directives [Hash{Class => Hash}] Directives to apply to this field
202
203
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
203
204
  # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts
204
205
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
205
206
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
206
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, 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: nil, arguments: EMPTY_HASH, &definition_block)
207
+ # @param validates [Array<Hash>] Configurations for validating this field
208
+ # @param legacy_edge_class [Class, nil] (DEPRECATED) If present, pass this along to the legacy field definition
209
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: :not_given, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, 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: nil, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, legacy_edge_class: nil, &definition_block)
207
210
  if name.nil?
208
211
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
209
212
  end
@@ -222,7 +225,6 @@ module GraphQL
222
225
  name_s = -name.to_s
223
226
  @underscored_name = -Member::BuildType.underscore(name_s)
224
227
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
225
- NameValidator.validate!(@name)
226
228
  @description = description
227
229
  if field.is_a?(GraphQL::Schema::Field)
228
230
  raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
@@ -231,7 +233,7 @@ module GraphQL
231
233
  end
232
234
  @function = function
233
235
  @resolve = resolve
234
- @deprecation_reason = deprecation_reason
236
+ self.deprecation_reason = deprecation_reason
235
237
 
236
238
  if method && hash_key
237
239
  raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
@@ -270,6 +272,7 @@ module GraphQL
270
272
  @relay_nodes_field = relay_nodes_field
271
273
  @ast_node = ast_node
272
274
  @method_conflict_warning = method_conflict_warning
275
+ @legacy_edge_class = legacy_edge_class
273
276
 
274
277
  arguments.each do |name, arg|
275
278
  if arg.is_a?(Hash)
@@ -298,6 +301,14 @@ module GraphQL
298
301
  self.extension(connection_extension)
299
302
  end
300
303
 
304
+ if directives.any?
305
+ directives.each do |(dir_class, options)|
306
+ self.directive(dir_class, **options)
307
+ end
308
+ end
309
+
310
+ self.validates(validates)
311
+
301
312
  if definition_block
302
313
  if definition_block.arity == 1
303
314
  yield self
@@ -439,8 +450,8 @@ module GraphQL
439
450
  field_defn.description = @description
440
451
  end
441
452
 
442
- if @deprecation_reason
443
- field_defn.deprecation_reason = @deprecation_reason
453
+ if self.deprecation_reason
454
+ field_defn.deprecation_reason = self.deprecation_reason
444
455
  end
445
456
 
446
457
  if @resolver_class
@@ -462,6 +473,10 @@ module GraphQL
462
473
  field_defn.relay_nodes_field = @relay_nodes_field
463
474
  end
464
475
 
476
+ if @legacy_edge_class
477
+ field_defn.edge_class = @legacy_edge_class
478
+ end
479
+
465
480
  field_defn.resolve = self.method(:resolve_field)
466
481
  field_defn.connection = connection?
467
482
  field_defn.connection_max_page_size = max_page_size
@@ -581,6 +596,9 @@ module GraphQL
581
596
  begin
582
597
  # Unwrap the GraphQL object to get the application object.
583
598
  application_object = object.object
599
+
600
+ Schema::Validator.validate!(validators, application_object, ctx, args)
601
+
584
602
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
585
603
  if is_authorized
586
604
  public_send_field(object, args, ctx)
@@ -7,6 +7,7 @@ module GraphQL
7
7
  extend GraphQL::Schema::Member::HasArguments
8
8
  extend GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader
9
9
  extend GraphQL::Schema::Member::ValidatesInput
10
+ extend GraphQL::Schema::Member::HasValidators
10
11
 
11
12
  include GraphQL::Dig
12
13
 
@@ -37,14 +38,15 @@ module GraphQL
37
38
  load_application_object(arg_defn, loads, value, context)
38
39
  end
39
40
  maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
40
- @ruby_style_hash[ruby_kwargs_key] = loaded_value
41
+ overwrite_argument(ruby_kwargs_key, loaded_value)
41
42
  end
42
43
  end
43
44
 
44
45
  # Weirdly, procs are applied during coercion, but not methods.
45
46
  # Probably because these methods require a `self`.
46
47
  if arg_defn.prepare.is_a?(Symbol) || context.nil? || !context.interpreter?
47
- @ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
48
+ prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
49
+ overwrite_argument(ruby_kwargs_key, prepared_value)
48
50
  end
49
51
  end
50
52
  end
@@ -74,6 +76,9 @@ module GraphQL
74
76
  def prepare
75
77
  if context
76
78
  context.schema.after_any_lazies(@maybe_lazies) do
79
+ object = context[:current_object]
80
+ # Pass this object's class with `as` so that messages are rendered correctly from inherited validators
81
+ Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class)
77
82
  self
78
83
  end
79
84
  else
@@ -125,8 +130,12 @@ module GraphQL
125
130
  def argument(*args, **kwargs, &block)
126
131
  argument_defn = super(*args, **kwargs, &block)
127
132
  # Add a method access
128
- define_accessor_method(argument_defn.keyword)
129
- argument_defn
133
+ method_name = argument_defn.keyword
134
+ class_eval <<-RUBY, __FILE__, __LINE__
135
+ def #{method_name}
136
+ self[#{method_name.inspect}]
137
+ end
138
+ RUBY
130
139
  end
131
140
 
132
141
  def to_graphql
@@ -164,17 +173,10 @@ module GraphQL
164
173
  return result
165
174
  end
166
175
 
167
- input = begin
168
- input.to_h
169
- rescue
170
- begin
171
- # Handle ActionController::Parameters:
172
- input.to_unsafe_h
173
- rescue
174
- # We're not sure it'll act like a hash, so reject it:
175
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
176
- return result
177
- end
176
+ if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
177
+ # We're not sure it'll act like a hash, so reject it:
178
+ result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
179
+ return result
178
180
  end
179
181
 
180
182
  # Inject missing required arguments
@@ -186,16 +188,19 @@ module GraphQL
186
188
  m
187
189
  end
188
190
 
189
- input.merge(missing_required_inputs).each do |argument_name, value|
190
- argument = warden.get_argument(self, argument_name)
191
- # Items in the input that are unexpected
192
- unless argument
193
- result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
194
- next
191
+
192
+ [input, missing_required_inputs].each do |args_to_validate|
193
+ args_to_validate.each do |argument_name, value|
194
+ argument = warden.get_argument(self, argument_name)
195
+ # Items in the input that are unexpected
196
+ unless argument
197
+ result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
198
+ next
199
+ end
200
+ # Items in the input that are expected, but have invalid values
201
+ argument_result = argument.type.validate_input(value, ctx)
202
+ result.merge_result!(argument_name, argument_result) unless argument_result.valid?
195
203
  end
196
- # Items in the input that are expected, but have invalid values
197
- argument_result = argument.type.validate_input(value, ctx)
198
- result.merge_result!(argument_name, argument_result) unless argument_result.valid?
199
204
  end
200
205
 
201
206
  result
@@ -235,13 +240,16 @@ module GraphQL
235
240
 
236
241
  result
237
242
  end
243
+ end
238
244
 
239
- private
245
+ private
240
246
 
241
- def define_accessor_method(method_name)
242
- define_method(method_name) { self[method_name] }
243
- alias_method(method_name, method_name)
247
+ def overwrite_argument(key, value)
248
+ # Argument keywords come in frozen from the interpreter, dup them before modifying them.
249
+ if @ruby_style_hash.frozen?
250
+ @ruby_style_hash = @ruby_style_hash.dup
244
251
  end
252
+ @ruby_style_hash[key] = value
245
253
  end
246
254
  end
247
255
  end
@@ -15,6 +15,7 @@ module GraphQL
15
15
  include GraphQL::Schema::Member::Scoped
16
16
  include GraphQL::Schema::Member::HasAstNode
17
17
  include GraphQL::Schema::Member::HasUnresolvedTypeError
18
+ include GraphQL::Schema::Member::HasDirectives
18
19
 
19
20
  # Methods defined in this block will be:
20
21
  # - Added as class methods to this interface
@@ -19,6 +19,7 @@ module GraphQL
19
19
  # @return [String]
20
20
  def graphql_name(new_name = nil)
21
21
  if new_name
22
+ GraphQL::NameValidator.validate!(new_name)
22
23
  @graphql_name = new_name
23
24
  else
24
25
  overridden_graphql_name || default_graphql_name
@@ -60,11 +60,11 @@ module GraphQL
60
60
  parse_type(type_expr.first, null: false)
61
61
  when 2
62
62
  inner_type, nullable_option = type_expr
63
- if nullable_option.keys != [:null] || nullable_option.values != [true]
63
+ if nullable_option.keys != [:null] || (nullable_option[:null] != true && nullable_option[:null] != false)
64
64
  raise ArgumentError, LIST_TYPE_ERROR
65
65
  end
66
66
  list_type = true
67
- parse_type(inner_type, null: true)
67
+ parse_type(inner_type, null: nullable_option[:null])
68
68
  else
69
69
  raise ArgumentError, LIST_TYPE_ERROR
70
70
  end
@@ -75,7 +75,7 @@ module GraphQL
75
75
  if type_expr.respond_to?(:graphql_definition)
76
76
  type_expr
77
77
  else
78
- # Eg `String` => GraphQL::STRING_TYPE
78
+ # Eg `String` => GraphQL::Types::String
79
79
  parse_type(type_expr.name, null: true)
80
80
  end
81
81
  when Proc
@@ -89,7 +89,7 @@ module GraphQL
89
89
  arg_defns = self.arguments
90
90
 
91
91
  if arg_defns.empty?
92
- GraphQL::Execution::Interpreter::Arguments.new(argument_values: nil)
92
+ GraphQL::Execution::Interpreter::Arguments::EMPTY
93
93
  else
94
94
  argument_values = {}
95
95
  arg_lazies = arg_defns.map do |arg_name, arg_defn|
@@ -111,24 +111,28 @@ module GraphQL
111
111
  if has_value
112
112
  loads = arg_defn.loads
113
113
  loaded_value = nil
114
+ coerced_value = context.schema.error_handler.with_error_handling(context) do
115
+ arg_defn.type.coerce_input(value, context)
116
+ end
117
+
118
+ # TODO this should probably be inside after_lazy
114
119
  if loads && !arg_defn.from_resolver?
115
120
  loaded_value = if arg_defn.type.list?
116
- loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
121
+ loaded_values = coerced_value.map { |val| load_application_object(arg_defn, loads, val, context) }
117
122
  context.schema.after_any_lazies(loaded_values) { |result| result }
118
123
  else
119
- load_application_object(arg_defn, loads, value, context)
124
+ load_application_object(arg_defn, loads, coerced_value, context)
120
125
  end
121
126
  end
122
127
 
123
128
  coerced_value = if loaded_value
124
129
  loaded_value
125
130
  else
126
- context.schema.error_handler.with_error_handling(context) do
127
- arg_defn.type.coerce_input(value, context)
128
- end
131
+ coerced_value
129
132
  end
130
133
 
131
134
  context.schema.after_lazy(coerced_value) do |coerced_value|
135
+ validate_directive_argument(arg_defn, coerced_value)
132
136
  prepared_value = context.schema.error_handler.with_error_handling(context) do
133
137
  arg_defn.prepare_value(parent_object, coerced_value, context: context)
134
138
  end
@@ -140,6 +144,9 @@ module GraphQL
140
144
  default_used: default_used,
141
145
  )
142
146
  end
147
+ else
148
+ # has_value is false
149
+ validate_directive_argument(arg_defn, nil)
143
150
  end
144
151
  end
145
152
 
@@ -151,6 +158,17 @@ module GraphQL
151
158
  end
152
159
  end
153
160
 
161
+ # Usually, this is validated statically by RequiredArgumentsArePresent,
162
+ # but not for directives.
163
+ # TODO apply static validations on schema definitions?
164
+ def validate_directive_argument(arg_defn, value)
165
+ if arg_defn.owner.is_a?(Class) && arg_defn.owner < GraphQL::Schema::Directive
166
+ if value.nil? && arg_defn.type.non_null?
167
+ raise ArgumentError, "#{arg_defn.path} is required, but no value was given"
168
+ end
169
+ end
170
+ end
171
+
154
172
  def arguments_statically_coercible?
155
173
  return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
156
174
 
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDeprecationReason
7
+ # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
8
+ def deprecation_reason
9
+ dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
10
+ dir && dir.arguments[:reason]
11
+ end
12
+
13
+ # Set the deprecation reason for this member, or remove it by assigning `nil`
14
+ # @param text [String, nil]
15
+ def deprecation_reason=(text)
16
+ if text.nil?
17
+ remove_directive(GraphQL::Schema::Directive::Deprecated)
18
+ else
19
+ directive(GraphQL::Schema::Directive::Deprecated, reason: text)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDirectives
7
+ # Create an instance of `dir_class` for `self`, using `options`.
8
+ #
9
+ # It removes a previously-attached instance of `dir_class`, if there is one.
10
+ #
11
+ # @return [void]
12
+ def directive(dir_class, **options)
13
+ @own_directives ||= []
14
+ remove_directive(dir_class)
15
+ @own_directives << dir_class.new(self, **options)
16
+ nil
17
+ end
18
+
19
+ # Remove an attached instance of `dir_class`, if there is one
20
+ # @param dir_class [Class<GraphQL::Schema::Directive>]
21
+ # @return [viod]
22
+ def remove_directive(dir_class)
23
+ @own_directives && @own_directives.reject! { |d| d.is_a?(dir_class) }
24
+ nil
25
+ end
26
+
27
+ NO_DIRECTIVES = [].freeze
28
+
29
+ def directives
30
+ case self
31
+ when Class
32
+ inherited_directives = if superclass.respond_to?(:directives)
33
+ superclass.directives
34
+ else
35
+ NO_DIRECTIVES
36
+ end
37
+ if inherited_directives.any? && @own_directives
38
+ dirs = []
39
+ merge_directives(dirs, inherited_directives)
40
+ merge_directives(dirs, @own_directives)
41
+ dirs
42
+ elsif @own_directives
43
+ @own_directives
44
+ elsif inherited_directives.any?
45
+ inherited_directives
46
+ else
47
+ NO_DIRECTIVES
48
+ end
49
+ when Module
50
+ dirs = nil
51
+ self.ancestors.reverse_each do |ancestor|
52
+ if ancestor.respond_to?(:own_directives) &&
53
+ (anc_dirs = ancestor.own_directives).any?
54
+ dirs ||= []
55
+ merge_directives(dirs, anc_dirs)
56
+ end
57
+ end
58
+ if own_directives
59
+ dirs ||= []
60
+ merge_directives(dirs, own_directives)
61
+ end
62
+ dirs || NO_DIRECTIVES
63
+ when HasDirectives
64
+ @own_directives || NO_DIRECTIVES
65
+ else
66
+ raise "Invariant: how could #{self} not be a Class, Module, or instance of HasDirectives?"
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ def own_directives
73
+ @own_directives
74
+ end
75
+
76
+ private
77
+
78
+ # Modify `target` by adding items from `dirs` such that:
79
+ # - Any name conflict is overriden by the incoming member of `dirs`
80
+ # - Any other member of `dirs` is appended
81
+ # @param target [Array<GraphQL::Schema::Directive>]
82
+ # @param dirs [Array<GraphQL::Schema::Directive>]
83
+ # @return [void]
84
+ def merge_directives(target, dirs)
85
+ dirs.each do |dir|
86
+ if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
87
+ target.slice!(idx)
88
+ target.insert(idx, dir)
89
+ else
90
+ target << dir
91
+ end
92
+ end
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ module HasValidators
6
+ include Schema::FindInheritedValue::EmptyObjects
7
+
8
+ # Build {GraphQL::Schema::Validator}s based on the given configuration
9
+ # and use them for this schema member
10
+ # @param validation_config [Hash{Symbol => Hash}]
11
+ # @return [void]
12
+ def validates(validation_config)
13
+ new_validators = GraphQL::Schema::Validator.from_config(self, validation_config)
14
+ @own_validators ||= []
15
+ @own_validators.concat(new_validators)
16
+ nil
17
+ end
18
+
19
+ # @return [Array<GraphQL::Schema::Validator>]
20
+ def validators
21
+ own_validators = @own_validators || EMPTY_ARRAY
22
+ if self.is_a?(Class) && superclass.respond_to?(:validators) && (inherited_validators = superclass.validators).any?
23
+ inherited_validators + own_validators
24
+ else
25
+ own_validators
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  # @return [GraphQL::TypeKinds::TypeKind]
32
32
  def kind
33
- raise GraphQL::RequiredImplementationMissingError
33
+ raise GraphQL::RequiredImplementationMissingError, "No `.kind` defined for #{self}"
34
34
  end
35
35
  end
36
36
  end
@@ -4,8 +4,11 @@ require 'graphql/schema/member/base_dsl_methods'
4
4
  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
+ require 'graphql/schema/member/has_directives'
8
+ require 'graphql/schema/member/has_deprecation_reason'
7
9
  require 'graphql/schema/member/has_path'
8
10
  require 'graphql/schema/member/has_unresolved_type_error'
11
+ require 'graphql/schema/member/has_validators'
9
12
  require 'graphql/schema/member/relay_shortcuts'
10
13
  require 'graphql/schema/member/scoped'
11
14
  require 'graphql/schema/member/type_system_helpers'
@@ -30,6 +33,7 @@ module GraphQL
30
33
  extend RelayShortcuts
31
34
  extend HasPath
32
35
  extend HasAstNode
36
+ extend HasDirectives
33
37
  end
34
38
  end
35
39
  end
@@ -14,6 +14,17 @@ module GraphQL
14
14
  # @return [GraphQL::Query::Context] the context instance for this query
15
15
  attr_reader :context
16
16
 
17
+ # @return [GraphQL::Dataloader]
18
+ def dataloader
19
+ context.dataloader
20
+ end
21
+
22
+ # Call this in a field method to return a value that should be returned to the client
23
+ # without any further handling by GraphQL.
24
+ def raw_value(obj)
25
+ GraphQL::Execution::Interpreter::RawValue.new(obj)
26
+ end
27
+
17
28
  class << self
18
29
  # This is protected so that we can be sure callers use the public method, {.authorized_new}
19
30
  # @see authorized_new to make instances
@@ -59,14 +59,15 @@ module GraphQL
59
59
 
60
60
  # Return the GraphQL schema string for the introspection type system
61
61
  def self.print_introspection_schema
62
- query_root = ObjectType.define(name: "Root") do
63
- field :throwaway_field, types.String
62
+ query_root = Class.new(GraphQL::Schema::Object) do
63
+ graphql_name "Root"
64
+ field :throwaway_field, String, null: true
64
65
  end
65
- schema = GraphQL::Schema.define(query: query_root)
66
+ schema = Class.new(GraphQL::Schema) { query(query_root) }
66
67
 
67
68
  introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
68
69
  schema,
69
- except: ->(member, _) { member.name == "Root" },
70
+ except: ->(member, _) { member.graphql_name == "Root" },
70
71
  include_introspection_types: true,
71
72
  include_built_in_directives: true,
72
73
  ).document
@@ -44,6 +44,8 @@ module GraphQL
44
44
  end
45
45
  end
46
46
 
47
+ NO_INTERFACES = [].freeze
48
+
47
49
  private
48
50
 
49
51
  # Build a subclass of {.object_class} based on `self`.
@@ -24,6 +24,7 @@ module GraphQL
24
24
  # Really we only need description from here, but:
25
25
  extend Schema::Member::BaseDSLMethods
26
26
  extend GraphQL::Schema::Member::HasArguments
27
+ extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
28
29
  extend Schema::Member::HasPath
29
30
 
@@ -49,6 +50,11 @@ module GraphQL
49
50
  # @return [GraphQL::Query::Context]
50
51
  attr_reader :context
51
52
 
53
+ # @return [GraphQL::Dataloader]
54
+ def dataloader
55
+ context.dataloader
56
+ end
57
+
52
58
  # @return [GraphQL::Schema::Field]
53
59
  attr_reader :field
54
60
 
@@ -80,6 +86,7 @@ module GraphQL
80
86
  load_arguments_val = load_arguments(args)
81
87
  context.schema.after_lazy(load_arguments_val) do |loaded_args|
82
88
  @prepared_arguments = loaded_args
89
+ Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
83
90
  # Then call `authorized?`, which may raise or may return a lazy object
84
91
  authorized_val = if loaded_args.any?
85
92
  authorized?(**loaded_args)
@@ -25,6 +25,22 @@ module GraphQL
25
25
  @mode = context.query.subscription_update? ? :update : :subscribe
26
26
  end
27
27
 
28
+ def resolve_with_support(**args)
29
+ result = nil
30
+ unsubscribed = true
31
+ catch :graphql_subscription_unsubscribed do
32
+ result = super
33
+ unsubscribed = false
34
+ end
35
+
36
+
37
+ if unsubscribed
38
+ context.skip
39
+ else
40
+ result
41
+ end
42
+ end
43
+
28
44
  # Implement the {Resolve} API
29
45
  def resolve(**args)
30
46
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
@@ -55,7 +71,8 @@ module GraphQL
55
71
  def resolve_update(**args)
56
72
  ret_val = args.any? ? update(**args) : update
57
73
  if ret_val == :no_update
58
- throw :graphql_no_subscription_update
74
+ context.namespace(:subscriptions)[:no_update] = true
75
+ context.skip
59
76
  else
60
77
  ret_val
61
78
  end
@@ -80,6 +97,7 @@ module GraphQL
80
97
 
81
98
  # Call this to halt execution and remove this subscription from the system
82
99
  def unsubscribe
100
+ context.namespace(:subscriptions)[:unsubscribed] = true
83
101
  throw :graphql_subscription_unsubscribed
84
102
  end
85
103
 
@@ -23,6 +23,8 @@ module GraphQL
23
23
  # Bugsnag.notify(timeout_error, {query_string: query_ctx.query.query_string})
24
24
  # end
25
25
  #
26
+ # @api deprecated
27
+ # @see Schema::Timeout
26
28
  class TimeoutMiddleware
27
29
  # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
28
30
  def initialize(max_seconds:, context_key: nil, &block)