graphql 1.11.6 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/object_generator.rb +2 -0
  4. data/lib/generators/graphql/relay_generator.rb +63 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/graphql.rb +39 -4
  12. data/lib/graphql/analysis/analyze_query.rb +7 -0
  13. data/lib/graphql/analysis/ast.rb +11 -2
  14. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  15. data/lib/graphql/backtrace.rb +28 -19
  16. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  17. data/lib/graphql/backtrace/table.rb +22 -2
  18. data/lib/graphql/backtrace/tracer.rb +40 -9
  19. data/lib/graphql/backwards_compatibility.rb +2 -1
  20. data/lib/graphql/base_type.rb +1 -1
  21. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  22. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  23. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  24. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  25. data/lib/graphql/dataloader.rb +198 -0
  26. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  27. data/lib/graphql/dataloader/request.rb +24 -0
  28. data/lib/graphql/dataloader/request_all.rb +22 -0
  29. data/lib/graphql/dataloader/source.rb +93 -0
  30. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  31. data/lib/graphql/define/instance_definable.rb +32 -2
  32. data/lib/graphql/define/type_definer.rb +5 -5
  33. data/lib/graphql/deprecated_dsl.rb +7 -2
  34. data/lib/graphql/deprecation.rb +13 -0
  35. data/lib/graphql/enum_type.rb +2 -0
  36. data/lib/graphql/execution/errors.rb +4 -0
  37. data/lib/graphql/execution/execute.rb +7 -0
  38. data/lib/graphql/execution/interpreter.rb +10 -6
  39. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  40. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  41. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  42. data/lib/graphql/execution/interpreter/runtime.rb +219 -117
  43. data/lib/graphql/execution/multiplex.rb +20 -6
  44. data/lib/graphql/function.rb +4 -0
  45. data/lib/graphql/input_object_type.rb +2 -0
  46. data/lib/graphql/integer_decoding_error.rb +17 -0
  47. data/lib/graphql/interface_type.rb +3 -1
  48. data/lib/graphql/internal_representation/document.rb +2 -2
  49. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +1 -1
  51. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  52. data/lib/graphql/object_type.rb +2 -0
  53. data/lib/graphql/pagination/connection.rb +5 -1
  54. data/lib/graphql/pagination/connections.rb +6 -16
  55. data/lib/graphql/query.rb +6 -1
  56. data/lib/graphql/query/arguments.rb +1 -1
  57. data/lib/graphql/query/context.rb +8 -1
  58. data/lib/graphql/query/serial_execution.rb +1 -0
  59. data/lib/graphql/query/validation_pipeline.rb +1 -1
  60. data/lib/graphql/relay/array_connection.rb +2 -2
  61. data/lib/graphql/relay/base_connection.rb +7 -0
  62. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  63. data/lib/graphql/relay/connection_type.rb +1 -1
  64. data/lib/graphql/relay/mutation.rb +1 -0
  65. data/lib/graphql/relay/node.rb +3 -0
  66. data/lib/graphql/relay/type_extensions.rb +2 -0
  67. data/lib/graphql/scalar_type.rb +2 -0
  68. data/lib/graphql/schema.rb +80 -29
  69. data/lib/graphql/schema/argument.rb +25 -7
  70. data/lib/graphql/schema/build_from_definition.rb +139 -51
  71. data/lib/graphql/schema/default_type_error.rb +2 -0
  72. data/lib/graphql/schema/directive.rb +76 -0
  73. data/lib/graphql/schema/directive/flagged.rb +57 -0
  74. data/lib/graphql/schema/enum.rb +3 -0
  75. data/lib/graphql/schema/enum_value.rb +12 -6
  76. data/lib/graphql/schema/field.rb +50 -22
  77. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  78. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  79. data/lib/graphql/schema/input_object.rb +33 -22
  80. data/lib/graphql/schema/interface.rb +1 -0
  81. data/lib/graphql/schema/member.rb +4 -0
  82. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  83. data/lib/graphql/schema/member/build_type.rb +3 -3
  84. data/lib/graphql/schema/member/has_arguments.rb +67 -50
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  86. data/lib/graphql/schema/member/has_directives.rb +98 -0
  87. data/lib/graphql/schema/member/has_validators.rb +31 -0
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/middleware_chain.rb +1 -1
  90. data/lib/graphql/schema/object.rb +11 -0
  91. data/lib/graphql/schema/printer.rb +5 -4
  92. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  93. data/lib/graphql/schema/resolver.rb +7 -0
  94. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  95. data/lib/graphql/schema/subscription.rb +19 -1
  96. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  97. data/lib/graphql/schema/validation.rb +4 -2
  98. data/lib/graphql/schema/validator.rb +163 -0
  99. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  100. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  101. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  102. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  103. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  104. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  105. data/lib/graphql/static_validation.rb +1 -0
  106. data/lib/graphql/static_validation/all_rules.rb +1 -0
  107. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  108. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  109. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  110. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  111. data/lib/graphql/static_validation/validator.rb +32 -9
  112. data/lib/graphql/subscriptions.rb +17 -20
  113. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  114. data/lib/graphql/tracing.rb +2 -2
  115. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  116. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  117. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  118. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  119. data/lib/graphql/types/int.rb +9 -2
  120. data/lib/graphql/types/relay.rb +11 -3
  121. data/lib/graphql/types/relay/base_connection.rb +2 -91
  122. data/lib/graphql/types/relay/base_edge.rb +2 -34
  123. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  124. data/lib/graphql/types/relay/default_relay.rb +27 -0
  125. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  126. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  127. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  128. data/lib/graphql/types/relay/node.rb +2 -4
  129. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  130. data/lib/graphql/types/relay/node_field.rb +1 -19
  131. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  132. data/lib/graphql/types/relay/page_info.rb +2 -14
  133. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  134. data/lib/graphql/types/string.rb +7 -1
  135. data/lib/graphql/union_type.rb +2 -0
  136. data/lib/graphql/upgrader/member.rb +1 -0
  137. data/lib/graphql/upgrader/schema.rb +1 -0
  138. data/lib/graphql/version.rb +1 -1
  139. data/readme.md +1 -1
  140. metadata +53 -9
  141. data/lib/graphql/types/relay/base_field.rb +0 -22
  142. data/lib/graphql/types/relay/base_interface.rb +0 -29
  143. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -37,6 +37,9 @@ module GraphQL
