graphql 1.12.10 → 1.13.4

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/relay.rb +19 -11
  7. data/lib/generators/graphql/templates/schema.erb +14 -2
  8. data/lib/generators/graphql/type_generator.rb +0 -1
  9. data/lib/graphql/analysis/ast/field_usage.rb +28 -1
  10. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  11. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  12. data/lib/graphql/backtrace/table.rb +15 -3
  13. data/lib/graphql/backtrace/tracer.rb +7 -4
  14. data/lib/graphql/base_type.rb +4 -2
  15. data/lib/graphql/boolean_type.rb +1 -1
  16. data/lib/graphql/dataloader/null_dataloader.rb +1 -0
  17. data/lib/graphql/dataloader/source.rb +50 -2
  18. data/lib/graphql/dataloader.rb +110 -41
  19. data/lib/graphql/define/instance_definable.rb +1 -1
  20. data/lib/graphql/deprecated_dsl.rb +11 -3
  21. data/lib/graphql/deprecation.rb +1 -5
  22. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  23. data/lib/graphql/directive/include_directive.rb +1 -1
  24. data/lib/graphql/directive/skip_directive.rb +1 -1
  25. data/lib/graphql/directive.rb +0 -4
  26. data/lib/graphql/enum_type.rb +5 -1
  27. data/lib/graphql/execution/errors.rb +1 -0
  28. data/lib/graphql/execution/execute.rb +1 -1
  29. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  30. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -4
  31. data/lib/graphql/execution/interpreter/resolve.rb +6 -2
  32. data/lib/graphql/execution/interpreter/runtime.rb +513 -213
  33. data/lib/graphql/execution/interpreter.rb +4 -8
  34. data/lib/graphql/execution/lazy.rb +5 -1
  35. data/lib/graphql/execution/lookahead.rb +2 -2
  36. data/lib/graphql/execution/multiplex.rb +4 -1
  37. data/lib/graphql/float_type.rb +1 -1
  38. data/lib/graphql/id_type.rb +1 -1
  39. data/lib/graphql/int_type.rb +1 -1
  40. data/lib/graphql/integer_encoding_error.rb +18 -2
  41. data/lib/graphql/introspection/directive_type.rb +1 -1
  42. data/lib/graphql/introspection/entry_points.rb +2 -2
  43. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  44. data/lib/graphql/introspection/field_type.rb +2 -2
  45. data/lib/graphql/introspection/input_value_type.rb +10 -4
  46. data/lib/graphql/introspection/schema_type.rb +3 -3
  47. data/lib/graphql/introspection/type_type.rb +10 -10
  48. data/lib/graphql/language/block_string.rb +2 -6
  49. data/lib/graphql/language/document_from_schema_definition.rb +10 -4
  50. data/lib/graphql/language/lexer.rb +0 -3
  51. data/lib/graphql/language/lexer.rl +0 -4
  52. data/lib/graphql/language/nodes.rb +13 -3
  53. data/lib/graphql/language/parser.rb +442 -434
  54. data/lib/graphql/language/parser.y +5 -4
  55. data/lib/graphql/language/printer.rb +6 -1
  56. data/lib/graphql/language/sanitized_printer.rb +5 -5
  57. data/lib/graphql/language/token.rb +0 -4
  58. data/lib/graphql/name_validator.rb +0 -4
  59. data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
  60. data/lib/graphql/pagination/connections.rb +40 -16
  61. data/lib/graphql/pagination/relation_connection.rb +57 -27
  62. data/lib/graphql/query/arguments.rb +1 -1
  63. data/lib/graphql/query/arguments_cache.rb +1 -1
  64. data/lib/graphql/query/context.rb +15 -2
  65. data/lib/graphql/query/literal_input.rb +1 -1
  66. data/lib/graphql/query/null_context.rb +12 -7
  67. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  68. data/lib/graphql/query/validation_pipeline.rb +1 -1
  69. data/lib/graphql/query/variables.rb +5 -1
  70. data/lib/graphql/query.rb +5 -1
  71. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  72. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  73. data/lib/graphql/relay/page_info.rb +1 -1
  74. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  75. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  76. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  77. data/lib/graphql/rubocop.rb +4 -0
  78. data/lib/graphql/schema/addition.rb +247 -0
  79. data/lib/graphql/schema/argument.rb +103 -45
  80. data/lib/graphql/schema/build_from_definition.rb +13 -7
  81. data/lib/graphql/schema/directive/feature.rb +1 -1
  82. data/lib/graphql/schema/directive/flagged.rb +2 -2
  83. data/lib/graphql/schema/directive/include.rb +1 -1
  84. data/lib/graphql/schema/directive/skip.rb +1 -1
  85. data/lib/graphql/schema/directive/transform.rb +14 -2
  86. data/lib/graphql/schema/directive.rb +7 -3
  87. data/lib/graphql/schema/enum.rb +70 -11
  88. data/lib/graphql/schema/enum_value.rb +6 -0
  89. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  90. data/lib/graphql/schema/field.rb +243 -81
  91. data/lib/graphql/schema/field_extension.rb +89 -2
  92. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  93. data/lib/graphql/schema/finder.rb +5 -5
  94. data/lib/graphql/schema/input_object.rb +39 -29
  95. data/lib/graphql/schema/interface.rb +11 -20
  96. data/lib/graphql/schema/introspection_system.rb +1 -1
  97. data/lib/graphql/schema/list.rb +3 -1
  98. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  99. data/lib/graphql/schema/member/build_type.rb +1 -4
  100. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  101. data/lib/graphql/schema/member/has_arguments.rb +145 -57
  102. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  103. data/lib/graphql/schema/member/has_fields.rb +76 -18
  104. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  105. data/lib/graphql/schema/member.rb +1 -0
  106. data/lib/graphql/schema/non_null.rb +7 -1
  107. data/lib/graphql/schema/object.rb +10 -75
  108. data/lib/graphql/schema/printer.rb +12 -17
  109. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  110. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  111. data/lib/graphql/schema/resolver.rb +75 -65
  112. data/lib/graphql/schema/scalar.rb +2 -0
  113. data/lib/graphql/schema/subscription.rb +36 -8
  114. data/lib/graphql/schema/traversal.rb +1 -1
  115. data/lib/graphql/schema/type_expression.rb +1 -1
  116. data/lib/graphql/schema/type_membership.rb +18 -4
  117. data/lib/graphql/schema/union.rb +8 -1
  118. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  119. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  120. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  121. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  122. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  123. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  124. data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
  125. data/lib/graphql/schema/validator/required_validator.rb +29 -15
  126. data/lib/graphql/schema/validator.rb +33 -25
  127. data/lib/graphql/schema/warden.rb +116 -52
  128. data/lib/graphql/schema.rb +162 -227
  129. data/lib/graphql/static_validation/all_rules.rb +1 -0
  130. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  131. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  132. data/lib/graphql/static_validation/error.rb +3 -1
  133. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  134. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  135. data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
  136. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  137. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  138. data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
  139. data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
  140. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  141. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  142. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
  143. data/lib/graphql/static_validation/validation_context.rb +8 -2
  144. data/lib/graphql/static_validation/validator.rb +15 -12
  145. data/lib/graphql/string_encoding_error.rb +13 -3
  146. data/lib/graphql/string_type.rb +1 -1
  147. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +36 -6
  148. data/lib/graphql/subscriptions/event.rb +68 -31
  149. data/lib/graphql/subscriptions/serialize.rb +23 -3
  150. data/lib/graphql/subscriptions.rb +17 -19
  151. data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
  152. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  153. data/lib/graphql/tracing/notifications_tracing.rb +59 -0
  154. data/lib/graphql/types/big_int.rb +5 -1
  155. data/lib/graphql/types/int.rb +1 -1
  156. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  157. data/lib/graphql/types/relay/default_relay.rb +5 -1
  158. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  159. data/lib/graphql/types/relay/has_node_field.rb +2 -2
  160. data/lib/graphql/types/relay/has_nodes_field.rb +2 -2
  161. data/lib/graphql/types/relay/node_field.rb +15 -4
  162. data/lib/graphql/types/relay/nodes_field.rb +14 -4
  163. data/lib/graphql/types/string.rb +1 -1
  164. data/lib/graphql/unauthorized_error.rb +1 -1
  165. data/lib/graphql/version.rb +1 -1
  166. data/lib/graphql.rb +10 -28
  167. data/readme.md +1 -4
  168. metadata +17 -21
  169. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
