Skip to content

Commit bb3e055

Browse files
authored
Merge pull request rmosolgo#3837 from rmosolgo/repeatable-directives
Support repeatable directives
2 parents ae31305 + 75129f2 commit bb3e055

19 files changed

+936
-852
lines changed

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ ERR
6262
end
6363

6464
assert_dependency_version("Ragel", "7.0.0.9", "ragel -v")
65-
assert_dependency_version("Racc", "1.5.2", %|ruby -e "require 'racc'; puts Racc::VERSION"|)
65+
assert_dependency_version("Racc", "1.6.0", %|ruby -e "require 'racc'; puts Racc::VERSION"|)
6666

6767
`rm -f lib/graphql/language/parser.rb lib/graphql/language/lexer.rb `
6868
`racc lib/graphql/language/parser.y -o lib/graphql/language/parser.rb`

lib/graphql/language/document_from_schema_definition.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def build_input_object_node(input_object)
152152
def build_directive_node(directive)
153153
GraphQL::Language::Nodes::DirectiveDefinition.new(
154154
name: directive.graphql_name,
155+
repeatable: directive.repeatable?,
155156
arguments: build_argument_nodes(warden.arguments(directive)),
156157
locations: build_directive_location_nodes(directive.locations),
157158
description: directive.description,

lib/graphql/language/lexer.rb

Lines changed: 50 additions & 25 deletions
Large diffs are not rendered by default.

lib/graphql/language/lexer.rl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
ENUM = 'enum';
2828
INPUT = 'input';
2929
DIRECTIVE = 'directive';
30+
REPEATABLE = 'repeatable';
3031
LCURLY = '{';
3132
RCURLY = '}';
3233
LPAREN = '(';
@@ -85,6 +86,7 @@
8586
ENUM => { emit(:ENUM, ts, te, meta) };
8687
INPUT => { emit(:INPUT, ts, te, meta) };
8788
DIRECTIVE => { emit(:DIRECTIVE, ts, te, meta) };
89+
REPEATABLE => { emit(:REPEATABLE, ts, te, meta, "repeatable") };
8890
RCURLY => { emit(:RCURLY, ts, te, meta, "}") };
8991
LCURLY => { emit(:LCURLY, ts, te, meta, "{") };
9092
RPAREN => { emit(:RPAREN, ts, te, meta, ")") };

lib/graphql/language/nodes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ class DirectiveLocation < NameOnlyNode
318318
class DirectiveDefinition < AbstractNode
319319
include DefinitionNode
320320
attr_reader :description
321-
scalar_methods :name
321+
scalar_methods :name, :repeatable
322322
children_methods(
323323
locations: Nodes::DirectiveLocation,
324324
arguments: Nodes::Argument,

lib/graphql/language/parser.rb

Lines changed: 829 additions & 816 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/graphql/language/parser.y

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ rule
147147
name_without_on:
148148
IDENTIFIER
149149
| FRAGMENT
150+
| REPEATABLE
150151
| TRUE
151152
| FALSE
152153
| operation_type
@@ -155,6 +156,7 @@ rule
155156
enum_name: /* any identifier, but not "true", "false" or "null" */
156157
IDENTIFIER
157158
| FRAGMENT
159+
| REPEATABLE
158160
| ON
159161
| operation_type
160162
| schema_keyword
@@ -422,10 +424,14 @@ rule
422424
}
423425

424426
directive_definition:
425-
description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt ON directive_locations {
426-
result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[6], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
427+
description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt directive_repeatable_opt ON directive_locations {
428+
result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[7], repeatable: !!val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
427429
}
428430

431+
directive_repeatable_opt:
432+
/* nothing */
433+
| REPEATABLE
434+
429435
directive_locations:
430436
name { result = [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] }
431437
| directive_locations PIPE name { val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) }

lib/graphql/language/printer.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ def print_directive_definition(directive)
252252
out << print_arguments(directive.arguments)
253253
end
254254

255+
if directive.repeatable
256+
out << " repeatable"
257+
end
258+
255259
out << " on #{directive.locations.map(&:name).join(' | ')}"
256260
end
257261

lib/graphql/schema/build_from_definition.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ def build_directive(directive_definition, type_resolver)
377377
Class.new(GraphQL::Schema::Directive) do
378378
graphql_name(directive_definition.name)
379379
description(directive_definition.description)
380+
repeatable(directive_definition.repeatable)
380381
locations(*directive_definition.locations.map { |location| location.name.to_sym })
381382
ast_node(directive_definition)
382383
builder.build_arguments(self, directive_definition.arguments, type_resolver)

lib/graphql/schema/directive.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ def on_fragment?
101101
def on_operation?
102102
locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION)
103103
end
104+
105+
def repeatable?
106+
!!@repeatable
107+
end
108+
109+
def repeatable(new_value)
110+
@repeatable = new_value
111+
end
104112
end
105113

