graphql 1.5.5 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -2
  3. data/lib/graphql/analysis/query_complexity.rb +0 -1
  4. data/lib/graphql/argument.rb +43 -3
  5. data/lib/graphql/base_type.rb +50 -7
  6. data/lib/graphql/boolean_type.rb +2 -2
  7. data/lib/graphql/compatibility/execution_specification.rb +1 -1
  8. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  9. data/lib/graphql/compatibility/lazy_execution_specification.rb +1 -1
  10. data/lib/graphql/enum_type.rb +35 -27
  11. data/lib/graphql/execution/execute.rb +3 -5
  12. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -2
  13. data/lib/graphql/float_type.rb +2 -2
  14. data/lib/graphql/function.rb +5 -0
  15. data/lib/graphql/id_type.rb +2 -2
  16. data/lib/graphql/input_object_type.rb +46 -36
  17. data/lib/graphql/int_type.rb +2 -2
  18. data/lib/graphql/introspection/input_value_type.rb +1 -1
  19. data/lib/graphql/list_type.rb +17 -17
  20. data/lib/graphql/non_null_type.rb +6 -11
  21. data/lib/graphql/query.rb +2 -2
  22. data/lib/graphql/query/literal_input.rb +15 -8
  23. data/lib/graphql/query/null_context.rb +29 -0
  24. data/lib/graphql/query/serial_execution/value_resolution.rb +2 -4
  25. data/lib/graphql/query/variables.rb +9 -7
  26. data/lib/graphql/relay/mutation.rb +6 -7
  27. data/lib/graphql/scalar_type.rb +54 -19
  28. data/lib/graphql/schema.rb +21 -5
  29. data/lib/graphql/schema/build_from_definition.rb +3 -1
  30. data/lib/graphql/schema/catchall_middleware.rb +1 -1
  31. data/lib/graphql/schema/default_type_error.rb +1 -1
  32. data/lib/graphql/schema/loader.rb +2 -2
  33. data/lib/graphql/schema/printer.rb +1 -1
  34. data/lib/graphql/schema/validation.rb +1 -2
  35. data/lib/graphql/static_validation/arguments_validator.rb +1 -1
  36. data/lib/graphql/static_validation/literal_validator.rb +5 -4
  37. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +1 -1
  38. data/lib/graphql/static_validation/validation_context.rb +1 -1
  39. data/lib/graphql/string_encoding_error.rb +10 -0
  40. data/lib/graphql/string_type.rb +8 -3
  41. data/lib/graphql/version.rb +1 -1
  42. data/spec/graphql/argument_spec.rb +13 -0
  43. data/spec/graphql/base_type_spec.rb +1 -1
  44. data/spec/graphql/boolean_type_spec.rb +1 -1
  45. data/spec/graphql/enum_type_spec.rb +11 -11
  46. data/spec/graphql/field_spec.rb +1 -1
  47. data/spec/graphql/float_type_spec.rb +4 -4
  48. data/spec/graphql/function_spec.rb +5 -4
  49. data/spec/graphql/input_object_type_spec.rb +28 -20
  50. data/spec/graphql/int_type_spec.rb +4 -4
  51. data/spec/graphql/language/lexer_spec.rb +0 -1
  52. data/spec/graphql/list_type_spec.rb +3 -3
  53. data/spec/graphql/query/literal_input_spec.rb +51 -0
  54. data/spec/graphql/query/variables_spec.rb +8 -4
  55. data/spec/graphql/relay/array_connection_spec.rb +1 -1
  56. data/spec/graphql/relay/page_info_spec.rb +1 -1
  57. data/spec/graphql/relay/relation_connection_spec.rb +3 -3
  58. data/spec/graphql/scalar_type_spec.rb +8 -8
  59. data/spec/graphql/schema/build_from_definition_spec.rb +2 -2
  60. data/spec/graphql/schema/loader_spec.rb +4 -4
  61. data/spec/graphql/string_type_spec.rb +33 -6
  62. data/spec/spec_helper.rb +0 -11
  63. data/spec/support/dummy/schema.rb +1 -1
  64. metadata +6 -2
@@ -3,7 +3,7 @@ GraphQL::INT_TYPE = GraphQL::ScalarType.define do
3
3
  name "Int"
4
4
  description "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1."
5
5
 