@@ -28,7 +28,7 @@ module GraphQL
28
28
  include Schema::Member::HasPath
29
29
  extend Schema::Member::HasPath
30
30
 
31
- # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value`
31
+ # @param object [Object] The application object that this field is being resolved on
32
32
  # @param context [GraphQL::Query::Context]
33
33
  # @param field [GraphQL::Schema::Field]
34
34
  def initialize(object:, context:, field:)
@@ -37,10 +37,9 @@ module GraphQL
37
37
  @field = field
38
38
  # Since this hash is constantly rebuilt, cache it for this call
39
39
  @arguments_by_keyword = {}
40
- self.class.arguments.each do |name, arg|
40
+ self.class.arguments(context).each do |name, arg|
41
41
  @arguments_by_keyword[arg.keyword] = arg
42
42
  end
43
- @arguments_loads_as_type = self.class.arguments_loads_as_type
44
43
  @prepared_arguments = nil
45
44
  end
46
45
 
@@ -110,7 +109,7 @@ module GraphQL
110
109
  public_send(self.class.resolve_method)
111
110
  end
112
111
  else
113
- nil
112
+ raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
114
113
  end
115
114
  end
116
115
  end
@@ -146,7 +145,25 @@ module GraphQL
146
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
147
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
148
147
  def authorized?(**inputs)
