graphql 1.9.17 → 1.11.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +18 -2
  3. data/lib/generators/graphql/install_generator.rb +27 -0
  4. data/lib/generators/graphql/object_generator.rb +52 -8
  5. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  6. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  7. data/lib/generators/graphql/templates/base_field.erb +2 -0
  8. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  9. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  10. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  11. data/lib/generators/graphql/templates/base_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  13. data/lib/generators/graphql/templates/base_union.erb +2 -0
  14. data/lib/generators/graphql/templates/enum.erb +2 -0
  15. data/lib/generators/graphql/templates/graphql_controller.erb +14 -10
  16. data/lib/generators/graphql/templates/interface.erb +2 -0
  17. data/lib/generators/graphql/templates/loader.erb +2 -0
  18. data/lib/generators/graphql/templates/mutation.erb +2 -0
  19. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  20. data/lib/generators/graphql/templates/object.erb +2 -0
  21. data/lib/generators/graphql/templates/query_type.erb +2 -0
  22. data/lib/generators/graphql/templates/scalar.erb +2 -0
  23. data/lib/generators/graphql/templates/schema.erb +10 -0
  24. data/lib/generators/graphql/templates/union.erb +3 -1
  25. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  26. data/lib/graphql/analysis/ast/query_complexity.rb +178 -67
  27. data/lib/graphql/analysis/ast/visitor.rb +3 -3
  28. data/lib/graphql/analysis/ast.rb +12 -11
  29. data/lib/graphql/argument.rb +10 -38
  30. data/lib/graphql/backtrace/table.rb +10 -2
  31. data/lib/graphql/backtrace/tracer.rb +2 -1
  32. data/lib/graphql/base_type.rb +4 -0
  33. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  34. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +5 -9
  35. data/lib/graphql/define/assign_enum_value.rb +1 -1
  36. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  37. data/lib/graphql/define/assign_object_field.rb +3 -3
  38. data/lib/graphql/define/defined_object_proxy.rb +3 -0
  39. data/lib/graphql/define/instance_definable.rb +18 -108
  40. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  41. data/lib/graphql/directive.rb +8 -1
  42. data/lib/graphql/enum_type.rb +5 -71
  43. data/lib/graphql/execution/directive_checks.rb +2 -2
  44. data/lib/graphql/execution/errors.rb +2 -3
  45. data/lib/graphql/execution/execute.rb +1 -1
  46. data/lib/graphql/execution/instrumentation.rb +1 -1
  47. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  48. data/lib/graphql/execution/interpreter/arguments.rb +51 -0
  49. data/lib/graphql/execution/interpreter/arguments_cache.rb +79 -0
  50. data/lib/graphql/execution/interpreter/handles_raw_value.rb +25 -0
  51. data/lib/graphql/execution/interpreter/runtime.rb +227 -254
  52. data/lib/graphql/execution/interpreter.rb +34 -11
  53. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  54. data/lib/graphql/execution/lookahead.rb +39 -114
  55. data/lib/graphql/execution/multiplex.rb +14 -5
  56. data/lib/graphql/field.rb +14 -118
  57. data/lib/graphql/filter.rb +1 -1
  58. data/lib/graphql/function.rb +1 -30
  59. data/lib/graphql/input_object_type.rb +6 -24
  60. data/lib/graphql/integer_decoding_error.rb +17 -0
  61. data/lib/graphql/interface_type.rb +7 -23
  62. data/lib/graphql/internal_representation/scope.rb +2 -2
  63. data/lib/graphql/internal_representation/visit.rb +2 -2
  64. data/lib/graphql/introspection/base_object.rb +2 -5
  65. data/lib/graphql/introspection/directive_type.rb +1 -1
  66. data/lib/graphql/introspection/entry_points.rb +7 -7
  67. data/lib/graphql/introspection/field_type.rb +7 -3
  68. data/lib/graphql/introspection/input_value_type.rb +33 -9
  69. data/lib/graphql/introspection/introspection_query.rb +6 -92
  70. data/lib/graphql/introspection/schema_type.rb +4 -9
  71. data/lib/graphql/introspection/type_type.rb +11 -7
  72. data/lib/graphql/introspection.rb +96 -0
  73. data/lib/graphql/invalid_null_error.rb +18 -0
  74. data/lib/graphql/language/block_string.rb +24 -5
  75. data/lib/graphql/language/definition_slice.rb +21 -10
  76. data/lib/graphql/language/document_from_schema_definition.rb +89 -64
  77. data/lib/graphql/language/lexer.rb +7 -3
  78. data/lib/graphql/language/lexer.rl +7 -3
  79. data/lib/graphql/language/nodes.rb +52 -91
  80. data/lib/graphql/language/parser.rb +719 -717
  81. data/lib/graphql/language/parser.y +104 -98
  82. data/lib/graphql/language/printer.rb +1 -1
  83. data/lib/graphql/language/sanitized_printer.rb +222 -0
  84. data/lib/graphql/language/visitor.rb +2 -2
  85. data/lib/graphql/language.rb +2 -1
  86. data/lib/graphql/name_validator.rb +6 -7
  87. data/lib/graphql/non_null_type.rb +0 -10
  88. data/lib/graphql/object_type.rb +45 -56
  89. data/lib/graphql/pagination/active_record_relation_connection.rb +41 -0
  90. data/lib/graphql/pagination/array_connection.rb +77 -0
  91. data/lib/graphql/pagination/connection.rb +208 -0
  92. data/lib/graphql/pagination/connections.rb +145 -0
  93. data/lib/graphql/pagination/mongoid_relation_connection.rb +25 -0
  94. data/lib/graphql/pagination/relation_connection.rb +185 -0
  95. data/lib/graphql/pagination/sequel_dataset_connection.rb +28 -0
  96. data/lib/graphql/pagination.rb +6 -0
  97. data/lib/graphql/query/arguments.rb +4 -2
  98. data/lib/graphql/query/context.rb +36 -9
  99. data/lib/graphql/query/fingerprint.rb +26 -0
  100. data/lib/graphql/query/input_validation_result.rb +23 -6
  101. data/lib/graphql/query/literal_input.rb +30 -10
  102. data/lib/graphql/query/null_context.rb +5 -1
  103. data/lib/graphql/query/validation_pipeline.rb +4 -1
  104. data/lib/graphql/query/variable_validation_error.rb +1 -1
  105. data/lib/graphql/query/variables.rb +16 -7
  106. data/lib/graphql/query.rb +64 -15
  107. data/lib/graphql/rake_task/validate.rb +3 -0
  108. data/lib/graphql/rake_task.rb +9 -9
  109. data/lib/graphql/relay/array_connection.rb +10 -12
  110. data/lib/graphql/relay/base_connection.rb +23 -13
  111. data/lib/graphql/relay/connection_type.rb +2 -1
  112. data/lib/graphql/relay/edge_type.rb +1 -0
  113. data/lib/graphql/relay/edges_instrumentation.rb +1 -1
  114. data/lib/graphql/relay/mutation.rb +1 -86
  115. data/lib/graphql/relay/node.rb +2 -2
  116. data/lib/graphql/relay/range_add.rb +14 -5
  117. data/lib/graphql/relay/relation_connection.rb +8 -10
  118. data/lib/graphql/scalar_type.rb +15 -59
  119. data/lib/graphql/schema/argument.rb +113 -11
  120. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  121. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +1 -1
  122. data/lib/graphql/schema/build_from_definition/resolve_map.rb +13 -5
  123. data/lib/graphql/schema/build_from_definition.rb +212 -190
  124. data/lib/graphql/schema/built_in_types.rb +5 -5
  125. data/lib/graphql/schema/default_type_error.rb +2 -0
  126. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  127. data/lib/graphql/schema/directive/include.rb +1 -1
  128. data/lib/graphql/schema/directive/skip.rb +1 -1
  129. data/lib/graphql/schema/directive.rb +34 -3
  130. data/lib/graphql/schema/enum.rb +52 -4
  131. data/lib/graphql/schema/enum_value.rb +6 -1
  132. data/lib/graphql/schema/field/connection_extension.rb +44 -20
  133. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  134. data/lib/graphql/schema/field.rb +200 -129
  135. data/lib/graphql/schema/find_inherited_value.rb +13 -0
  136. data/lib/graphql/schema/finder.rb +13 -11
  137. data/lib/graphql/schema/input_object.rb +131 -22
  138. data/lib/graphql/schema/interface.rb +26 -8
  139. data/lib/graphql/schema/introspection_system.rb +108 -37
  140. data/lib/graphql/schema/late_bound_type.rb +3 -2
  141. data/lib/graphql/schema/list.rb +47 -0
  142. data/lib/graphql/schema/loader.rb +134 -96
  143. data/lib/graphql/schema/member/base_dsl_methods.rb +29 -12
  144. data/lib/graphql/schema/member/build_type.rb +19 -5
  145. data/lib/graphql/schema/member/cached_graphql_definition.rb +5 -0
  146. data/lib/graphql/schema/member/has_arguments.rb +105 -5
  147. data/lib/graphql/schema/member/has_ast_node.rb +20 -0
  148. data/lib/graphql/schema/member/has_fields.rb +20 -10
  149. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  150. data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
  151. data/lib/graphql/schema/member/validates_input.rb +33 -0
  152. data/lib/graphql/schema/member.rb +6 -0
  153. data/lib/graphql/schema/mutation.rb +5 -1
  154. data/lib/graphql/schema/non_null.rb +30 -0
  155. data/lib/graphql/schema/object.rb +65 -12
  156. data/lib/graphql/schema/possible_types.rb +9 -4
  157. data/lib/graphql/schema/printer.rb +0 -15
  158. data/lib/graphql/schema/relay_classic_mutation.rb +5 -3
  159. data/lib/graphql/schema/resolver/has_payload_type.rb +5 -2
  160. data/lib/graphql/schema/resolver.rb +26 -18
  161. data/lib/graphql/schema/scalar.rb +27 -3
  162. data/lib/graphql/schema/subscription.rb +8 -18
  163. data/lib/graphql/schema/timeout.rb +29 -15
  164. data/lib/graphql/schema/traversal.rb +1 -1
  165. data/lib/graphql/schema/type_expression.rb +21 -13
  166. data/lib/graphql/schema/type_membership.rb +2 -2
  167. data/lib/graphql/schema/union.rb +37 -3
  168. data/lib/graphql/schema/unique_within_type.rb +1 -2
  169. data/lib/graphql/schema/validation.rb +10 -2
  170. data/lib/graphql/schema/warden.rb +115 -29
  171. data/lib/graphql/schema.rb +903 -195
  172. data/lib/graphql/static_validation/all_rules.rb +1 -0
  173. data/lib/graphql/static_validation/base_visitor.rb +10 -6
  174. data/lib/graphql/static_validation/literal_validator.rb +52 -27
  175. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +43 -83
  176. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +17 -5
  177. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +33 -25
  178. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
  179. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  180. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  181. data/lib/graphql/static_validation/rules/fields_will_merge.rb +29 -21
  182. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  183. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  184. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  185. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
  186. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -5
  187. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +12 -13
  188. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -6
  189. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  190. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +5 -3
  191. data/lib/graphql/static_validation/type_stack.rb +2 -2
  192. data/lib/graphql/static_validation/validation_context.rb +1 -1
  193. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  194. data/lib/graphql/static_validation/validator.rb +30 -8
  195. data/lib/graphql/static_validation.rb +1 -0
  196. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +89 -19
  197. data/lib/graphql/subscriptions/broadcast_analyzer.rb +84 -0
  198. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +21 -0
  199. data/lib/graphql/subscriptions/event.rb +23 -5
  200. data/lib/graphql/subscriptions/instrumentation.rb +10 -5
  201. data/lib/graphql/subscriptions/serialize.rb +22 -4
  202. data/lib/graphql/subscriptions/subscription_root.rb +15 -5
  203. data/lib/graphql/subscriptions.rb +108 -35
  204. data/lib/graphql/tracing/active_support_notifications_tracing.rb +14 -10
  205. data/lib/graphql/tracing/appoptics_tracing.rb +171 -0
  206. data/lib/graphql/tracing/appsignal_tracing.rb +8 -0
  207. data/lib/graphql/tracing/data_dog_tracing.rb +8 -0
  208. data/lib/graphql/tracing/new_relic_tracing.rb +9 -12
  209. data/lib/graphql/tracing/platform_tracing.rb +53 -9
  210. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  211. data/lib/graphql/tracing/prometheus_tracing.rb +8 -0
  212. data/lib/graphql/tracing/scout_tracing.rb +19 -0
  213. data/lib/graphql/tracing/skylight_tracing.rb +8 -0
  214. data/lib/graphql/tracing/statsd_tracing.rb +42 -0
  215. data/lib/graphql/tracing.rb +14 -34
  216. data/lib/graphql/types/big_int.rb +1 -1
  217. data/lib/graphql/types/int.rb +9 -2
  218. data/lib/graphql/types/iso_8601_date.rb +3 -3
  219. data/lib/graphql/types/iso_8601_date_time.rb +25 -10
  220. data/lib/graphql/types/relay/base_connection.rb +11 -7
  221. data/lib/graphql/types/relay/base_edge.rb +2 -1
  222. data/lib/graphql/types/string.rb +7 -1
  223. data/lib/graphql/unauthorized_error.rb +1 -1
  224. data/lib/graphql/union_type.rb +13 -28
  225. data/lib/graphql/unresolved_type_error.rb +2 -2
  226. data/lib/graphql/version.rb +1 -1
  227. data/lib/graphql.rb +31 -6
  228. data/readme.md +1 -1
  229. metadata +34 -9
  230. data/lib/graphql/literal_validation_error.rb +0 -6
