graphql 1.11.4 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) 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/generators/graphql/templates/union.erb +1 -1
  12. data/lib/graphql.rb +55 -4
  13. data/lib/graphql/analysis/analyze_query.rb +7 -0
  14. data/lib/graphql/analysis/ast.rb +11 -2
  15. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  16. data/lib/graphql/argument.rb +3 -3
  17. data/lib/graphql/backtrace.rb +28 -19
  18. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  19. data/lib/graphql/backtrace/table.rb +22 -2
  20. data/lib/graphql/backtrace/tracer.rb +40 -8
  21. data/lib/graphql/backwards_compatibility.rb +1 -0
  22. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  23. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  24. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  25. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  26. data/lib/graphql/dataloader.rb +198 -0
  27. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  28. data/lib/graphql/dataloader/request.rb +24 -0
  29. data/lib/graphql/dataloader/request_all.rb +22 -0
  30. data/lib/graphql/dataloader/source.rb +93 -0
  31. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  32. data/lib/graphql/define/instance_definable.rb +32 -2
  33. data/lib/graphql/define/type_definer.rb +5 -5
  34. data/lib/graphql/deprecated_dsl.rb +5 -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 +20 -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 +236 -120
  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/introspection.rb +96 -0
  49. data/lib/graphql/introspection/field_type.rb +7 -3
  50. data/lib/graphql/introspection/input_value_type.rb +6 -0
  51. data/lib/graphql/introspection/introspection_query.rb +6 -92
  52. data/lib/graphql/introspection/type_type.rb +7 -3
  53. data/lib/graphql/invalid_null_error.rb +1 -1
  54. data/lib/graphql/language/block_string.rb +24 -5
  55. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  56. data/lib/graphql/language/lexer.rb +7 -3
  57. data/lib/graphql/language/lexer.rl +7 -3
  58. data/lib/graphql/language/nodes.rb +1 -1
  59. data/lib/graphql/language/parser.rb +107 -103
  60. data/lib/graphql/language/parser.y +4 -0
  61. data/lib/graphql/language/sanitized_printer.rb +59 -26
  62. data/lib/graphql/name_validator.rb +6 -7
  63. data/lib/graphql/object_type.rb +2 -0
  64. data/lib/graphql/pagination/connection.rb +5 -1
  65. data/lib/graphql/pagination/connections.rb +15 -17
  66. data/lib/graphql/query.rb +8 -3
  67. data/lib/graphql/query/context.rb +18 -3
  68. data/lib/graphql/query/serial_execution.rb +1 -0
  69. data/lib/graphql/query/validation_pipeline.rb +1 -1
  70. data/lib/graphql/relay/array_connection.rb +2 -2
  71. data/lib/graphql/relay/base_connection.rb +7 -0
  72. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  73. data/lib/graphql/relay/connection_type.rb +1 -1
  74. data/lib/graphql/relay/mutation.rb +1 -0
  75. data/lib/graphql/relay/node.rb +3 -0
  76. data/lib/graphql/relay/range_add.rb +14 -5
  77. data/lib/graphql/relay/type_extensions.rb +2 -0
  78. data/lib/graphql/scalar_type.rb +2 -0
  79. data/lib/graphql/schema.rb +104 -39
  80. data/lib/graphql/schema/argument.rb +74 -5
  81. data/lib/graphql/schema/build_from_definition.rb +203 -86
  82. data/lib/graphql/schema/default_type_error.rb +2 -0
  83. data/lib/graphql/schema/directive.rb +76 -0
  84. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  85. data/lib/graphql/schema/directive/flagged.rb +57 -0
  86. data/lib/graphql/schema/enum.rb +3 -0
  87. data/lib/graphql/schema/enum_value.rb +12 -6
  88. data/lib/graphql/schema/field.rb +59 -24
  89. data/lib/graphql/schema/field/connection_extension.rb +10 -8
  90. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  91. data/lib/graphql/schema/input_object.rb +38 -25
  92. data/lib/graphql/schema/interface.rb +2 -1
  93. data/lib/graphql/schema/late_bound_type.rb +2 -2
  94. data/lib/graphql/schema/loader.rb +1 -0
  95. data/lib/graphql/schema/member.rb +4 -0
  96. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  97. data/lib/graphql/schema/member/build_type.rb +17 -7
  98. data/lib/graphql/schema/member/has_arguments.rb +70 -51
  99. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  100. data/lib/graphql/schema/member/has_directives.rb +98 -0
  101. data/lib/graphql/schema/member/has_fields.rb +2 -2
  102. data/lib/graphql/schema/member/has_validators.rb +31 -0
  103. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  104. data/lib/graphql/schema/object.rb +11 -0
  105. data/lib/graphql/schema/printer.rb +5 -4
  106. data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
  107. data/lib/graphql/schema/resolver.rb +7 -0
  108. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  109. data/lib/graphql/schema/subscription.rb +19 -1
  110. data/lib/graphql/schema/timeout.rb +29 -15
  111. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  112. data/lib/graphql/schema/unique_within_type.rb +1 -2
  113. data/lib/graphql/schema/validation.rb +10 -0
  114. data/lib/graphql/schema/validator.rb +163 -0
  115. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  116. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  117. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  118. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  119. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  120. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  121. data/lib/graphql/static_validation.rb +1 -0
  122. data/lib/graphql/static_validation/all_rules.rb +1 -0
  123. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  124. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  125. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  126. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  127. data/lib/graphql/static_validation/validator.rb +33 -7
  128. data/lib/graphql/subscriptions.rb +18 -23
  129. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  130. data/lib/graphql/tracing.rb +2 -2
  131. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  132. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  133. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  134. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  135. data/lib/graphql/types/int.rb +9 -2
  136. data/lib/graphql/types/relay.rb +11 -3
  137. data/lib/graphql/types/relay/base_connection.rb +2 -91
  138. data/lib/graphql/types/relay/base_edge.rb +2 -34
  139. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  140. data/lib/graphql/types/relay/default_relay.rb +27 -0
  141. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  142. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  143. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  144. data/lib/graphql/types/relay/node.rb +2 -4
  145. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  146. data/lib/graphql/types/relay/node_field.rb +1 -19
  147. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  148. data/lib/graphql/types/relay/page_info.rb +2 -14
  149. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  150. data/lib/graphql/types/string.rb +7 -1
  151. data/lib/graphql/unauthorized_error.rb +1 -1
  152. data/lib/graphql/union_type.rb +2 -0
  153. data/lib/graphql/upgrader/member.rb +1 -0
  154. data/lib/graphql/upgrader/schema.rb +1 -0
  155. data/lib/graphql/version.rb +1 -1
  156. data/readme.md +1 -1
  157. metadata +49 -6
  158. data/lib/graphql/types/relay/base_field.rb +0 -22
  159. data/lib/graphql/types/relay/base_interface.rb +0 -29
  160. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -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