6
- coerce_input ->(value) { value.is_a?(Numeric) ? value.to_i : nil }
7
- coerce_result ->(value) { value.to_i }
6
+ coerce_input ->(value, _ctx) { value.is_a?(Numeric) ? value.to_i : nil }
7
+ coerce_result ->(value, _ctx) { value.to_i }
8
8
  default_scalar true
9
9
  end
@@ -14,7 +14,7 @@ GraphQL::Introspection::InputValueType = GraphQL::ObjectType.define do
14
14
  if value.nil?
15
15
  'null'
16
16
  else
17
- coerced_default_value = obj.type.coerce_result(value)
17
+ coerced_default_value = obj.type.coerce_result(value, ctx)
18
18
  if obj.type.unwrap.is_a?(GraphQL::EnumType)
19
19
  coerced_default_value
20
20
  else
@@ -40,11 +40,25 @@ module GraphQL
40
40
  "[#{of_type.to_s}]"
41
41
  end
42
42
 
43
- def validate_non_null_input(value, warden)
43
+ def coerce_result(value, ctx = nil)
44
+ if ctx.nil?
45
+ warn_deprecated_coerce("coerce_isolated_result")
46
+ ctx = GraphQL::Query::NullContext
47
+ end
48
+ Array(value).map { |item| item.nil? ? nil : of_type.coerce_result(item, ctx) }
49
+ end
50
+
51
+ private
52
+
53
+ def coerce_non_null_input(value, ctx)
54
+ Array(value).map { |item| of_type.coerce_input(item, ctx) }
55
+ end
56
+
57
+ def validate_non_null_input(value, ctx)
44
58
  result = GraphQL::Query::InputValidationResult.new
45
59
 
46
- ensure_array(value).each_with_index do |item, index|
47
- item_result = of_type.validate_input(item, warden)
60
+ Array(value).each_with_index do |item, index|
61
+ item_result = of_type.validate_input(item, ctx)
48
62
  if !item_result.valid?
49
63
  result.merge_result!(index, item_result)
50
64
  end
@@ -52,19 +66,5 @@ module GraphQL
52
66
 
53
67
  result
54
68
  end
55
-
56
- def coerce_non_null_input(value)
57
- ensure_array(value).map { |item| of_type.coerce_input(item) }
58
- end
59
-
60
- def coerce_result(value)
61
- ensure_array(value).map { |item| item.nil? ? nil : of_type.coerce_result(item) }
62
- end
63
-
64
- private
65
-
66
- def ensure_array(value)
67
- value.is_a?(Array) ? value : [value]
68
- end
69
69
  end
70
70
  end
@@ -30,6 +30,7 @@ module GraphQL
30
30
  #
31
31
  class NonNullType < GraphQL::BaseType
32
32
  include GraphQL::BaseType::ModifiesAnotherType
33
+ extend Forwardable
33
34
 
34
35
  attr_reader :of_type
35
36
  def initialize(of_type:)
@@ -37,27 +38,21 @@ module GraphQL
37
38
  @of_type = of_type
38
39
  end
39
40
 
40
- def valid_input?(value, warden)
41
- validate_input(value, warden).valid?
41
+ def valid_input?(value, ctx)
42
+ validate_input(value, ctx).valid?
42
43
  end
43
44
 
44
- def validate_input(value, warden)
45
+ def validate_input(value, ctx)
45
46
  if value.nil?
46
47
  result = GraphQL::Query::InputValidationResult.new
47
48
  result.add_problem("Expected value to not be null")
48
49
  result
49
50
  else
50
- of_type.validate_input(value, warden)
51
+ of_type.validate_input(value, ctx)
51
52
  end
52
53
  end
53
54
 
54
- def coerce_input(value)
55
- of_type.coerce_input(value)
56
- end
57
-
58
- def coerce_result(value)
59
- of_type.coerce_result(value)
60
- end
55
+ def_delegators :@of_type, :coerce_input, :coerce_result
61
56
 
62
57
  def kind
63
58
  GraphQL::TypeKinds::NON_NULL
@@ -4,6 +4,7 @@ require "graphql/query/arguments_cache"
4
4
  require "graphql/query/context"
5
5
  require "graphql/query/executor"
6
6
  require "graphql/query/literal_input"
7
+ require "graphql/query/null_context"
7
8
  require "graphql/query/serial_execution"