37
37
  def value(*args, **kwargs, &block)
38
38
  kwargs[:owner] = self
39
39
  value = enum_value_class.new(*args, **kwargs, &block)
40
+ if own_values.key?(value.graphql_name)
41
+ raise ArgumentError, "#{value.graphql_name} is already defined for #{self.graphql_name}, please remove one of the definitions."
42
+ end
40
43
  own_values[value.graphql_name] = value
41
44
  nil
42
45
  end
@@ -30,23 +30,29 @@ module GraphQL
30
30
  include GraphQL::Schema::Member::AcceptsDefinition
31
31
  include GraphQL::Schema::Member::HasPath
32
32
  include GraphQL::Schema::Member::HasAstNode
33
+ include GraphQL::Schema::Member::HasDirectives
34
+ include GraphQL::Schema::Member::HasDeprecationReason
33
35
 
34
36
  attr_reader :graphql_name
35
37
 
36
38
  # @return [Class] The enum type that owns this value
37
39
  attr_reader :owner
38
40
 
39
- # @return [String] Explains why this value was deprecated (if present, this will be marked deprecated in introspection)
40
- attr_accessor :deprecation_reason
41
-
42
- def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block)
41
+ def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, value: nil, deprecation_reason: nil, &block)
43
42
  @graphql_name = graphql_name.to_s
44
43
  GraphQL::NameValidator.validate!(@graphql_name)
45
44
  @description = desc || description
46
45
  @value = value.nil? ? @graphql_name : value
47
- @deprecation_reason = deprecation_reason
46
+ if deprecation_reason
47
+ self.deprecation_reason = deprecation_reason
48
+ end
48
49
  @owner = owner
49
50
  @ast_node = ast_node
51
+ if directives
52
+ directives.each do |dir_class, dir_options|
53
+ directive(dir_class, **dir_options)
54
+ end
55
+ end
50
56
 
