Skip to content

Commit 4dd3ec7

Browse files
committed
Support custom validators on directives
1 parent 212eaf8 commit 4dd3ec7

File tree

3 files changed

+69
-3
lines changed

3 files changed

+69
-3
lines changed

lib/graphql/execution/interpreter/runtime.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,13 @@ def run_directive(method_name, object, directives, idx, &block)
826826
else
827827
dir_defn = @schema_directives.fetch(dir_node.name)
828828
raw_dir_args = arguments(nil, dir_defn, dir_node)
829+
if !raw_dir_args.is_a?(GraphQL::ExecutionError)
830+
begin
831+
dir_defn.validate!(raw_dir_args, context)
832+
rescue GraphQL::ExecutionError => err
833+
raw_dir_args = err
834+
end
835+
end
829836
dir_args = continue_value(
830837
raw_dir_args, # value
831838
nil, # field

lib/graphql/schema/directive.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Schema
99
class Directive < GraphQL::Schema::Member
1010
extend GraphQL::Schema::Member::HasArguments
1111
extend GraphQL::Schema::Member::HasArguments::HasDirectiveArguments
12+
extend GraphQL::Schema::Member::HasValidators
1213

1314
class << self
1415
# Directives aren't types, they don't have kinds.
@@ -75,6 +76,10 @@ def resolve_each(object, arguments, context)
7576
yield
7677
end
7778

79+
def validate!(arguments, context)
80+
Schema::Validator.validate!(validators, self, context, arguments)
81+
end
82+
7883
def on_field?
7984
locations.include?(FIELD)
8085
end
@@ -122,15 +127,19 @@ def initialize(owner, **arguments)
122127
# - lazy resolution
123128
# Probably, those won't be needed here, since these are configuration arguments,
124129
# not runtime arguments.
130+
context = Query::NullContext.instance
125131
self.class.all_argument_definitions.each do |arg_defn|
126132
value = arguments[arg_defn.keyword]
127-
arg_type = arg_defn.type
128-
result = arg_defn.type.validate_input(value, Query::NullContext.instance)
133+
result = arg_defn.type.validate_input(value, context)
129134
if !result.valid?
130135
raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}"
131136
end
132137
end
133-
@arguments = self.class.coerce_arguments(nil, arguments, Query::NullContext.instance)
138+
self.class.validate!(arguments, context)
139+
@arguments = self.class.coerce_arguments(nil, arguments, context)
140+
if @arguments.is_a?(GraphQL::ExecutionError)
141+
raise @arguments
142+
end
134143
end
135144

136145
def graphql_name

spec/graphql/schema/directive_spec.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,56 @@ def numbers
401401
assert_equal [["tag", { name: "t7"}], ["tag", { name: "t8"}]], enum_value.directives.map { |dir| [dir.graphql_name, dir.arguments.to_h] }
402402
end
403403

404+
describe "Custom validations on definition directives" do
405+
class DirectiveValidationSchema < GraphQL::Schema
406+
class ValidatedDirective < GraphQL::Schema::Directive
407+
locations OBJECT, FIELD
408+
argument :f, Float, required: false, validates: { numericality: { greater_than: 0 } }
409+
argument :s, String, required: false, validates: { format: { with: /^[a-z]{3}$/ } }
410+
validates required: { one_of: [:f, :s]}
411+
end
412+
413+
class Query < GraphQL::Schema::Object
414+
field :i, Int, fallback_value: 100
415+
end
416+
417+
query(Query)
418+
directive(ValidatedDirective)
419+
end
420+
421+
it "runs custom validation during execution" do
422+
f_err_res = DirectiveValidationSchema.execute("{ i @validatedDirective(f: -10) }")
423+
assert_equal [{"message" => "f must be greater than 0", "locations" => [{"line" => 1, "column" => 5}], "path" => ["i"]}], f_err_res["errors"]
424+
425+
s_err_res = DirectiveValidationSchema.execute("{ i @validatedDirective(s: \"wnrn\") }")
426+
assert_equal [{"message" => "s is invalid", "locations" => [{"line" => 1, "column" => 5}], "path" => ["i"]}], s_err_res["errors"]
427+
428+
f_s_err_res = DirectiveValidationSchema.execute("{ i @validatedDirective }")
429+
assert_equal [{"message" => "validatedDirective must include exactly one of the following arguments: f, s.", "locations" => [{"line" => 1, "column" => 5}], "path" => ["i"]}], f_s_err_res["errors"]
430+
end
431+
432+
it "runs custom validation during definition" do
433+
obj_type = Class.new(GraphQL::Schema::Object)
434+
directive_defn = DirectiveValidationSchema::ValidatedDirective
435+
obj_type.directive(directive_defn, f: 1)
436+
f_err = assert_raises GraphQL::Schema::Validator::ValidationFailedError do
437+
obj_type.directive(directive_defn, f: -1)
438+
end
439+
assert_equal "f must be greater than 0", f_err.message
440+
441+
obj_type.directive(directive_defn, s: "abc")
442+
s_err = assert_raises GraphQL::Schema::Validator::ValidationFailedError do
443+
obj_type.directive(directive_defn, s: "defg")
444+
end
445+
assert_equal "s is invalid", s_err.message
446+
447+
required_err = assert_raises GraphQL::Schema::Validator::ValidationFailedError do
448+
obj_type.directive(directive_defn)
449+
end
450+
assert_equal "validatedDirective must include exactly one of the following arguments: f, s.", required_err.message
451+
end
452+
end
453+
404454
describe "Validating schema directives" do
405455
def build_sdl(size:)
406456
<<~GRAPHQL

0 commit comments

Comments
 (0)