149
- self.class.arguments.each_value do |argument|
148
+ arg_owner = @field # || self.class
149
+ args = arg_owner.arguments(context)
150
+ authorize_arguments(args, inputs)
151
+ end
152
+
153
+ # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
154
+ #
155
+ # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
156
+ #
157
+ # Any value returned here will be used _instead of_ of the loaded object.
158
+ # @param err [GraphQL::UnauthorizedError]
159
+ def unauthorized_object(err)
160
+ raise err
161
+ end
162
+
163
+ private
164
+
165
+ def authorize_arguments(args, inputs)
166
+ args.each_value do |argument|
150
167
  arg_keyword = argument.keyword
151
168
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
152
169
  arg_auth, err = argument.authorized?(self, arg_value, context)
@@ -161,8 +178,6 @@ module GraphQL
161
178
  end
162
179
  end
163
180
 
164
- private
165
-
166
181
  def load_arguments(args)
167
182
  prepared_args = {}
168
183
  prepare_lazies = []
@@ -170,18 +185,14 @@ module GraphQL
170
185
  args.each do |key, value|
171
186
  arg_defn = @arguments_by_keyword[key]
172
187
  if arg_defn
173
- if value.nil?
174
- prepared_args[key] = value
175
- else
176
- prepped_value = prepared_args[key] = load_argument(key, value)
177
- if context.schema.lazy?(prepped_value)
178
- prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
179
- prepared_args[key] = finished_prepped_value
180
- end
188
+ prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
189
+ if context.schema.lazy?(prepped_value)
190
+ prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
191
+ prepared_args[key] = finished_prepped_value
181
192
  end
182
193
  end
183
194
  else
184
- # These are `extras: [...]`
195
+ # these are `extras:`
185
196
  prepared_args[key] = value
186
197
  end
187
198
  end
@@ -194,8 +205,8 @@ module GraphQL
194
205
  end