8
9
  require "graphql/query/variables"
9
10
  require "graphql/query/input_validation_result"
@@ -145,8 +146,7 @@ module GraphQL
145
146
  def variables
146
147
  @variables ||= begin
147
148
  vars = GraphQL::Query::Variables.new(
148
- @schema,
149
- @warden,
149
+ @context,
150
150
  @ast_variables,
151
151
  @provided_variables,
152
152
  )
@@ -14,9 +14,9 @@ module GraphQL
14
14
  else
15
15
  case type
16
16
  when GraphQL::ScalarType
17
- type.coerce_input(ast_node)
17
+ type.coerce_input(ast_node, variables.context)
18
18
  when GraphQL::EnumType
19
- type.coerce_input(ast_node.name)
19
+ type.coerce_input(ast_node.name, variables.context)
20
20
  when GraphQL::NonNullType
21
21
  LiteralInput.coerce(type.of_type, ast_node, variables)
22
22
  when GraphQL::ListType
@@ -49,14 +49,21 @@ module GraphQL
49
49
  # First, check the argument in the AST.
50
50
  # If the value is a variable,
51
51
  # only add a value if the variable is actually present.
52
- # Otherwise, coerce the value in the AST and add it.
52
+ # Otherwise, coerce the value in the AST, prepare the value and add it.
53
53
  if ast_arg
54
- if ast_arg.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
55
- if variables.key?(ast_arg.value.name)
56
- values_hash[ast_arg.name] = coerce(arg_defn.type, ast_arg.value, variables)
54
+ value_is_a_variable = ast_arg.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
55
+
56
+ if (!value_is_a_variable || (value_is_a_variable && variables.key?(ast_arg.value.name)))
57
+
58
+ value = coerce(arg_defn.type, ast_arg.value, variables)
59
+ value = arg_defn.prepare(value)
60
+
61
+ if value.is_a?(GraphQL::ExecutionError)
62
+ value.ast_node = ast_arg
63
+ raise value
57
64
  end
58
- else
59
- values_hash[ast_arg.name] = coerce(arg_defn.type, ast_arg.value, variables)
65
+
66
+ values_hash[ast_arg.name] = value
60
67
  end
61
68
  end
62
69
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Query
4
+ # This object can be `ctx` in places where there is no query
5
+ class NullContext
6
+ attr_reader :schema, :query, :warden
7
+
8
+ def initialize
9
+ @query = nil
10
+ @schema = GraphQL::Schema.new
11
+ @warden = GraphQL::Schema::Warden.new(
12
+ GraphQL::Schema::NullMask,
13
+ context: self,
14
+ schema: @schema,
15
+ )
16
+ end
17
+
18
+ class << self
19
+ extend Forwardable
20
+
21
+ def instance
22
+ @instance = self.new
23
+ end
24
+
25
+ def_delegators :instance, :query, :schema, :warden
26
+ end
27
+ end
28
+ end
29
+ end
@@ -16,10 +16,8 @@ module GraphQL
16
16
  end
17
17
  else
18
18
  case field_type.kind
19
- when GraphQL::TypeKinds::SCALAR
20
- field_type.coerce_result(value)
21
- when GraphQL::TypeKinds::ENUM
22
- field_type.coerce_result(value, query_ctx.query.warden)
19
+ when GraphQL::TypeKinds::SCALAR, GraphQL::TypeKinds::ENUM
20
+ field_type.coerce_result(value, query_ctx)
23
21
  when GraphQL::TypeKinds::LIST
24
22
  wrapped_type = field_type.of_type
25
23
  result = []
@@ -8,9 +8,11 @@ module GraphQL
8
8
  # @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
9
9
  attr_reader :errors
10
10
 
11
- def initialize(schema, warden, ast_variables, provided_variables)
12
- @schema = schema
13
- @warden = warden
11
+ attr_reader :context
12
+
13
+ def initialize(ctx, ast_variables, provided_variables)
14
+ schema = ctx.schema
15
+ @context = ctx
14
16
  @provided_variables = provided_variables
15
17
  @errors = []
16
18
  @storage = ast_variables.each_with_object({}) do |ast_variable, memo|
@@ -18,7 +20,7 @@ module GraphQL
18
20
  # - First, use the value provided at runtime
19
21
  # - Then, fall back to the default value from the query string
