graphql 1.11.6 → 1.12.3

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 (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