195
206
  end
196
207
 
197
- def load_argument(name, value)
198
- public_send("load_#{name}", value)
208
+ def get_argument(name, context = GraphQL::Query::NullContext)
209
+ self.class.get_argument(name, context)
199
210
  end
200
211
 
201
212
  class << self
@@ -218,8 +229,10 @@ module GraphQL
218
229
  own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
219
230
  end
220
231
 
221
- # Specifies whether or not the field is nullable. Defaults to `true`
222
- # TODO unify with {#type}
232
+ # If `true` (default), then the return type for this resolver will be nullable.
233
+ # If `false`, then the return type is non-null.
234
+ #
235
+ # @see #type which sets the return type of this field and accepts a `null:` option
223
236
  # @param allow_null [Boolean] Whether or not the response can be null
224
237
  def null(allow_null = nil)
225
238
  if !allow_null.nil?
@@ -298,19 +311,38 @@ module GraphQL
298
311
  end
299
312
 
300
313
  def field_options
314
+
315
+ all_args = {}
316
+ all_argument_definitions.each do |arg|
317
+ if (prev_entry = all_args[arg.graphql_name])
318
+ if prev_entry.is_a?(Array)
319
+ prev_entry << arg
320
+ else
321
+ all_args[arg.graphql_name] = [prev_entry, arg]
322
+ end
323
+ else
324
+ all_args[arg.graphql_name] = arg
325
+ end
326
+ end
327
+
301
328
  field_opts = {
302
329
  type: type_expr,
303
330
  description: description,
304
331
  extras: extras,
305
332
  resolver_method: :resolve_with_support,
306
333
  resolver_class: self,
307
- arguments: arguments,
334
+ arguments: all_args,
308
335
  null: null,
309
336
  complexity: complexity,
310
- extensions: extensions,
311
337
  broadcastable: broadcastable?,
312
338
  }
313
339
 
340
+ # If there aren't any, then the returned array is `[].freeze`,
341
+ # but passing that along breaks some user code.
342
+ if (exts = extensions).any?
343
+ field_opts[:extensions] = exts
344
+ end
345
+
314
346
  if has_max_page_size?
315
347
  field_opts[:max_page_size] = max_page_size
316
348
  end
@@ -327,65 +359,43 @@ module GraphQL
327
359
  # also add some preparation hook methods which will be used for this argument
328
360
  # @see {GraphQL::Schema::Argument#initialize} for the signature
329
361
  def argument(*args, **kwargs, &block)
330
- loads = kwargs[:loads]
331
362
  # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
332
363
  # so that we can support `#load_{x}` methods below.
333
- arg_defn = super(*args, from_resolver: true, **kwargs)
334
- own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
335
-
336
- if !method_defined?(:"load_#{arg_defn.keyword}")
337
- if loads && arg_defn.type.list?
338
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
339
- def load_#{arg_defn.keyword}(values)
340
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
341
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
342
- context.schema.after_lazy(values) do |values2|
343
- GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
344
- end
345
- end
346
- RUBY
347
- elsif loads
348
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
349
- def load_#{arg_defn.keyword}(value)
350
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
351
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
352
- load_application_object(argument, lookup_as_type, value, context)
353
- end
354
- RUBY
355
- else
356
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
357
- def load_#{arg_defn.keyword}(value)
358
- value
359
- end
360
- RUBY
361
- end
362
- end
363
-
364
- arg_defn
365
- end
366
-
367
- # @api private
368
- def arguments_loads_as_type
369
- inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
370
- inherited_lookups.merge(own_arguments_loads_as_type)
364
+ super(*args, from_resolver: true, **kwargs)
371
365
  end
372
366
 
373
367
  # Registers new extension
374
368
  # @param extension [Class] Extension class
375
369
  # @param options [Hash] Optional extension options
376
370
  def extension(extension, **options)