51
57
  if block_given?
52
58
  instance_eval(&block)
@@ -73,7 +79,7 @@ module GraphQL
73
79
  enum_value.name = @graphql_name
74
80
  enum_value.description = @description
75
81
  enum_value.value = @value
76
- enum_value.deprecation_reason = @deprecation_reason
82
+ enum_value.deprecation_reason = self.deprecation_reason
77
83
  enum_value.metadata[:type_class] = self
78
84
  enum_value.ast_node = ast_node
79
85
  enum_value
@@ -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,11 +82,11 @@ 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
86
- warn("Legacy-style `GraphQL::Relay::Node.field` is being added to a class-based type. See `GraphQL::Types::Relay::NodeField` for a replacement.")
85
+ if kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodeField.graphql_definition
86
+ GraphQL::Deprecation.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
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.")
88
+ elsif kwargs[:field].is_a?(GraphQL::Field) && kwargs[:field] == GraphQL::Types::Relay::NodesField.graphql_definition
89
+ GraphQL::Deprecation.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
92
92
  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
@@ -230,7 +233,7 @@ module GraphQL
230
233
  end
231
234
  @function = function
232
235
  @resolve = resolve
233
- @deprecation_reason = deprecation_reason
236
+ self.deprecation_reason = deprecation_reason
234
237
 
235
238
  if method && hash_key
236
239
  raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
@@ -269,6 +272,7 @@ module GraphQL
269
272
  @relay_nodes_field = relay_nodes_field
270
273
  @ast_node = ast_node
271
274
  @method_conflict_warning = method_conflict_warning
275
+ @legacy_edge_class = legacy_edge_class
272
276
 
273
277
  arguments.each do |name, arg|
274
278
  if arg.is_a?(Hash)
@@ -297,6 +301,14 @@ module GraphQL
297
301
  self.extension(connection_extension)
298
302
  end
299
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
+
300
312
  if definition_block
301
313
  if definition_block.arity == 1
302
314
  yield self
@@ -438,8 +450,8 @@ module GraphQL
438
450
  field_defn.description = @description
439
451
  end
440
452
 
441
- if @deprecation_reason
442
- field_defn.deprecation_reason = @deprecation_reason
453
+ if self.deprecation_reason
454
+ field_defn.deprecation_reason = self.deprecation_reason
443
455
  end
444
456
 
445
457
  if @resolver_class
@@ -461,6 +473,10 @@ module GraphQL
461
473
  field_defn.relay_nodes_field = @relay_nodes_field
462
474
  end
463
475
 
476
+ if @legacy_edge_class
477
+ field_defn.edge_class = @legacy_edge_class
478
+ end
479
+
464
480
  field_defn.resolve = self.method(:resolve_field)
465
481
  field_defn.connection = connection?
466
482
  field_defn.connection_max_page_size = max_page_size
@@ -580,6 +596,9 @@ module GraphQL
580
596
  begin
581
597
  # Unwrap the GraphQL object to get the application object.
582
598
  application_object = object.object
599
+
600
+ Schema::Validator.validate!(validators, application_object, ctx, args)
601
+
583
602
  ctx.schema.after_lazy(self.authorized?(application_object, args, ctx)) do |is_authorized|
584
603
  if is_authorized
585
604
  public_send_field(object, args, ctx)
@@ -725,33 +744,42 @@ module GraphQL
725
744
  if @extensions.empty?
726
745
  yield(obj, args)
727
746
  else
728
- extended_obj = obj
729
- extended_args = args
730
-
731
- memos = []
732
- value = run_extensions_before_resolve(memos, obj, args, ctx) do |obj, args|
733
- extended_obj = obj
734
- extended_args = args
747
+ # This is a hack to get the _last_ value for extended obj and args,
748
+ # in case one of the extensions doesn't `yield`.
749
+ # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
750
+ extended = { args: args, obj: obj, memos: nil }
751
+ value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
735
752
  yield(obj, args)
736
753
  end
737
754
 
