Skip to content

Commit 2362ef3

Browse files
committed
VariableDefinition Directives: hide behind experimental flag
Replicates graphql/graphql-js@3fdf240
1 parent f2567ab commit 2362ef3

File tree

7 files changed

+83
-18
lines changed

7 files changed

+83
-18
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ a query language for APIs created by Facebook.
1313

1414
The current version 1.0.0rc2 of GraphQL-core-next is up-to-date with GraphQL.js
1515
version 14.0.0rc2. All parts of the API are covered by an extensive test
16-
suite of currently 1549 unit tests.
16+
suite of currently 1551 unit tests.
1717

1818

1919
## Documentation

graphql/language/lexer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,16 @@ class Lexer:
123123

124124
def __init__(self, source: Source,
125125
no_location=False,
126-
experimental_fragment_variables=False) -> None:
126+
experimental_fragment_variables=False,
127+
experimental_variable_definition_directives=False) -> None:
127128
"""Given a Source object, this returns a Lexer for that source."""
128129
self.source = source
129130
self.token = self.last_token = Token(TokenKind.SOF, 0, 0, 0, 0)
130131
self.line, self.line_start = 1, 0
131132
self.no_location = no_location
132133
self.experimental_fragment_variables = experimental_fragment_variables
134+
self.experimental_variable_definition_directives = \
135+
experimental_variable_definition_directives
133136

134137
def advance(self):
135138
self.last_token = self.token

graphql/language/parser.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,46 @@
2828
SourceType = Union[Source, str]
2929

3030

31-
def parse(source: SourceType,
32-
no_location=False,
33-
experimental_fragment_variables=False) -> DocumentNode:
31+
def parse(source: SourceType, no_location=False,
32+
experimental_fragment_variables=False,
33+
experimental_variable_definition_directives=False) -> DocumentNode:
3434
"""Given a GraphQL source, parse it into a Document.
3535
3636
Throws GraphQLError if a syntax error is encountered.
3737
3838
By default, the parser creates AST nodes that know the location
3939
in the source that they correspond to. The `no_location` option
4040
disables that behavior for performance or testing.
41+
42+
Experimental features:
43+
44+
If `experimental_fragment_variables` is set to True, the parser will
45+
understand and parse variable definitions contained in a fragment
46+
definition. They'll be represented in the `variable_definitions` field
47+
of the `FragmentDefinitionNode`.
48+
49+
The syntax is identical to normal, query-defined variables. For example:
50+
51+
fragment A($var: Boolean = false) on T {
52+
...
53+
}
54+
55+
If `experimental_variable_definition_directives` is set to True, the parser
56+
understands directives on variable definitions:
57+
58+
query Foo($var: String = "abc" @variable_definition_directive) {
59+
...
60+
}
4161
"""
4262
if isinstance(source, str):
4363
source = Source(source)
4464
elif not isinstance(source, Source):
4565
raise TypeError(f'Must provide Source. Received: {source!r}')
4666
lexer = Lexer(
4767
source, no_location=no_location,
48-
experimental_fragment_variables=experimental_fragment_variables)
68+
experimental_fragment_variables=experimental_fragment_variables,
69+
experimental_variable_definition_directives # noqa
70+
=experimental_variable_definition_directives)
4971
return parse_document(lexer)
5072

5173

@@ -171,12 +193,21 @@ def parse_variable_definitions(lexer: Lexer) -> List[VariableDefinitionNode]:
171193
def parse_variable_definition(lexer: Lexer) -> VariableDefinitionNode:
172194
"""VariableDefinition: Variable: Type DefaultValue? Directives[Const]?"""
173195
start = lexer.token
196+
if lexer.experimental_variable_definition_directives:
197+
return VariableDefinitionNode(
198+
variable=parse_variable(lexer),
199+
type=expect(lexer, TokenKind.COLON)
200+
and parse_type_reference(lexer),
201+
default_value=parse_value_literal(lexer, True)
202+
if skip(lexer, TokenKind.EQUALS) else None,
203+
directives=parse_directives(lexer, True),
204+
loc=loc(lexer, start))
174205
return VariableDefinitionNode(
175206
variable=parse_variable(lexer),
176-
type=expect(lexer, TokenKind.COLON) and parse_type_reference(lexer),
207+
type=expect(lexer, TokenKind.COLON) and parse_type_reference(
208+
lexer),
177209
default_value=parse_value_literal(lexer, True)
178210
if skip(lexer, TokenKind.EQUALS) else None,
179-
directives=parse_directives(lexer, True),
180211
loc=loc(lexer, start))
181212

182213

graphql/language/printer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def leave_operation_definition(self, node, *_args):
5252
def leave_variable_definition(self, node, *_args):
5353
return (f"{node.variable}: {node.type}"
5454
f"{wrap(' = ', node.default_value)}"
55-
f"{wrap(' ', ' '.join(node.directives))}")
55+
f"{wrap(' ', join(node.directives, ' '))}")
5656