@@ -34,7 +34,7 @@ module GraphQL
34
34
  # Remove this child from the result value
35
35
  # (used for null propagation and skip)
36
36
  # @api private
37
- def delete(child_ctx)
37
+ def delete_child(child_ctx)
38
38
  @value.delete(child_ctx.key)
39
39
  end
40
40
 
@@ -143,9 +143,9 @@ module GraphQL
143
143
  # Make a new context which delegates key lookup to `values`
144
144
  # @param query [GraphQL::Query] the query who owns this context
145
145
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
146
- def initialize(query:, values: , object:)
146
+ def initialize(query:, schema: query.schema, values:, object:)
147
147
  @query = query
148
- @schema = query.schema
148
+ @schema = schema
149
149
  @provided_values = values || {}
150
150
  @object = object
151
151
  # Namespaced storage, where user-provided values are in `nil` namespace:
@@ -167,8 +167,10 @@ module GraphQL
167
167
  # @api private
168
168
  attr_accessor :scoped_context
169
169
 
170
- def_delegators :@provided_values, :[]=
171
- def_delegators :to_h, :fetch, :dig
170
+ def []=(key, value)
171
+ @provided_values[key] = value
172
+ end
173
+
172
174
  def_delegators :@query, :trace, :interpreter?
173
175
 