755
+ extended_obj = extended[:obj]
756
+ extended_args = extended[:args]
757
+ memos = extended[:memos] || EMPTY_HASH
758
+
738
759
  ctx.schema.after_lazy(value) do |resolved_value|
739
- @extensions.each_with_index do |ext, idx|
760
+ idx = 0
761
+ @extensions.each do |ext|
740
762
  memo = memos[idx]
741
763
  # TODO after_lazy?
742
764
  resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo)
765
+ idx += 1
743
766
  end
744
767
  resolved_value
745
768
  end
746
769
  end
747
770
  end
748
771
 
749
- def run_extensions_before_resolve(memos, obj, args, ctx, idx: 0)
772
+ def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
750
773
  extension = @extensions[idx]
751
774
  if extension
752
775
  extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo|
753
- memos << memo
754
- run_extensions_before_resolve(memos, extended_obj, extended_args, ctx, idx: idx + 1) { |o, a| yield(o, a) }
776
+ if memo
777
+ memos = extended[:memos] ||= {}
778
+ memos[idx] = memo
779
+ end
780
+ extended[:obj] = extended_obj
781
+ extended[:args] = extended_args
782
+ run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
755
783
  end
756
784
  else
757
785
  yield(obj, args)
@@ -42,6 +42,7 @@ module GraphQL
42
42
  value.after_value ||= original_arguments[:after]
43
43
  value.last_value ||= original_arguments[:last]
44
44
  value.before_value ||= original_arguments[:before]
45
+ value.field ||= field
45
46
  if field.has_max_page_size? && !value.has_max_page_size_override?
46
47
  value.max_page_size = field.max_page_size
47
48
  end
@@ -50,8 +51,8 @@ module GraphQL
50
51
  end
51
52
  value
52
53
  elsif context.schema.new_connections?
53
- wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
54
- context.schema.connections.wrap(field, object.object, value, original_arguments, context, wrappers: wrappers)
54
+ context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
55
+ context.schema.connections.wrap(field, object.object, value, original_arguments, context)
55
56
  else
56
57
  if object.is_a?(GraphQL::Schema::Object)
57
58
  object = object.object
@@ -4,7 +4,7 @@ module GraphQL
4
4
  class Schema
5
5
  class Field
6
6
  class ScopeExtension < GraphQL::Schema::FieldExtension
7
- def after_resolve(value:, context:, **rest)
7
+ def after_resolve(object:, arguments:, context:, value:, memo:)
8
8
  if value.nil?
9
9
  value
10
10
  else
@@ -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
@@ -168,17 +173,10 @@ module GraphQL
168
173
  return result
169
174
  end
170
175
 
171
- input = begin
172
- input.to_h
173
- rescue
174
- begin
175
- # Handle ActionController::Parameters:
176
- input.to_unsafe_h
177
- rescue
178
- # We're not sure it'll act like a hash, so reject it:
179
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
180
- return result
181
- 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
182
180
  end
183
181
 
184
182
  # Inject missing required arguments
@@ -190,16 +188,19 @@ module GraphQL
190
188
  m
191
189
  end
192
190
 
193
- input.merge(missing_required_inputs).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
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?
199
203
  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?
203
204
  end
204
205
 
205
206
  result
@@ -240,6 +241,16 @@ module GraphQL
240
241
  result
241
242
  end
242
243
  end
244
+
245
+ private
246
+
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
251
+ end
252
+ @ruby_style_hash[key] = value
253
+ end
243
254
  end
244
255
  end
245
256
  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
@@ -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
@@ -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
@@ -85,70 +85,87 @@ module GraphQL
85
85
  # @param context [GraphQL::Query::Context]
86
86
  # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
87
87
  def coerce_arguments(parent_object, values, context)
88
- argument_values = {}
89
- kwarg_arguments = {}
90
88
  # Cache this hash to avoid re-merging it
91
89
  arg_defns = self.arguments
92
90
 