5757
def leave_selection_set(self, node, *_args):
5858
return block(node.selections)

tests/language/test_parser.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ def parses_constant_default_values():
8383
'Unexpected $', (1, 37))
8484

8585
def parses_variable_definition_directives():
86-
parse('query Foo($x: Boolean = false @bar) { field }')
86+
parse('query Foo($x: Boolean = false @bar) { field }',
87+
experimental_variable_definition_directives=True)
8788

8889
def does_not_accept_fragments_named_on():
8990
assert_syntax_error(

tests/language/test_printer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def correctly_prints_mutation_operation_without_name():
4040

4141
def correctly_prints_query_operation_with_artifacts():
4242
query_ast_with_artifacts = parse(
43-
'query ($foo: TestType) @testDirective { id, name }')
43+
'query ($foo: TestType) @testDirective { id, name }',
44+
experimental_variable_definition_directives=True)
4445
assert print_ast(query_ast_with_artifacts) == dedent("""
4546
query ($foo: TestType) @testDirective {
4647
id
@@ -51,7 +52,8 @@ def correctly_prints_query_operation_with_artifacts():
5152
def correcty_prints_query_operation_with_variable_directive():
5253
query_ast_with_variable_directive = parse(
5354
'query ($foo: TestType = {a: 123}'
54-
' @testDirective(if: true) @test) { id }')
55+
' @testDirective(if: true) @test) { id }',
56+
experimental_variable_definition_directives=True)
5557
assert print_ast(query_ast_with_variable_directive) == dedent("""
5658
query ($foo: TestType = {a: 123} @testDirective(if: true) @test) {
5759
id

tests/validation/test_known_directives.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from functools import partial
22

3+
from graphql.language import parse
34
from graphql.utilities import build_schema
4-
from graphql.validation import KnownDirectivesRule
5+
from graphql.validation import validate, KnownDirectivesRule
56
from graphql.validation.rules.known_directives import (
67
unknown_directive_message, misplaced_directive_message)
78

89
from .harness import (
9-
expect_fails_rule, expect_passes_rule, expect_sdl_errors_from_rule)
10+
expect_fails_rule, expect_passes_rule, expect_sdl_errors_from_rule,
11+
test_schema)
1012

1113

1214
expect_sdl_errors = partial(
@@ -98,7 +100,7 @@ def with_many_unknown_directives():
98100

99101
def with_well_placed_directives():
100102
expect_passes_rule(KnownDirectivesRule, """
101-
query Foo($var: Boolean @onVariableDefinition) @onQuery {
103+
query Foo($var: Boolean) @onQuery {
102104
name @include(if: $var)
103105
...Frag @include(if: true)
104106
skippedField @skip(if: true)
@@ -110,9 +112,21 @@ def with_well_placed_directives():
110112
}
111113
""")
112114

115+
def with_well_placed_variable_definition_directives():
116+
# Need to parse with experimental flag
117+
query_string = """
118+
query Foo($var: Boolean @onVariableDefinition) {
119+
name
120+
}
121+
"""
122+
errors = validate(test_schema, parse(
123+
query_string, experimental_variable_definition_directives=True),
124+
[KnownDirectivesRule])
125+
assert errors == [], 'Should validate'
126+
113127
def with_misplaced_directives():
114128
expect_fails_rule(KnownDirectivesRule, """
115-
query Foo($var: Boolean @onField) @include(if: true) {
129+
query Foo($var: Boolean) @include(if: true) {
116130
name @onQuery @include(if: $var)
117131
...Frag @onQuery
118132
}
@@ -121,13 +135,27 @@ def with_misplaced_directives():
121135
someField
122136
}
123137
""", [
124-
misplaced_directive('onField', 'variable definition', 2, 37),
125-
misplaced_directive('include', 'query', 2, 47),
138+
misplaced_directive('include', 'query', 2, 38),
126139
misplaced_directive('onQuery', 'field', 3, 20),
127140
misplaced_directive('onQuery', 'fragment spread', 4, 23),
128141
misplaced_directive('onQuery', 'mutation', 7, 26),
129142
])
130143

144+
def with_misplaced_variable_definition_directives():
145+
# Need to parse with experimental flag
146+
query_string = """
147+
query Foo($var: Boolean @onField) {
148+
name
149+
}
150+
"""
151+
errors = validate(test_schema, parse(
152+
query_string, experimental_variable_definition_directives=True),
153+
[KnownDirectivesRule])
154+
expected_errors = [
155+
misplaced_directive('onField', 'variable definition', 2, 37)]
156+
assert len(errors) >= 1, 'Should not validate'
157+
assert errors == expected_errors
158+
131159
def describe_within_sdl():
132160

133161
def with_directive_defined_inside_sdl():

0 commit comments

Comments
 (0)