174
176
  # @!method []=(key, value)
@@ -180,6 +182,34 @@ module GraphQL
180
182
  @provided_values[key]
181
183
  end
182
184
 
185
+ def delete(key)
186
+ if @scoped_context.key?(key)
187
+ @scoped_context.delete(key)
188
+ else
189
+ @provided_values.delete(key)
190
+ end
191
+ end
192
+
193
+ UNSPECIFIED_FETCH_DEFAULT = Object.new
194
+
195
+ def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT)
196
+ if @scoped_context.key?(key)
197
+ @scoped_context[key]
198
+ elsif @provided_values.key?(key)
199
+ @provided_values[key]
200
+ elsif default != UNSPECIFIED_FETCH_DEFAULT
201
+ default
202
+ elsif block_given?
203
+ yield(self, key)
204
+ else
205
+ raise KeyError.new(key: key)
206
+ end
207
+ end
208
+
209
+ def dig(key, *other_keys)
210
+ @scoped_context.key?(key) ? @scoped_context.dig(key, *other_keys) : @provided_values.dig(key, *other_keys)
211
+ end
212
+
183
213
  def to_h
184
214
  @provided_values.merge(@scoped_context)
185
215
  end
@@ -293,7 +323,7 @@ module GraphQL
293
323
  end
294
324
  when GraphQL::Execution::Execute::SKIP