377
- extensions << {extension => options}
371
+ @own_extensions ||= []
372
+ @own_extensions << {extension => options}
378
373
  end
379
374
 
380
375
  # @api private
381
376
  def extensions
382
- @extensions ||= []
377
+ own_exts = @own_extensions
378
+ # Jump through some hoops to avoid creating arrays when we don't actually need them
379
+ if superclass.respond_to?(:extensions)
380
+ s_exts = superclass.extensions
381
+ if own_exts
382
+ if s_exts.any?
383
+ own_exts + s_exts
384
+ else
385
+ own_exts
386
+ end
387
+ else
388
+ s_exts
389
+ end
390
+ else
391
+ own_exts || EMPTY_ARRAY
392
+ end
383
393
  end
384
394
 
385
395
  private
386
396
 
387
- def own_arguments_loads_as_type
388
- @own_arguments_loads_as_type ||= {}
397
+ def own_extensions
398
+ @own_extensions
389
399
  end
390
400
  end
391
401
  end
@@ -14,6 +14,8 @@ module GraphQL
14
14
  val
15
15
  end
16
16
 
17
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
18
+
17
19
  def to_graphql
18
20
  type_defn = GraphQL::ScalarType.new
19
21
  type_defn.name = graphql_name
@@ -14,7 +14,7 @@ module GraphQL
14
14
  class Subscription < GraphQL::Schema::Resolver
15
15
  extend GraphQL::Schema::Resolver::HasPayloadType
16
16
  extend GraphQL::Schema::Member::HasFields
17
-
17
+ NO_UPDATE = :no_update
18
18
  # The generated payload type is required; If there's no payload,
19
19
  # propagate null.
20
20
  null false
@@ -58,11 +58,9 @@ module GraphQL
58
58
  end
59
59
  end
60
60
 
61
- # Default implementation returns the root object.
61
+ # The default implementation returns nothing on subscribe.
62
62
  # Override it to return an object or
63
- # `:no_response` to return nothing.
64
- #
65
- # The default is `:no_response`.
63
+ # `:no_response` to (explicitly) return nothing.
66
64
  def subscribe(args = {})
67
65
  :no_response
68
66
  end
@@ -70,7 +68,7 @@ module GraphQL
70
68
  # Wrap the user-provided `#update` hook
71
69
  def resolve_update(**args)
72
70
  ret_val = args.any? ? update(**args) : update
73
- if ret_val == :no_update
71
+ if ret_val == NO_UPDATE
74
72
  context.namespace(:subscriptions)[:no_update] = true
75
73
  context.skip
76
74
  else
@@ -79,7 +77,7 @@ module GraphQL
79
77
  end
80
78
 
81
79
  # The default implementation returns the root object.
82
- # Override it to return `:no_update` if you want to
80
+ # Override it to return {NO_UPDATE} if you want to
83
81
  # skip updates sometimes. Or override it to return a different object.
84
82
  def update(args = {})
85
83
  object
@@ -105,10 +103,12 @@ module GraphQL
105
103
  # Call this method to provide a new subscription_scope; OR
106
104
  # call it without an argument to get the subscription_scope
107
105
  # @param new_scope [Symbol]
106
+ # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
108
107
  # @return [Symbol]
109
- def self.subscription_scope(new_scope = READING_SCOPE)
108
+ def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
110
109
  if new_scope != READING_SCOPE
111
110
  @subscription_scope = new_scope
111
+ @subscription_scope_optional = optional
112
112
  elsif defined?(@subscription_scope)
113
113
  @subscription_scope
114
114
  else
@@ -116,6 +116,34 @@ module GraphQL
116
116
  end
117
117
  end
118
118
 