106114
# @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module]

lib/graphql/static_validation/base_visitor.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def on_field(node, parent)
110110
end
111111

112112
def on_directive(node, parent)
113-
directive_defn = @schema.directives[node.name]
113+
directive_defn = @context.schema_directives[node.name]
114114
@directive_definitions.push(directive_defn)
115115
super
116116
@directive_definitions.pop

lib/graphql/static_validation/rules/arguments_are_defined.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def parent_definition(parent)
5959
end
6060
end
6161
when GraphQL::Language::Nodes::Directive
62-
context.schema.directives[parent.name]
62+
context.schema_directives[parent.name]
6363
when GraphQL::Language::Nodes::Field
6464
context.field_definition
6565
else

lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module DirectivesAreInValidLocations
55
include GraphQL::Language
66

77
def on_directive(node, parent)
8-
validate_location(node, parent, context.schema.directives)
8+
validate_location(node, parent, context.schema_directives)
99
super
1010
end
1111

lib/graphql/static_validation/rules/required_arguments_are_present.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def on_field(node, _parent)
88
end
99

1010
def on_directive(node, _parent)
11-
directive_defn = context.schema.directives[node.name]
11+
directive_defn = context.schema_directives[node.name]
1212
assert_required_args(node, directive_defn)
1313
super
1414
end

lib/graphql/static_validation/rules/unique_directives_per_location.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def validate_directive_location(node)
4040
nodes: [used_directives[directive_name], ast_directive],
4141
directive: directive_name,
4242
))
43-
else
43+
elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?)
4444
used_directives[directive_name] = ast_directive
4545
end
4646
end

lib/graphql/static_validation/validation_context.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ def validate_literal(ast_value, type)
4444
def too_many_errors?
4545
@errors.length >= @max_errors
4646
end
47+
48+
def schema_directives
49+
@schema_directives ||= schema.directives
50+
end
4751
end
4852
end
4953
end

spec/graphql/language/parser_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@
108108

109109
it "is parsed for directive definitions" do
110110
document = subject.parse <<-GRAPHQL
111-
"thing description" directive @thing on FIELD
111+
"thing description" directive @thing repeatable on FIELD
112112
GRAPHQL
113113

114114
thing_defn = document.definitions[0]
115115
assert_equal "thing", thing_defn.name
116+
assert_equal true, thing_defn.repeatable
116117
assert_equal "thing description", thing_defn.description
117118
end
118119
end

spec/graphql/schema/build_from_definition_spec.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def assert_schema_and_compare_output(definition)
6868
6969
directive @greeting(pleasant: Boolean = true) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | UNION
7070
71-
directive @hashed on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
71+
directive @hashed repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
7272
7373
directive @language(is: String!) on ENUM_VALUE
7474
@@ -105,6 +105,10 @@ def assert_schema_and_compare_output(definition)
105105
parsed_schema.directives.values.each do |dir_class|
106106
assert dir_class < GraphQL::Schema::Directive
107107
end
108+
109+
assert_equal true, parsed_schema.directives["hashed"].repeatable?
110+
assert_equal false, parsed_schema.directives["deprecated"].repeatable?
111+
108112
assert_equal 1, hello_type.directives.size
109113
assert_instance_of parsed_schema.directives["greeting"], hello_type.directives.first
110114
assert_equal({ pleasant: true }, hello_type.directives.first.arguments.keyword_arguments)

spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
1616
directive @A on FIELD
1717
directive @B on FIELD
18+
directive @C repeatable on FIELD
1819
") }
1920

2021
describe "query with no directives" do
@@ -31,6 +32,20 @@
3132
end
3233
end
3334

35+
describe "query with repeatable directives" do
36+
let(:query_string) {"
37+
{
38+
type {
39+
field @C @C @C
40+
}
41+
}
42+
"}
43+
44+
it "passes rule" do
45+
assert_equal [], errors
46+
end
47+
end
48+
3449
describe "query with unique directives in different locations" do
3550
let(:query_string) {"
3651
{

0 commit comments

Comments
 (0)