295
325
  @parent.skipped = true
296
- @parent.delete(self)
326
+ @parent.delete_child(self)
297
327
  else
298
328
  @value = new_value
299
329
  end
@@ -334,6 +364,3 @@ module GraphQL
334
364
  end
335
365
  end
336
366
  end
337
-
338
-
339
- GraphQL::Schema::Context = GraphQL::Query::Context
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha2'
4
+
5
+ module GraphQL
6
+ class Query
7
+ # @api private
8
+ # @see Query#query_fingerprint
9
+ # @see Query#variables_fingerprint
10
+ # @see Query#fingerprint
11
+ module Fingerprint
12
+ # Make an obfuscated hash of the given string (either a query string or variables JSON)
13
+ # @param string [String]
14
+ # @return [String] A normalized, opaque hash
15
+ def self.generate(input_str)
16
+ # Implemented to be:
17
+ # - Short (and uniform) length
18
+ # - Stable
19
+ # - Irreversibly Opaque (don't want to leak variable values)
20
+ # - URL-friendly
21
+ bytes = Digest::SHA256.digest(input_str)
22
+ Base64.urlsafe_encode64(bytes)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -4,22 +4,39 @@ module GraphQL
4
4
  class InputValidationResult
5
5
  attr_accessor :problems
6
6
 
7
+ def initialize(valid: true, problems: nil)
8
+ @valid = valid
9
+ @problems = problems
10
+ end
11
+
7
12
  def valid?
8
- @problems.nil?
13
+ @valid
9
14
  end
10
15
 
11
- def add_problem(explanation, path = nil)
16
+ def add_problem(explanation, path = nil, extensions: nil, message: nil)
12
17
  @problems ||= []
13
- @problems.push({ "path" => path || [], "explanation" => explanation })
18
+ @valid = false
19
+ problem = { "path" => path || [], "explanation" => explanation }
20
+ if extensions
21
+ problem["extensions"] = extensions
22
+ end
23
+ if message
24
+ problem["message"] = message
25
+ end
26
+ @problems.push(problem)
14
27
  end
15
28
 
16
29
  def merge_result!(path, inner_result)
17
30
  return if inner_result.valid?
18
31
 
19
- inner_result.problems.each do |p|
20
- item_path = [path, *p["path"]]
21
- add_problem(p["explanation"], item_path)
32
+ if inner_result.problems
33
+ inner_result.problems.each do |p|
34
+ item_path = [path, *p["path"]]
35
+ add_problem(p["explanation"], item_path, message: p["message"], extensions: p["extensions"])
36
+ end
22
37
  end
38
+ # It could have been explicitly set on inner_result (if it had no problems)
39
+ @valid = false
23
40
  end
24
41
  end
25
42
  end
@@ -12,8 +12,8 @@ module GraphQL
12
12
  when Language::Nodes::VariableIdentifier
13
13
  variables[ast_node.name]
14
14
  else
15
- case type
16
- when GraphQL::ScalarType
15
+ case type.kind.name
16
+ when "SCALAR"
17
17
  # TODO smell
18
18
  # This gets used for plain values during subscriber.trigger
19
19
  if variables
@@ -21,7 +21,7 @@ module GraphQL
21
21
  else
22
22
  type.coerce_isolated_input(ast_node)
23
23
  end
24
- when GraphQL::EnumType
24
+ when "ENUM"
25
25
  # TODO smell
26
26
  # This gets used for plain values sometimes
27
27
  v = ast_node.is_a?(GraphQL::Language::Nodes::Enum) ? ast_node.name : ast_node
@@ -30,18 +30,20 @@ module GraphQL
30
30
  else
31
31
  type.coerce_isolated_input(v)
32
32
  end