20
22
  # If it's still nil, raise an error if it's required.
21
- variable_type = @schema.type_from_ast(ast_variable.type)
23
+ variable_type = schema.type_from_ast(ast_variable.type)
22
24
  if variable_type.nil?
23
25
  # Pass -- it will get handled by a validator
24
26
  else
@@ -27,16 +29,16 @@ module GraphQL
27
29
  provided_value = @provided_variables[variable_name]
28
30
  value_was_provided = @provided_variables.key?(variable_name)
29
31
 
30
- validation_result = variable_type.validate_input(provided_value, @warden)
32
+ validation_result = variable_type.validate_input(provided_value, ctx)
31
33
  if !validation_result.valid?
32
34
  # This finds variables that were required but not provided
33
35
  @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
34
36
  elsif value_was_provided
35
37
  # Add the variable if a value was provided
36
- memo[variable_name] = variable_type.coerce_input(provided_value)
38
+ memo[variable_name] = variable_type.coerce_input(provided_value, ctx)
37
39
  elsif default_value
38
40
  # Add the variable if it wasn't provided but it has a default value (including `null`)
39
- memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, {})
41
+ memo[variable_name] = GraphQL::Query::LiteralInput.coerce(variable_type, default_value, self)
40
42
  end
41
43
  end
42
44
  end
@@ -163,18 +163,17 @@ module GraphQL
163
163
  def input_type
164
164
  @input_type ||= begin
165
165
  relay_mutation = self
166
- GraphQL::InputObjectType.define do
166
+ input_object_type = GraphQL::InputObjectType.define do
167
167
  name("#{relay_mutation.name}Input")
168
168
  description("Autogenerated input type of #{relay_mutation.name}")
169
169
  input_field :clientMutationId, types.String, "A unique identifier for the client performing the mutation."
170
- relay_mutation.arguments.each do |input_field_name, field_obj|
171
- kwargs = {}
172
- kwargs[:default_value] = field_obj.default_value if field_obj.default_value?
173
- kwargs[:as] = field_obj.as
174
- input_field(input_field_name, field_obj.type, field_obj.description, **kwargs)
175
- end
176
170
  mutation(relay_mutation)
177
171
  end
172
+ input_fields.each do |name, arg|
173
+ input_object_type.arguments[name] = arg
174
+ end
175
+
176
+ input_object_type
178
177
  end
179
178
  end
180
179
 
@@ -30,49 +30,84 @@ module GraphQL
30
30
  # name "Time"
31
31
  # description "Time since epoch in seconds"
32
32
  #
33
- # coerce_input ->(value) { Time.at(Float(value)) }
34
- # coerce_result ->(value) { value.to_f }
33
+ # coerce_input ->(value, ctx) { Time.at(Float(value)) }
34
+ # coerce_result ->(value, ctx) { value.to_f }
35
35
  # end
36
36
  #
37
37
  class ScalarType < GraphQL::BaseType
38
38
  accepts_definitions :coerce, :coerce_input, :coerce_result
39
39
  ensure_defined :coerce_non_null_input, :coerce_result
40
40
 
41
+ module NoOpCoerce
42
+ def self.call(val, ctx)
43
+ val
44
+ end
45
+ end
46
+
47
+ def initialize
48
+ super
49
+ self.coerce = NoOpCoerce
50
+ end
51
+
41
52
  def coerce=(proc)
42
53
  self.coerce_input = proc
43
54
  self.coerce_result = proc
44
55
  end
45
56
 
46
- def validate_non_null_input(value, warden)
47
- result = Query::InputValidationResult.new
48
- if coerce_non_null_input(value).nil?
49
- result.add_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{name}")
57
+ def coerce_input=(coerce_input_fn)
58
+ if !coerce_input_fn.nil?
59
+ @coerce_input_proc = ensure_two_arg(coerce_input_fn, :coerce_input)
50
60
  end
51
- result
52
61
  end
53
62
 
54
- def coerce_non_null_input(value)
55
- @coerce_input_proc.call(value)
63
+ def coerce_result(value, ctx = nil)
64
+ if ctx.nil?
65
+ warn_deprecated_coerce("coerce_isolated_result")
66
+ ctx = GraphQL::Query::NullContext
67
+ end
68
+ @coerce_result_proc.call(value, ctx)
56
69
  end
57
70
 
