Skip to content

Commit e38defe

Browse files
authored
Merge pull request #5588 from rmosolgo/optimize-validation-rules
Optimize validation rule hot paths
2 parents 6d1e5fa + a7af5e4 commit e38defe

3 files changed

Lines changed: 55 additions & 12 deletions

File tree

lib/graphql/static_validation/rules/argument_names_are_unique.rb

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@ def on_directive(node, parent)
1616

1717
def validate_arguments(node)
1818
argument_defns = node.arguments
19-
if !argument_defns.empty?
20-
args_by_name = Hash.new { |h, k| h[k] = [] }
21-
argument_defns.each { |a| args_by_name[a.name] << a }
22-
args_by_name.each do |name, defns|
23-
if defns.size > 1
24-
add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: defns, name: name))
19+
if argument_defns.size > 1
20+
seen = {}
21+
argument_defns.each do |a|
22+
name = a.name
23+
if seen.key?(name)
24+
prev = seen[name]
25+
if prev.is_a?(Array)
26+
prev << a
27+
else
28+
seen[name] = [prev, a]
29+
end
30+
else
31+
seen[name] = a
32+
end
33+
end
34+
seen.each do |name, val|
35+
if val.is_a?(Array)
36+
add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: val, name: name))
2537
end
2638
end
2739
end

lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ def on_operation_definition(node, _parent)
2323

2424

2525
def validate_field_selections(ast_node, resolved_type)
26+
# Fast paths for the two most common cases:
27+
# 1. Leaf type with no selections (scalars, enums) — most fields
28+
# 2. Non-leaf type with selections (objects, interfaces)
29+
if resolved_type
30+
if ast_node.selections.empty?
31+
return true if resolved_type.kind.leaf?
32+
else
33+
return true unless resolved_type.kind.leaf?
34+
end
35+
end
36+
2637
msg = if resolved_type.nil?
2738
nil
2839
elsif resolved_type.kind.leaf?

lib/graphql/static_validation/rules/required_arguments_are_present.rb

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
module GraphQL
33
module StaticValidation
44
module RequiredArgumentsArePresent
5+
def initialize(*)
6+
super
7+
@required_args_cache = {}.compare_by_identity
8+
end
9+
510
def on_field(node, _parent)
611
assert_required_args(node, field_definition)
712
super
@@ -16,13 +21,28 @@ def on_directive(node, _parent)
1621
private
1722

1823
def assert_required_args(ast_node, defn)
19-
args = @context.query.types.arguments(defn)
20-
return if args.empty?
21-
present_argument_names = ast_node.arguments.map(&:name)
22-
required_argument_names = context.query.types.arguments(defn)
23-
.select { |a| a.type.kind.non_null? && !a.default_value? && context.query.types.argument(defn, a.name) }
24-
.map!(&:name)
24+
return unless defn
2525

26+
# Cache required argument names per definition to avoid re-iterating
27+
# arguments for the same definition across field instances
28+
if @required_args_cache.key?(defn)
29+
required_argument_names = @required_args_cache[defn]
30+
else
31+
args = @types.arguments(defn)
32+
required_argument_names = nil
33+
if !args.empty?
34+
args.each do |a|
35+
if a.type.kind.non_null? && !a.default_value? && @types.argument(defn, a.name)
36+
(required_argument_names ||= []) << a.graphql_name
37+
end
38+
end
39+
end
40+
@required_args_cache[defn] = required_argument_names
41+
end
42+
43+
return if required_argument_names.nil?
44+
45+
present_argument_names = ast_node.arguments.map(&:name)
2646
missing_names = required_argument_names - present_argument_names
2747
if !missing_names.empty?
2848
add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new(

0 commit comments

Comments
 (0)