33
- when GraphQL::NonNullType
33
+ when "NON_NULL"
34
34
  LiteralInput.coerce(type.of_type, ast_node, variables)
35
- when GraphQL::ListType
35
+ when "LIST"
36
36
  if ast_node.is_a?(Array)
37
37
  ast_node.map { |element_ast| LiteralInput.coerce(type.of_type, element_ast, variables) }
38
38
  else
39
39
  [LiteralInput.coerce(type.of_type, ast_node, variables)]
40
40
  end
41
- when GraphQL::InputObjectType
41
+ when "INPUT_OBJECT"
42
42
  # TODO smell: handling AST vs handling plain Ruby
43
43
  next_args = ast_node.is_a?(Hash) ? ast_node : ast_node.arguments
44
44
  from_arguments(next_args, type, variables)
45
+ else
46
+ raise "Invariant: unexpected type to coerce to: #{type}"
45
47
  end
46
48
  end
47
49
  end
@@ -78,7 +80,10 @@ module GraphQL
78
80
  if (!value_is_a_variable || (value_is_a_variable && variables.key?(arg_value.name)))
79
81
 
80
82
  value = coerce(arg_defn.type, arg_value, variables)
81
- value = arg_defn.prepare(value, context)
83
+ # Legacy `prepare` application
84
+ if arg_defn.is_a?(GraphQL::Argument)
85
+ value = arg_defn.prepare(value, context)
86
+ end
82
87
 
83
88
  if value.is_a?(GraphQL::ExecutionError)
84
89
  value.ast_node = ast_arg
@@ -98,7 +103,9 @@ module GraphQL
98
103
  defaults_used << arg_name
99
104
  # `context` isn't present when pre-calculating defaults
100
105
  if context
101
- value = arg_defn.prepare(value, context)
106
+ if arg_defn.is_a?(GraphQL::Argument)
107
+ value = arg_defn.prepare(value, context)
108
+ end
102
109
  if value.is_a?(GraphQL::ExecutionError)
103
110
  value.ast_node = ast_arg
104
111
  raise value
@@ -108,8 +115,21 @@ module GraphQL
108
115
  end
109
116
  end
110
117
 
111
- result = argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used)
112
- result.prepare
118
+ if argument_owner.is_a?(Class) || argument_owner.is_a?(GraphQL::Schema::Field)
119
+ # A Schema::InputObject, Schema::GraphQL::Field, Schema::Directive, logic from Query::Arguments#to_kwargs
120
+ ruby_kwargs = {}
121
+ values_hash.each do |key, value|
122
+ ruby_kwargs[Schema::Member::BuildType.underscore(key).to_sym] = value
123
+ end
124
+ if argument_owner.is_a?(Class) && argument_owner < GraphQL::Schema::InputObject
125
+ argument_owner.new(ruby_kwargs: ruby_kwargs, context: context, defaults_used: defaults_used)
126
+ else
127
+ ruby_kwargs
128
+ end
129
+ else
130
+ result = argument_owner.arguments_class.new(values_hash, context: context, defaults_used: defaults_used)
131
+ result.prepare
132
+ end
113
133
  end
114
134
  end
115
135
  end
@@ -23,6 +23,10 @@ module GraphQL
23
23
 
24
24
  def [](key); end
25
25
 
26
+ def interpreter?
27
+ false
28
+ end
29
+
26
30
  class << self
27
31
  extend Forwardable
28
32
 
@@ -32,7 +36,7 @@ module GraphQL
32
36
  @instance = self.new
33
37
  end
34
38
 
35
- def_delegators :instance, :query, :schema, :warden
39
+ def_delegators :instance, :query, :schema, :warden, :interpreter?
36
40
  end
37
41
  end
38
42
  end
@@ -72,7 +72,7 @@ module GraphQL
72
72
  elsif @operation_name_error
73
73
  @validation_errors << @operation_name_error
74
74
  else
75
- validation_result = @schema.static_validator.validate(@query, validate: @validate)
75
+ validation_result = @schema.static_validator.validate(@query, validate: @validate, timeout: @schema.validate_timeout)
76
76
  @validation_errors.concat(validation_result[:errors])
77
77
  @internal_representation = validation_result[:irep]
78
78
 
@@ -90,6 +90,9 @@ module GraphQL
90
90
  end
91
91
 
92
92
  @valid = @validation_errors.empty?
93
+ rescue SystemStackError => err
94
+ @valid = false
95
+ @schema.query_stack_error(@query, err)
93
96
  end
94
97
 
95
98
  # If there are max_* values, add them,
@@ -8,7 +8,7 @@ module GraphQL
8
8
  @value = value
9
9
  @validation_result = validation_result
10
10
 
11
- msg = "Variable #{variable_ast.name} of type #{type} was provided invalid value"
11
+ msg = "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value"
12
12
 
13
13
  if problem_fields.any?
14
14
  msg += " for #{problem_fields.join(", ")}"
@@ -21,7 +21,7 @@ module GraphQL
21
21
  # - First, use the value provided at runtime
22
22
  # - Then, fall back to the default value from the query string
23
23
  # If it's still nil, raise an error if it's required.