119
+ def self.subscription_scope_optional?
120
+ if defined?(@subscription_scope_optional)
121
+ @subscription_scope_optional
122
+ else
123
+ find_inherited_value(:subscription_scope_optional, false)
124
+ end
125
+ end
126
+
127
+ # This is called during initial subscription to get a "name" for this subscription.
128
+ # Later, when `.trigger` is called, this will be called again to build another "name".
129
+ # Any subscribers with matching topic will begin the update flow.
130
+ #
131
+ # The default implementation creates a string using the field name, subscription scope, and argument keys and values.
132
+ # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
133
+ #
134
+ # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
135
+ # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
136
+ # update should be filtered out.
137
+ #
138
+ # @see {#update} for how to skip updates when an event comes with a matching topic.
139
+ # @param arguments [Hash<String => Object>] The arguments for this topic, in GraphQL-style (camelized strings)
140
+ # @param field [GraphQL::Schema::Field]
141
+ # @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions).
142
+ # @return [String] An identifier corresponding to a stream of updates
143
+ def self.topic_for(arguments:, field:, scope:)
144
+ Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
145
+ end
146
+
119
147
  # Overriding Resolver#field_options to include subscription_scope
120
148
  def self.field_options
121
149
  super.merge(
@@ -173,7 +173,7 @@ Some late-bound types couldn't be resolved:
173
173
  end
174
174
  when Class
175
175
  if member.respond_to?(:graphql_definition)
176
- graphql_member = member.graphql_definition
176
+ graphql_member = member.graphql_definition(silence_deprecation_warning: true)
177
177
  visit(schema, graphql_member, context_description)
178
178
  else
179
179
  raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})")
@@ -11,7 +11,7 @@ module GraphQL
11
11
  def self.build_type(type_owner, ast_node)
12
12
  case ast_node
13
13
  when GraphQL::Language::Nodes::TypeName
14
- type_owner.get_type(ast_node.name)
14
+ type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
15
15
  when GraphQL::Language::Nodes::NonNullType
16
16
  ast_inner_type = ast_node.of_type
17
17
  inner_type = build_type(type_owner, ast_inner_type)
@@ -4,8 +4,6 @@ module GraphQL
4
4
  class Schema
5
5
  # This class joins an object type to an abstract type (interface or union) of which
6
6
  # it is a member.
7
- #
8
- # TODO: Not yet implemented for interfaces.
9
7
  class TypeMembership
10
8
  # @return [Class<GraphQL::Schema::Object>]
11
9
  attr_accessor :object_type
@@ -26,9 +24,25 @@ module GraphQL
26
24
  end
27
25
 
28
26
  # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