58
- def coerce_input=(proc)
59
- if !proc.nil?
60
- @coerce_input_proc = proc
71
+ def coerce_result=(coerce_result_fn)
72
+ if !coerce_result_fn.nil?
73
+ @coerce_result_proc = ensure_two_arg(coerce_result_fn, :coerce_result)
61
74
  end
62
75
  end
63
76
 
64
- def coerce_result(value)
65
- @coerce_result_proc ? @coerce_result_proc.call(value) : value
77
+ def kind
78
+ GraphQL::TypeKinds::SCALAR
66
79
  end
67
80
 
68
- def coerce_result=(proc)
69
- if !proc.nil?
70
- @coerce_result_proc = proc
81
+ private
82
+
83
+ def get_arity(callable)
84
+ case callable
85
+ when Proc
86
+ callable.arity
87
+ else
88
+ callable.method(:call).arity
71
89
  end
72
90
  end
73
91
 
74
- def kind
75
- GraphQL::TypeKinds::SCALAR
92
+ def ensure_two_arg(callable, method_name)
93
+ if get_arity(callable) == 1
94
+ warn("Scalar coerce functions receive two values (`val` and `ctx`), one-argument functions are deprecated (see #{name}.#{method_name}).")
95
+ ->(val, ctx) { callable.call(val) }
96
+ else
97
+ callable
98
+ end
99
+ end
100
+
101
+ def coerce_non_null_input(value, ctx)
102
+ @coerce_input_proc.call(value, ctx)
103
+ end
104
+
105
+ def validate_non_null_input(value, ctx)
106
+ result = Query::InputValidationResult.new
107
+ if coerce_non_null_input(value, ctx).nil?
108
+ result.add_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{name}")
109
+ end
110
+ result
76
111
  end
77
112
  end
78
113
  end
@@ -58,6 +58,7 @@ module GraphQL
58
58
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
59
59
  :max_depth, :max_complexity,
60
60
  :orphan_types, :resolve_type, :type_error, :parse_error,
61
+ :raise_definition_error,
61
62
  :object_from_id, :id_from_object,
62
63
  :default_mask,
63
64
  :cursor_encoder,
@@ -74,7 +75,8 @@ module GraphQL
74
75
  :max_depth, :max_complexity,
75
76
  :orphan_types, :directives,
76
77
  :query_analyzers, :instrumenters, :lazy_methods,
77
- :cursor_encoder
78
+ :cursor_encoder,
79
+ :raise_definition_error
78
80
 
79
81
  # @return [MiddlewareChain] MiddlewareChain which is applied to fields during execution
80
82
  attr_accessor :middleware
@@ -96,6 +98,7 @@ module GraphQL
96
98
  attr_reader :static_validator, :object_from_id_proc, :id_from_object_proc, :resolve_type_proc
97
99
 
98
100
  def initialize
101
+ @definition_error = nil
99
102
  @orphan_types = []
100
103
  @directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m }
101
104
  @static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
@@ -175,6 +178,15 @@ module GraphQL
175
178
  validation_error = Validation.validate(self)
176
179
  validation_error && raise(NotImplementedError, validation_error)
177
180
  build_instrumented_field_map
181
+ @definition_error = nil
182
+ nil
183
+ rescue StandardError => err
184
+ if @raise_definition_error
185
+ raise
186
+ else
187
+ # Raise this error _later_ to avoid messing with Rails constant loading
188
+ @definition_error = err
189
+ end
178
190
  nil
179
191
  end
180
192
 
@@ -195,12 +207,16 @@ module GraphQL
195
207
  @types ||= build_types_map
196
208
  end
197
209
 
198
- # Execute a query on itself.
199
- # See {Query#initialize} for arguments.
210
+ # Execute a query on itself. Raises an error if the schema definition is invalid.
211
+ # @see {Query#initialize} for arguments.
200
212
  # @return [Hash] query result, ready to be serialized as JSON
201
213
  def execute(*args)
202
- query_obj = GraphQL::Query.new(self, *args)
203
- query_obj.result
214
+ if @definition_error
215
+ raise @definition_error
216
+ else
217
+ query_obj = GraphQL::Query.new(self, *args)
218
+ query_obj.result
219
+ end
204
220
  end
205
221
 
206
222
  # Resolve field named `field_name` for type `parent_type`.