24
- variable_type = schema.type_from_ast(ast_variable.type)
24
+ variable_type = schema.type_from_ast(ast_variable.type, context: ctx)
25
25
  if variable_type.nil?
26
26
  # Pass -- it will get handled by a validator
27
27
  else
@@ -29,21 +29,30 @@ module GraphQL
29
29
  default_value = ast_variable.default_value
30
30
  provided_value = @provided_variables[variable_name]
31
31
  value_was_provided = @provided_variables.key?(variable_name)
32
-
33
32
  begin
34
33
  validation_result = variable_type.validate_input(provided_value, ctx)
35
34
  if validation_result.valid?
36
35
  if value_was_provided
37
36
  # Add the variable if a value was provided
38
- memo[variable_name] = schema.error_handler.with_error_handling(context) do
39
- variable_type.coerce_input(provided_value, ctx)
37
+ memo[variable_name] = if ctx.interpreter?
38
+ provided_value
39
+ elsif provided_value.nil?
40
+ nil
41
+ else
42
+ schema.error_handler.with_error_handling(context) do
43
+ variable_type.coerce_input(provided_value, ctx)
44
+ end
40
45
  end
41
46
  elsif default_value != nil
42
- # Add the variable if it wasn't provided but it has a default value (including `null`)
43
- memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
47
+ memo[variable_name] = if ctx.interpreter?
48
+ default_value
49
+ else
50
+ # Add the variable if it wasn't provided but it has a default value (including `null`)
51
+ GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
52
+ end
44
53
  end
45
54
  end
46
- rescue GraphQL::CoercionError, GraphQL::ExecutionError => ex
55
+ rescue GraphQL::ExecutionError => ex
47
56
  # TODO: This should really include the path to the problematic node in the variable value
48
57
  # like InputValidationResults generated by validate_non_null_input but unfortunately we don't
49
58
  # have this information available in the coerce_input call chain. Note this path is the path
data/lib/graphql/query.rb CHANGED
@@ -3,6 +3,7 @@ require "graphql/query/arguments"
3
3
  require "graphql/query/arguments_cache"
4
4
  require "graphql/query/context"
5
5
  require "graphql/query/executor"
6
+ require "graphql/query/fingerprint"
6
7
  require "graphql/query/literal_input"
7
8
  require "graphql/query/null_context"
8
9
  require "graphql/query/result"
@@ -78,19 +79,25 @@ module GraphQL
78
79
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
79
80
  # @param except [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns truthy
80
81
  # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
81
- def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil)
82
+ def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
82
83
  # Even if `variables: nil` is passed, use an empty hash for simpler logic
83
84
  variables ||= {}
85
+
86
+ # Use the `.graphql_definition` here which will return legacy types instead of classes
87
+ if schema.is_a?(Class) && !schema.interpreter?
88
+ schema = schema.graphql_definition
89
+ end
84
90
  @schema = schema
91
+ @interpreter = @schema.interpreter?
85
92
  @filter = schema.default_filter.merge(except: except, only: only)
86
93
  @context = schema.context_class.new(query: self, object: root_value, values: context)
94
+ @warden = warden
87
95
  @subscription_topic = subscription_topic
88
96
  @root_value = root_value
89
97
  @fragments = nil
90
98
  @operations = nil
91
99
  @validate = validate
92
- # TODO: remove support for global tracers
93
- @tracers = schema.tracers + GraphQL::Tracing.tracers + (context ? context.fetch(:tracers, []) : [])
100
+ @tracers = schema.tracers + (context ? context.fetch(:tracers, []) : [])
94
101
  # Support `ctx[:backtrace] = true` for wrapping backtraces
95
102
  if context && context[:backtrace] && !@tracers.include?(GraphQL::Backtrace::Tracer)
96
103
  @tracers << GraphQL::Backtrace::Tracer
@@ -100,7 +107,7 @@ module GraphQL
100
107
  if variables.is_a?(String)
101
108
  raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables."
102
109
  else
103
- @provided_variables = variables
110
+ @provided_variables = variables || {}
104
111
  end
105
112
 
106
113
  @query_string = query_string || query
@@ -118,8 +125,6 @@ module GraphQL
118
125
  end
119
126
  end
120
127
 
121
- @arguments_cache = ArgumentsCache.build(self)
122
-
123
128
  # Trying to execute a document
124
129
  # with no operations returns an empty hash
125
130
  @ast_variables = []
@@ -134,7 +139,6 @@ module GraphQL
134
139
  @executed = false
135
140
 
136
141
  # TODO add a general way to define schema-level filters
137
- # TODO also add this to schema dumps
138
142
  if @schema.respond_to?(:visible?)
139
143
  merge_filters(only: @schema.method(:visible?))
140
144
  end
@@ -145,7 +149,9 @@ module GraphQL
145
149
  @query_string ||= (document ? document.to_query_string : nil)
146
150
  end
147
151
 
148
- def_delegators :@schema, :interpreter?
152
+ def interpreter?
153
+ @interpreter
154
+ end
149
155
 
150
156
  def subscription_update?