@@ -126,9 +131,11 @@ module GraphQL
126
131
  argument_defn = super(*args, **kwargs, &block)
127
132
  # Add a method access
128
133
  method_name = argument_defn.keyword
129
- define_method(method_name) do
130
- self[method_name]
131
- end
134
+ class_eval <<-RUBY, __FILE__, __LINE__
135
+ def #{method_name}
136
+ self[#{method_name.inspect}]
137
+ end
138
+ RUBY
132
139
  end
133
140
 
134
141
  def to_graphql
@@ -166,17 +173,10 @@ module GraphQL
166
173
  return result
167
174
  end
168
175
 
169
- input = begin
170
- input.to_h
171
- rescue
172
- begin
173
- # Handle ActionController::Parameters:
174
- input.to_unsafe_h
175
- rescue
176
- # We're not sure it'll act like a hash, so reject it:
177
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
178
- return result
179
- 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
180
180
  end
181
181
 
182
182
  # Inject missing required arguments
@@ -188,16 +188,19 @@ module GraphQL
188
188
  m
189
189
  end
190
190
 
191
- input.merge(missing_required_inputs).each do |argument_name, value|
192
- argument = warden.get_argument(self, argument_name)
193
- # Items in the input that are unexpected
194
- unless argument
195
- result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
196
- 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?
197
203
  end
198
- # Items in the input that are expected, but have invalid values
199
- argument_result = argument.type.validate_input(value, ctx)
200
- result.merge_result!(argument_name, argument_result) unless argument_result.valid?
201
204
  end
202
205
 
203
206
  result
@@ -238,6 +241,16 @@ module GraphQL
238
241
  result
239
242
  end
240
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
241
254
  end
242
255
  end
243
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
@@ -30,7 +31,7 @@ module GraphQL
30
31
 
31
32
  # The interface is accessible if any of its possible types are accessible
32
33
  def accessible?(context)
33
- context.schema.possible_types(self).each do |type|
34
+ context.schema.possible_types(self, context).each do |type|
34
35
  if context.schema.accessible?(type, context)
35
36
  return true
36
37
  end
@@ -16,11 +16,11 @@ module GraphQL
16
16
  end
17
17
 
18
18
  def to_non_null_type
19
- GraphQL::NonNullType.new(of_type: self)
19
+ @to_non_null_type ||= GraphQL::NonNullType.new(of_type: self)
20
20
  end
21
21
 
22
22
  def to_list_type
23
- GraphQL::ListType.new(of_type: self)
23
+ @to_list_type ||= GraphQL::ListType.new(of_type: self)
24
24
  end
25
25
 
26
26
  def inspect
@@ -189,6 +189,7 @@ module GraphQL
189
189
  kwargs = {
190
190
  type: type_resolver.call(arg["type"]),
191
191
  description: arg["description"],
192
+ deprecation_reason: arg["deprecationReason"],
192
193
  required: false,
193
194
  method_access: false,
194
195
  camelize: false,
@@ -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
@@ -4,6 +4,10 @@ module GraphQL
4
4
  class Member
5
5
  # @api private
6
6
  module BuildType
7
+ if !String.method_defined?(:match?)
8
+ using GraphQL::StringMatchBackport
9
+ end
10
+
7
11
  LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported"
8
12
 
9
13
  module_function
@@ -56,11 +60,11 @@ module GraphQL
56
60
  parse_type(type_expr.first, null: false)
57
61
  when 2
58
62
  inner_type, nullable_option = type_expr
59
- if nullable_option.keys != [:null] || nullable_option.values != [true]
63
+ if nullable_option.keys != [:null] || (nullable_option[:null] != true && nullable_option[:null] != false)
60
64
  raise ArgumentError, LIST_TYPE_ERROR
61
65
  end
62
66
  list_type = true
63
- parse_type(inner_type, null: true)
67
+ parse_type(inner_type, null: nullable_option[:null])
64
68
  else
65
69
  raise ArgumentError, LIST_TYPE_ERROR
66
70
  end
@@ -71,7 +75,7 @@ module GraphQL
71
75
  if type_expr.respond_to?(:graphql_definition)
72
76
  type_expr
73
77
  else
74
- # Eg `String` => GraphQL::STRING_TYPE
78
+ # Eg `String` => GraphQL::Types::String
75
79
  parse_type(type_expr.name, null: true)
76
80
  end
77
81
  when Proc
@@ -162,10 +166,16 @@ module GraphQL
162
166
  end
163
167
 
164
168
  def underscore(string)
165
- string
166
- .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
167
- .gsub(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
168
- .downcase
169
+ if string.match?(/\A[a-z_]+\Z/)
170
+ return string
171
+ end
172
+ string2 = string.dup
173
+
174
+ string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder
175
+ string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing
176
+ string2.downcase!
177
+
178
+ string2
169
179
  end
170
180
  end
171
181
  end
@@ -43,6 +43,7 @@ module GraphQL
43
43
  # @param arg_defn [GraphQL::Schema::Argument]
44
44
  # @return [GraphQL::Schema::Argument]
45
45
  def add_argument(arg_defn)
46
+ @own_arguments ||= {}
46
47
  own_arguments[arg_defn.name] = arg_defn
47
48
  arg_defn
48
49
  end
@@ -84,70 +85,87 @@ module GraphQL
84
85
  # @param context [GraphQL::Query::Context]
85
86
  # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
86
87
  def coerce_arguments(parent_object, values, context)
87
- argument_values = {}
88
- kwarg_arguments = {}
89
88
  # Cache this hash to avoid re-merging it
90
89
  arg_defns = self.arguments
91
90
 
92
- maybe_lazies = []
93
- arg_lazies = arg_defns.map do |arg_name, arg_defn|
94
- arg_key = arg_defn.keyword
95
- has_value = false
96
- default_used = false
97
- if values.key?(arg_name)
98
- has_value = true
99
- value = values[arg_name]
100
- elsif values.key?(arg_key)
101
- has_value = true
102
- value = values[arg_key]
103
- elsif arg_defn.default_value?
104
- has_value = true
105
- value = arg_defn.default_value
106
- default_used = true
107
- end
108
-
109
- if has_value
110
- loads = arg_defn.loads
111
- loaded_value = nil
112
- if loads && !arg_defn.from_resolver?
113
- loaded_value = if arg_defn.type.list?
114
- loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
115
- context.schema.after_any_lazies(loaded_values) { |result| result }
116
- else
117
- load_application_object(arg_defn, loads, value, context)
118
- 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
119
109
  end
120
110
 
121
- coerced_value = if loaded_value
122
- loaded_value
123
- else
124
- 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
125
115
  arg_defn.type.coerce_input(value, context)
126
116
  end
127
- end
128
117
 
129
- context.schema.after_lazy(coerced_value) do |coerced_value|
130
- prepared_value = context.schema.error_handler.with_error_handling(context) do
131
- 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
132
132
  end
133
133
 
134
- kwarg_arguments[arg_key] = prepared_value
135
- # TODO code smell to access such a deeply-nested constant in a distant module
136
- argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
137
- value: prepared_value,
138
- definition: arg_defn,
139
- default_used: default_used,
140
- )
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)
141
150
  end
142
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
143
158
  end
159
+ end
144
160
 
145
- maybe_lazies.concat(arg_lazies)
146
- context.schema.after_any_lazies(maybe_lazies) do
147
- GraphQL::Execution::Interpreter::Arguments.new(
148
- keyword_arguments: kwarg_arguments,
149
- argument_values: argument_values,
150
- )
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
151
169
  end
152
170
  end
153
171
 
@@ -229,8 +247,9 @@ module GraphQL
229
247
  end
230
248
  end
231
249
 
250
+ NO_ARGUMENTS = {}.freeze
232
251
  def own_arguments
233
- @own_arguments ||= {}
252
+ @own_arguments || NO_ARGUMENTS
234
253
  end
235
254
  end
236
255
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDeprecationReason
7
+ # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
8
+ def deprecation_reason
9
+ dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
10
+ dir && dir.arguments[:reason]
11
+ end
12
+
13
+ # Set the deprecation reason for this member, or remove it by assigning `nil`
14
+ # @param text [String, nil]
15
+ def deprecation_reason=(text)
16
+ if text.nil?
17
+ remove_directive(GraphQL::Schema::Directive::Deprecated)
18
+ else
19
+ directive(GraphQL::Schema::Directive::Deprecated, reason: text)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDirectives
7
+ # Create an instance of `dir_class` for `self`, using `options`.
8
+ #
9
+ # It removes a previously-attached instance of `dir_class`, if there is one.
10
+ #
11
+ # @return [void]
12
+ def directive(dir_class, **options)
13
+ @own_directives ||= []
14
+ remove_directive(dir_class)
15
+ @own_directives << dir_class.new(self, **options)
16
+ nil
17
+ end
18
+
19
+ # Remove an attached instance of `dir_class`, if there is one
20
+ # @param dir_class [Class<GraphQL::Schema::Directive>]
21
+ # @return [viod]
22
+ def remove_directive(dir_class)
23
+ @own_directives && @own_directives.reject! { |d| d.is_a?(dir_class) }
24
+ nil
25
+ end
26
+
27
+ NO_DIRECTIVES = [].freeze
28
+
29
+ def directives
30
+ case self
31
+ when Class
32
+ inherited_directives = if superclass.respond_to?(:directives)
33
+ superclass.directives
34
+ else
35
+ NO_DIRECTIVES
36
+ end
37
+ if inherited_directives.any? && @own_directives
38
+ dirs = []
39
+ merge_directives(dirs, inherited_directives)
40
+ merge_directives(dirs, @own_directives)
41
+ dirs
42
+ elsif @own_directives
43
+ @own_directives
44
+ elsif inherited_directives.any?
45
+ inherited_directives
46
+ else
47
+ NO_DIRECTIVES
48
+ end
49
+ when Module
50
+ dirs = nil
51
+ self.ancestors.reverse_each do |ancestor|
52
+ if ancestor.respond_to?(:own_directives) &&
53
+ (anc_dirs = ancestor.own_directives).any?
54
+ dirs ||= []
55
+ merge_directives(dirs, anc_dirs)
56
+ end
57
+ end
58
+ if own_directives
59
+ dirs ||= []
60
+ merge_directives(dirs, own_directives)
61
+ end
62
+ dirs || NO_DIRECTIVES
63
+ when HasDirectives
64
+ @own_directives || NO_DIRECTIVES
65
+ else
66
+ raise "Invariant: how could #{self} not be a Class, Module, or instance of HasDirectives?"
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ def own_directives
73
+ @own_directives
74
+ end
75
+
76
+ private
77
+
78
+ # Modify `target` by adding items from `dirs` such that:
79
+ # - Any name conflict is overriden by the incoming member of `dirs`
80
+ # - Any other member of `dirs` is appended
81
+ # @param target [Array<GraphQL::Schema::Directive>]
82
+ # @param dirs [Array<GraphQL::Schema::Directive>]
83
+ # @return [void]
84
+ def merge_directives(target, dirs)
85
+ dirs.each do |dir|
86
+ if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
87
+ target.slice!(idx)
88
+ target.insert(idx, dir)
89
+ else
90
+ target << dir
91
+ end
92
+ end
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end