Skip to content

Commit c31e679

Browse files
author
Robert Mosolgo
authored
Merge pull request rmosolgo#1602 from Tapjoy/fix/variable_coercion
Fix scalar coercion errors in variable values (including defaults) not being handled
2 parents bf29cde + 15c23c0 commit c31e679

File tree

5 files changed

+106
-3
lines changed

5 files changed

+106
-3
lines changed

lib/graphql/query/variables.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ def initialize(ctx, ast_variables, provided_variables)
3030
provided_value = @provided_variables[variable_name]
3131
value_was_provided = @provided_variables.key?(variable_name)
3232

33-
validation_result = variable_type.validate_input(provided_value, ctx)
33+
begin
34+
validation_result = variable_type.validate_input(provided_value, ctx)
35+
rescue GraphQL::CoercionError => ex
36+
validation_result = GraphQL::Query::InputValidationResult.new
37+
validation_result.add_problem(ex.message)
38+
end
39+
3440
if !validation_result.valid?
3541
# This finds variables that were required but not provided
3642
@errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)

lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@ def validate_default_value(node, context)
2020
type = context.schema.type_from_ast(node.type)
2121
if type.nil?
2222
# This is handled by another validator
23-
elsif !context.valid_literal?(value, type)
24-
context.errors << message("Default value for $#{node.name} doesn't match type #{type}", node, context: context)
23+
else
24+
begin
25+
valid = context.valid_literal?(value, type)
26+
rescue GraphQL::CoercionError => err
27+
error_message = err.message
28+
end
29+
30+
if !valid
31+
error_message ||= "Default value for $#{node.name} doesn't match type #{type}"
32+
context.errors << message(error_message, node, context: context)
33+
end
2534
end
2635
end
2736
end

spec/graphql/query/variables_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@
110110
assert_equal expected, variables.errors.first.message
111111
end
112112
end
113+
114+
describe "when provided input cannot be coerced" do
115+
let(:query_string) {%|
116+
query searchMyDairy (
117+
$time: Time
118+
) {
119+
searchDairy(expiresAfter: $time) {
120+
... on Cheese {
121+
flavor
122+
}
123+
}
124+
}
125+
|}
126+
let(:provided_variables) { { "time" => "a" } }
127+
128+
it "validates invalid input objects" do
129+
expected = "Variable time of type Time was provided invalid value"
130+
assert_equal expected, variables.errors.first.message
131+
end
132+
end
113133
end
114134

115135
describe "nullable variables" do

spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,56 @@
134134
end
135135
end
136136
end
137+
138+
describe "custom error messages" do
139+
let(:schema) {
140+
TimeType = GraphQL::ScalarType.define do
141+
name "Time"
142+
description "Time since epoch in seconds"
143+
144+
coerce_input ->(value, ctx) do
145+
begin
146+
Time.at(Float(value))
147+
rescue ArgumentError
148+
raise GraphQL::CoercionError, 'cannot coerce to Float'
149+
end
150+
end
151+
152+
coerce_result ->(value, ctx) { value.to_f }
153+
end
154+
155+
QueryType = GraphQL::ObjectType.define do
156+
name "Query"
157+
description "The query root of this schema"
158+
159+
field :time do
160+
type TimeType
161+
argument :value, !TimeType
162+
resolve ->(obj, args, ctx) { args[:value] }
163+
end
164+
end
165+
166+
GraphQL::Schema.define do
167+
query QueryType
168+
end
169+
}
170+
171+
let(:query_string) {%|
172+
query(
173+
$value: Time = "a"
174+
) {
175+
time(value: $value)
176+
}
177+
|}
178+
179+
it "sets error message from a CoercionError if raised" do
180+
assert_equal 1, errors.length
181+
182+
assert_includes errors, {
183+
"message"=> "cannot coerce to Float",
184+
"locations"=>[{"line"=>3, "column"=>9}],
185+
"fields"=>["query"]
186+
}
187+
end
188+
end
137189
end

spec/support/dummy/schema.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,21 @@ class NoSuchDairyError < StandardError; end
248248
end
249249
end
250250

251+
TimeType = GraphQL::ScalarType.define do
252+
name "Time"
253+
description "Time since epoch in seconds"
254+
255+
coerce_input ->(value, ctx) do
256+
begin
257+
Time.at(Float(value))
258+
rescue ArgumentError
259+
raise GraphQL::CoercionError, 'cannot coerce to Float'
260+
end
261+
end
262+
263+
coerce_result ->(value, ctx) { value.to_f }
264+
end
265+
251266
class FetchItem < GraphQL::Function
252267
attr_reader :type, :description, :arguments
253268

@@ -312,6 +327,7 @@ def call(obj, args, ctx)
312327
type !DairyProductUnion
313328
# This is a list just for testing 😬
314329
argument :product, types[DairyProductInputType], default_value: [{"source" => "SHEEP"}]
330+
argument :expiresAfter, TimeType
315331
resolve ->(t, args, c) {
316332
source = args["product"][0][:source] # String or Sym is ok
317333
products = CHEESES.values + MILKS.values

0 commit comments

Comments
 (0)