151
157
  @subscription_topic && subscription?
@@ -157,7 +163,7 @@ module GraphQL
157
163
  @lookahead ||= begin
158
164
  ast_node = selected_operation
159
165
  root_type = warden.root_type_for_operation(ast_node.operation_type || "query")
160
- root_type = root_type.metadata[:type_class] || raise("Invariant: `lookahead` only works with class-based types")
166
+ root_type = root_type.type_class || raise("Invariant: `lookahead` only works with class-based types")
161
167
  GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [ast_node])
162
168
  end
163
169
  end
@@ -238,10 +244,54 @@ module GraphQL
238
244
  end
239
245
 
240
246
  # Node-level cache for calculating arguments. Used during execution and query analysis.
241
- # @api private
242
- # @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables
243
- def arguments_for(irep_or_ast_node, definition)
244
- @arguments_cache[irep_or_ast_node][definition]
247
+ # @param ast_node [GraphQL::Language::Nodes::AbstractNode]
248
+ # @param definition [GraphQL::Schema::Field]
249
+ # @param parent_object [GraphQL::Schema::Object]
250
+ # @return Hash{Symbol => Object}
251
+ def arguments_for(ast_node, definition, parent_object: nil)
252
+ if interpreter?
253
+ @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self)
254
+ @arguments_cache.fetch(ast_node, definition, parent_object)
255
+ else
256
+ @arguments_cache ||= ArgumentsCache.build(self)
257
+ @arguments_cache[ast_node][definition]
258
+ end
259
+ end
260
+
261
+ # A version of the given query string, with:
262
+ # - Variables inlined to the query
263
+ # - Strings replaced with `<REDACTED>`
264
+ # @return [String, nil] Returns nil if the query is invalid.
265
+ def sanitized_query_string(inline_variables: true)
266
+ with_prepared_ast {
267
+ GraphQL::Language::SanitizedPrinter.new(self, inline_variables: inline_variables).sanitized_query_string
268
+ }
269
+ end
270
+
271
+ # This contains a few components:
272
+ #
273
+ # - The selected operation name (or `anonymous`)
274
+ # - The fingerprint of the query string
275
+ # - The number of given variables (for readability)
276
+ # - The fingerprint of the given variables
277
+ #
278
+ # This fingerprint can be used to track runs of the same operation-variables combination over time.
279
+ #
280
+ # @see operation_fingerprint
281
+ # @see variables_fingerprint
282
+ # @return [String] An opaque hash identifying this operation-variables combination
283
+ def fingerprint
284
+ @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}"
285
+ end
286
+
287
+ # @return [String] An opaque hash for identifying this query's given query string and selected operation
288
+ def operation_fingerprint
289
+ @operation_fingerprint ||= "#{selected_operation_name || "anonymous"}/#{Fingerprint.generate(query_string)}"
290
+ end
291
+
292
+ # @return [String] An opaque hash for identifying this query's given a variable values (not including defaults)
293
+ def variables_fingerprint
294
+ @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}"
245
295
  end
246
296
 
247
297
  def validation_pipeline
@@ -321,8 +371,7 @@ module GraphQL
321
371
 
322
372
  def prepare_ast
323
373
  @prepared_ast = true
324
- @warden = GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context)
325
-
374
+ @warden ||= GraphQL::Schema::Warden.new(@filter, schema: @schema, context: @context)
326
375
  parse_error = nil
327
376
  @document ||= begin
328
377
  if query_string
@@ -7,6 +7,9 @@ module GraphQL
7
7
  desc "Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org"
8
8
  task "graphql:pro:validate", [:gem_version] do |t, args|
9
9
  version = args[:gem_version]
10
+ if version.nil?
11
+ raise ArgumentError, "A specific version is required, eg `rake graphql:pro:validate[1.12.0]`"
12
+ end
10
13
  check = "\e[32m✓\e[0m"
11
14
  ex = "\e[31m✘\e[0m"
12
15
  puts "Validating graphql-pro v#{version}"
@@ -76,15 +76,7 @@ module GraphQL
76
76
  # Set the parameters of this task by passing keyword arguments
77
77
  # or assigning attributes inside the block
78
78
  def initialize(options = {})
79
- default_dependencies = if Rake::Task.task_defined?("environment")
80
- [:environment]
81
- else
82
- []
83
- end
84
-
85
- all_options = DEFAULT_OPTIONS
86
- .merge(dependencies: default_dependencies)
87
- .merge(options)
79
+ all_options = DEFAULT_OPTIONS.merge(options)
88
80
  all_options.each do |k, v|
89
81
  self.public_send("#{k}=", v)
90
82
  end
@@ -117,18 +109,26 @@ module GraphQL
117
109
  File.join(@directory, @json_outfile)
118
110
  end
119
111
 
112
+ def load_rails_environment_if_defined
113
+ if Rake::Task.task_defined?('environment')
114
+ Rake::Task['environment'].invoke
115
+ end
116
+ end
117
+
120
118
  # Use the Rake DSL to add tasks
121
119
  def define_task
122
120
  namespace(@namespace) do