93
- maybe_lazies = []
94
- arg_lazies = arg_defns.map do |arg_name, arg_defn|
95
- arg_key = arg_defn.keyword
96
- has_value = false
97
- default_used = false
98
- if values.key?(arg_name)
99
- has_value = true
100
- value = values[arg_name]
101
- elsif values.key?(arg_key)
102
- has_value = true
103
- value = values[arg_key]
104
- elsif arg_defn.default_value?
105
- has_value = true
106
- value = arg_defn.default_value
107
- default_used = true
108
- end
109
-
110
- if has_value
111
- loads = arg_defn.loads
112
- loaded_value = nil
113
- if loads && !arg_defn.from_resolver?
114
- loaded_value = if arg_defn.type.list?
115
- loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
116
- context.schema.after_any_lazies(loaded_values) { |result| result }
117
- else
118
- load_application_object(arg_defn, loads, value, context)
119
- end
91
+ if arg_defns.empty?
92
+ GraphQL::Execution::Interpreter::Arguments::EMPTY
93
+ else
94
+ argument_values = {}
95
+ arg_lazies = arg_defns.map do |arg_name, arg_defn|
96
+ arg_key = arg_defn.keyword
97
+ has_value = false
98
+ default_used = false
99
+ if values.key?(arg_name)
100
+ has_value = true
101
+ value = values[arg_name]
102
+ elsif values.key?(arg_key)
103
+ has_value = true
104
+ value = values[arg_key]
105
+ elsif arg_defn.default_value?
106
+ has_value = true
107
+ value = arg_defn.default_value
108
+ default_used = true
120
109
  end
121
110
 
122
- coerced_value = if loaded_value
123
- loaded_value
124
- else
125
- context.schema.error_handler.with_error_handling(context) do
111
+ if has_value
112
+ loads = arg_defn.loads
113
+ loaded_value = nil
114
+ coerced_value = context.schema.error_handler.with_error_handling(context) do
126
115
  arg_defn.type.coerce_input(value, context)
127
116
  end
128
- end
129
117
 
130
- context.schema.after_lazy(coerced_value) do |coerced_value|
131
- prepared_value = context.schema.error_handler.with_error_handling(context) do
132
- arg_defn.prepare_value(parent_object, coerced_value, context: context)
118
+ # TODO this should probably be inside after_lazy
119
+ if loads && !arg_defn.from_resolver?
120
+ loaded_value = if arg_defn.type.list?
121
+ loaded_values = coerced_value.map { |val| load_application_object(arg_defn, loads, val, context) }
122
+ context.schema.after_any_lazies(loaded_values) { |result| result }
123
+ else
124
+ load_application_object(arg_defn, loads, coerced_value, context)
125
+ end
126
+ end
127
+
128
+ coerced_value = if loaded_value
129
+ loaded_value
130
+ else
131
+ coerced_value
133
132
  end
134
133
 
135
- kwarg_arguments[arg_key] = prepared_value
136
- # TODO code smell to access such a deeply-nested constant in a distant module
137
- argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
138
- value: prepared_value,
139
- definition: arg_defn,
140
- default_used: default_used,
141
- )
134
+ context.schema.after_lazy(coerced_value) do |coerced_value|
135
+ validate_directive_argument(arg_defn, coerced_value)
136
+ prepared_value = context.schema.error_handler.with_error_handling(context) do
137
+ arg_defn.prepare_value(parent_object, coerced_value, context: context)
138
+ end
139
+
140
+ # TODO code smell to access such a deeply-nested constant in a distant module
141
+ argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
142
+ value: prepared_value,
143
+ definition: arg_defn,
144
+ default_used: default_used,
145
+ )
146
+ end
147
+ else
148
+ # has_value is false
149
+ validate_directive_argument(arg_defn, nil)
142
150
  end
143
151
  end
152
+
153
+ context.schema.after_any_lazies(arg_lazies) do
154
+ GraphQL::Execution::Interpreter::Arguments.new(
155
+ argument_values: argument_values,
156
+ )
157
+ end
144
158
  end
159
+ end
145
160
 
146
- maybe_lazies.concat(arg_lazies)
147
- context.schema.after_any_lazies(maybe_lazies) do
148
- GraphQL::Execution::Interpreter::Arguments.new(
149
- keyword_arguments: kwarg_arguments,
150
- argument_values: argument_values,
151
- )
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
152
169
  end
153
170
  end
154
171