29
- def visible?(_ctx)
30
- true
27
+ def visible?(ctx)
28
+ warden = Warden.from_context(ctx)
29
+ (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
30
+ (@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
31
31
  end
32
+
33
+ def graphql_name
34
+ "#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
35
+ end
36
+
37
+ def path
38
+ graphql_name
39
+ end
40
+
41
+ def inspect
42
+ "#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
43
+ end
44
+
45
+ alias :type_class :itself
32
46
  end
33
47
  end
34
48
  end
@@ -19,8 +19,9 @@ module GraphQL
19
19
  end
20
20
  else
21
21
  visible_types = []
22
+ warden = Warden.from_context(context)
22
23
  type_memberships.each do |type_membership|
23
- if type_membership.visible?(context)
24
+ if warden.visible_type_membership?(type_membership, context)
24
25
  visible_types << type_membership.object_type
25
26
  end
26
27
  end
@@ -28,6 +29,12 @@ module GraphQL
28
29
  end
29
30
  end
30
31
 
32
+ def all_possible_types
33
+ type_memberships.map(&:object_type)
34
+ end
35
+
36
+ prepend GraphQL::Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
37
+
31
38
  def to_graphql
32
39
  type_defn = GraphQL::UnionType.new
33
40
  type_defn.name = graphql_name
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to specifically reject values that respond to `.blank?` and respond truthy for that method.
7
+ #
8
+ # @example Require a non-empty string for an argument
9
+ # argument :name, String, required: true, validate: { allow_blank: false }
10
+ class AllowBlankValidator < Validator
11
+ def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
12
+ @message = message
13
+ super(**default_options)
14
+ @allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
15
+ end
16
+
17
+ def validate(_object, _context, value)
18
+ if value.respond_to?(:blank?) && value.blank?
19
+ if (value.nil? && @allow_null) || @allow_blank
20
+ # pass
21
+ else
22
+ @message
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to specifically reject or permit `nil` values (given as `null` from GraphQL).
7
+ #
8
+ # @example require a non-null value for an argument if it is provided
9
+ # argument :name, String, required: false, validates: { allow_null: false }
10
+ class AllowNullValidator < Validator
11
+ MESSAGE = "%{validated} can't be null"
12
+ def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
13
+ @message = message
14
+ super(**default_options)
15
+ @allow_null = allow_null.nil? ? allow_null_positional : allow_null
16
+ end
17
+
18
+ def validate(_object, _context, value)
19
+ if value.nil? && !@allow_null
20
+ @message
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -21,7 +21,9 @@ module GraphQL
21
21
  end
22
22
 
23
23
  def validate(_object, _context, value)
24
- if @in_list.include?(value)
24
+ if permitted_empty_value?(value)
25
+ # pass
26
+ elsif @in_list.include?(value)
25
27
  @message
26
28
  end
27
29
  end
@@ -18,10 +18,6 @@ module GraphQL
18
18
  # # It's pretty hard to come up with a legitimate use case for `without:`
19
19
  #
20
20
  class FormatValidator < Validator
21
- if !String.method_defined?(:match?)
22
- using GraphQL::StringMatchBackport
23
- end
24
-
25
21
  # @param with [RegExp, nil]
26
22
  # @param without [Regexp, nil]
27
23
  # @param message [String]
@@ -38,7 +34,10 @@ module GraphQL
38
34
  end
39
35
 
40
36
  def validate(_object, _context, value)
41
- if (@with_pattern && !value.match?(@with_pattern)) ||
37
+ if permitted_empty_value?(value)
38
+ # Do nothing
39
+ elsif value.nil? ||
40
+ (@with_pattern && !value.match?(@with_pattern)) ||
42
41
  (@without_pattern && value.match?(@without_pattern))
43
42
  @message
44
43
  end
@@ -23,7 +23,9 @@ module GraphQL
23
23
  end
24
24
 
25
25
  def validate(_object, _context, value)
26
- if !@in_list.include?(value)
26
+ if permitted_empty_value?(value)
27
+ # pass
28
+ elsif !@in_list.include?(value)
27
29
  @message
28
30
  end
29
31
  end
@@ -43,11 +43,13 @@ module GraphQL
43
43
  end
44
44
 
45
45
  def validate(_object, _context, value)
46
- if @maximum && value.length > @maximum
46
+ return if permitted_empty_value?(value) # pass in this case
47
+ length = value.nil? ? 0 : value.length
48
+ if @maximum && length > @maximum
47
49
  partial_format(@too_long, { count: @maximum })
48
- elsif @minimum && value.length < @minimum
50
+ elsif @minimum && length < @minimum
49
51
  partial_format(@too_short, { count: @minimum })
50
- elsif @is && value.length != @is
52
+ elsif @is && length != @is
51
53
  partial_format(@wrong_length, { count: @is })
52
54
  end
53
55
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module GraphQL
2
3
  class Schema
3
4
  class Validator
@@ -24,13 +25,15 @@ module GraphQL
24
25
  # @param other_than [Integer]
25
26
  # @param odd [Boolean]
26
27
  # @param even [Boolean]
28
+ # @param within [Range]
27
29
  # @param message [String] used for all validation failures
28
30
  def initialize(
29
31
  greater_than: nil, greater_than_or_equal_to: nil,
30
32
  less_than: nil, less_than_or_equal_to: nil,
31
33
  equal_to: nil, other_than: nil,
32
- odd: nil, even: nil,
34
+ odd: nil, even: nil, within: nil,
33
35
  message: "%{validated} must be %{comparison} %{target}",
36
+ null_message: Validator::AllowNullValidator::MESSAGE,
34
37
  **default_options
35
38
  )
36
39
 
@@ -42,12 +45,18 @@ module GraphQL
42
45
  @other_than = other_than
43
46
  @odd = odd
44
47
  @even = even
48
+ @within = within
45
49
  @message = message
50
+ @null_message = null_message
46
51
  super(**default_options)
47
52
  end
48
53
 
49
54
  def validate(object, context, value)
50
- if @greater_than && value <= @greater_than
55
+ if permitted_empty_value?(value)
56
+ # pass in this case
57
+ elsif value.nil? # @allow_null is handled in the parent class
58
+ @null_message
59
+ elsif @greater_than && value <= @greater_than
51
60
  partial_format(@message, { comparison: "greater than", target: @greater_than })
52
61
  elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to
53
62
  partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to })