123
121
  namespace("schema") do
124
122
  desc("Dump the schema to IDL in #{idl_path}")
125
123
  task :idl => @dependencies do
124
+ load_rails_environment_if_defined
126
125
  write_outfile(:to_definition, idl_path)
127
126
  puts "Schema IDL dumped into #{idl_path}"
128
127
  end
129
128
 
130
129
  desc("Dump the schema to JSON in #{json_path}")
131
130
  task :json => @dependencies do
131
+ load_rails_environment_if_defined
132
132
  write_outfile(:to_json, json_path)
133
133
  puts "Schema JSON dumped into #{json_path}"
134
134
  end
@@ -31,24 +31,22 @@ module GraphQL
31
31
  end
32
32
  end
33
33
 
34
- private
35
-
36
34
  def first
37
- return @first if defined? @first
38
-
39
- @first = get_limited_arg(:first)
40
- @first = max_page_size if @first && max_page_size && @first > max_page_size
41
- @first
35
+ @first ||= begin
36
+ capped = limit_pagination_argument(arguments[:first], max_page_size)
37
+ if capped.nil? && last.nil?
38
+ capped = max_page_size
39
+ end
40
+ capped
41
+ end
42
42
  end
43
43
 
44
44
  def last
45
- return @last if defined? @last
46
-
47
- @last = get_limited_arg(:last)
48
- @last = max_page_size if @last && max_page_size && @last > max_page_size
49
- @last
45
+ @last ||= limit_pagination_argument(arguments[:last], max_page_size)
50
46
  end
51
47
 
48
+ private
49
+
52
50
  # apply first / last limit results
53
51
  def paged_nodes
54
52
  @paged_nodes ||= begin
@@ -25,6 +25,10 @@ module GraphQL
25
25
  # @param nodes [Object] A collection of nodes (eg, Array, AR::Relation)
26
26
  # @return [subclass of BaseConnection] a connection Class for wrapping `nodes`
27
27
  def connection_for_nodes(nodes)
28
+ # If it's a new-style connection object, it's already ready to go
29
+ if nodes.is_a?(GraphQL::Pagination::Connection)
30
+ return nodes
31
+ end
28
32
  # Check for class _names_ because classes can be redefined in Rails development
29
33
  nodes.class.ancestors.each do |ancestor|
30
34
  conn_impl = CONNECTION_IMPLEMENTATIONS[ancestor.name]
@@ -70,14 +74,18 @@ module GraphQL
70
74
 
71
75
  def decode(data)
72
76
  @encoder.decode(data, nonce: true)
73
- rescue ArgumentError
74
- raise GraphQL::ExecutionError, "Invalid cursor: #{data.inspect}"
75
77
  end
76
78
 
77
79
  # The value passed as `first:`, if there was one. Negative numbers become `0`.
78
80
  # @return [Integer, nil]
79
81
  def first
80
- @first ||= get_limited_arg(:first)
82
+ @first ||= begin
83
+ capped = limit_pagination_argument(arguments[:first], max_page_size)
84
+ if capped.nil? && last.nil?
85
+ capped = max_page_size
86
+ end
87
+ capped
88
+ end
81
89
  end
82
90
 
83
91
  # The value passed as `after:`, if there was one
@@ -89,7 +97,7 @@ module GraphQL
89
97
  # The value passed as `last:`, if there was one. Negative numbers become `0`.
90
98
  # @return [Integer, nil]
91
99
  def last
92
- @last ||= get_limited_arg(:last)
100
+ @last ||= limit_pagination_argument(arguments[:last], max_page_size)
93
101
  end
94
102
 
95
103
  # The value passed as `before:`, if there was one
@@ -148,16 +156,18 @@ module GraphQL
148
156
 
149
157
  private
150
158
 
151
- # Return a sanitized `arguments[arg_name]` (don't allow negatives)
152
- def get_limited_arg(arg_name)
153
- arg_value = arguments[arg_name]
154
- if arg_value.nil?
155
- arg_value
156
- elsif arg_value < 0
157
- 0
158
- else
159
- arg_value
159
+ # @param argument [nil, Integer] `first` or `last`, as provided by the client
160
+ # @param max_page_size [nil, Integer]
161
+ # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size`
162
+ def limit_pagination_argument(argument, max_page_size)
163
+ if argument
164
+ if argument < 0
165
+ argument = 0
166
+ elsif max_page_size && argument > max_page_size
167
+ argument = max_page_size
168
+ end
160
169
  end
170
+ argument
161
171
  end
162
172
 
163
173
  def paged_nodes
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module Relay
4
+ # @api deprecated
4
5
  module ConnectionType
5
6
  class << self
6
7
  # @return [Boolean] If true, connection types get a `nodes` shortcut field
@@ -12,7 +13,7 @@ module GraphQL
12
13
  self.default_nodes_field = false
13
14
  self.bidirectional_pagination = false
14
15
 
15
- # Create a connection which exposes edges of this type
16
+ # @api deprecated
16
17
  def self.create_type(wrapped_type, edge_type: nil, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block)
17
18
  custom_edge_class = edge_class
18
19