@@ -63,6 +72,8 @@ module GraphQL
63
72
  (partial_format(@message, { comparison: "even", target: "" })).strip
64
73
  elsif @odd && !value.odd?
65
74
  (partial_format(@message, { comparison: "odd", target: "" })).strip
75
+ elsif @within && !@within.include?(value)
76
+ partial_format(@message, { comparison: "within", target: @within })
66
77
  end
67
78
  end
68
79
  end
@@ -14,7 +14,7 @@ module GraphQL
14
14
  # argument :ingredient_id, ID, required: true
15
15
  # argument :cups, Integer, required: false
16
16
  # argument :tablespoons, Integer, required: false
17
- # argument :teaspoons, Integer, required: true
17
+ # argument :teaspoons, Integer, required: false
18
18
  # validates required: { one_of: [:cups, :tablespoons, :teaspoons] }
19
19
  # end
20
20
  #
@@ -28,11 +28,23 @@ module GraphQL
28
28
  # validates required: { one_of: [:node_id, [:object_type, :object_id]] }
29
29
  # end
30
30
  #
31
+ # @example require _some_ value for an argument, even if it's null
32
+ # field :update_settings, AccountSettings do
33
+ # # `required: :nullable` means this argument must be given, but may be `null`
34
+ # argument :age, Integer, required: :nullable
35
+ # end
36
+ #
31
37
  class RequiredValidator < Validator
32
38
  # @param one_of [Symbol, Array<Symbol>] An argument, or a list of arguments, that represents a valid set of inputs for this field
33
39
  # @param message [String]
34
- def initialize(one_of:, message: "%{validated} has the wrong arguments", **default_options)
35
- @one_of = one_of
40
+ def initialize(one_of: nil, argument: nil, message: "%{validated} has the wrong arguments", **default_options)
41
+ @one_of = if one_of
42
+ one_of
43
+ elsif argument
44
+ [argument]
45
+ else
46
+ raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
47
+ end
36
48
  @message = message
37
49
  super(**default_options)
38
50
  end
@@ -40,19 +52,21 @@ module GraphQL
40
52
  def validate(_object, _context, value)
41
53
  matched_conditions = 0
42
54
 
43
- @one_of.each do |one_of_condition|
44
- case one_of_condition
45
- when Symbol
46
- if value.key?(one_of_condition)
47
- matched_conditions += 1
48
- end
49
- when Array
50
- if one_of_condition.all? { |k| value.key?(k) }
51
- matched_conditions += 1
52
- break
55
+ if !value.nil?
56
+ @one_of.each do |one_of_condition|
57
+ case one_of_condition
58
+ when Symbol
59
+ if value.key?(one_of_condition)
60
+ matched_conditions += 1
61
+ end
62
+ when Array
63
+ if one_of_condition.all? { |k| value.key?(k) }
64
+ matched_conditions += 1
65
+ break
66
+ end
67
+ else
68
+ raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
53
69
  end
54
- else
55
- raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
56
70
  end
57
71
  end
58
72