Skip to content

Commit bac1fae

Browse files
committed
Extract check for unique operation types into separate rule
Replicates graphql/graphql-js@9b7a8af
1 parent 94c6097 commit bac1fae

File tree

9 files changed

+372
-138
lines changed

9 files changed

+372
-138
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
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
1414

1515
The current version 1.0.1 of GraphQL-core-next is up-to-date with GraphQL.js version
16-
14.0.2. All parts of the API are covered by an extensive test suite of currently 1675
16+
14.0.2. All parts of the API are covered by an extensive test suite of currently 1683
1717
unit tests.
1818

1919

graphql/utilities/build_ast_schema.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,6 @@ def get_operation_types(
172172
for operation_type in schema.operation_types:
173173
type_name = operation_type.type.name.value
174174
operation = operation_type.operation
175-
if operation in op_types:
176-
raise TypeError(f"Must provide only one {operation.value} type in schema.")
177175
if type_name not in node_map:
178176
raise TypeError(
179177
f"Specified {operation.value} type '{type_name}'"

graphql/utilities/extend_schema.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -496,10 +496,6 @@ def resolve_type(type_ref: NamedTypeNode) -> GraphQLNamedType:
496496
if schema_def:
497497
for operation_type in schema_def.operation_types:
498498
operation = operation_type.operation
499-
if operation_types[operation]:
500-
raise TypeError(
501-
f"Must provide only one {operation.value} type in schema."
502-
)
503499
# Note: While this could make early assertions to get the correctly typed
504500
# values, that would throw immediately while type system validation with
505501
# `validate_schema()` will produce more actionable results.
@@ -510,10 +506,6 @@ def resolve_type(type_ref: NamedTypeNode) -> GraphQLNamedType:
510506
if schema_extension.operation_types:
511507
for operation_type in schema_extension.operation_types:
512508
operation = operation_type.operation
513-
if operation_types[operation]:
514-
raise TypeError(
515-
f"Must provide only one {operation.value} type in schema."
516-
)
517509
# Note: While this could make early assertions to get the correctly
518510
# typed values, that would throw immediately while type system
519511
# validation with `validate_schema()` will produce more actionable
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from typing import Dict, Optional, Union
2+
3+
from ...error import GraphQLError
4+
from ...language import (
5+
OperationTypeDefinitionNode,
6+
OperationType,
7+
SchemaDefinitionNode,
8+
SchemaExtensionNode,
9+
)
10+
from ...type import GraphQLObjectType
11+
from . import SDLValidationContext, SDLValidationRule
12+
13+
__all__ = [
14+
"UniqueOperationTypesRule",
15+
"duplicate_operation_type_message",
16+
"existed_operation_type_message",
17+
]
18+
19+
20+
def duplicate_operation_type_message(operation: str) -> str:
21+
return f"There can be only one '{operation}' type in schema."
22+
23+
24+
def existed_operation_type_message(operation: str) -> str:
25+
return (
26+
f"Type for '{operation}' already defined in the schema."
27+
" It cannot be redefined."
28+
)
29+
30+
31+
class UniqueOperationTypesRule(SDLValidationRule):
32+
"""Unique operation types
33+
34+
A GraphQL document is only valid if it has only one type per operation.
35+
"""
36+
37+
def __init__(self, context: SDLValidationContext) -> None:
38+
super().__init__(context)
39+
schema = context.schema
40+
self.defined_operation_types: Dict[
41+
OperationType, OperationTypeDefinitionNode
42+
] = {}
43+
self.existing_operation_types: Dict[
44+
OperationType, Optional[GraphQLObjectType]
45+
] = (
46+
{
47+
OperationType.QUERY: schema.query_type,
48+
OperationType.MUTATION: schema.mutation_type,
49+
OperationType.SUBSCRIPTION: schema.subscription_type,
50+
}
51+
if schema
52+
else {}
53+
)
54+
self.schema = schema
55+
56+
def check_operation_types(
57+
self, node: Union[SchemaDefinitionNode, SchemaExtensionNode], *_args
58+
):
59+
for operation_type in node.operation_types or []:
60+
operation = operation_type.operation
61+
already_defined_operation_type = self.defined_operation_types.get(operation)
62+
63+
if self.existing_operation_types.get(operation):
64+
self.report_error(
65+
GraphQLError(
66+
existed_operation_type_message(operation.value), operation_type
67+
)
68+
)
69+
elif already_defined_operation_type:
70+
self.report_error(
71+
GraphQLError(
72+
duplicate_operation_type_message(operation.value),
73+
[already_defined_operation_type, operation_type],
74+
)
75+
)
76+
else:
77+
self.defined_operation_types[operation] = operation_type
78+
return self.SKIP
79+
80+
enter_schema_definition = enter_schema_extension = check_operation_types

graphql/validation/rules/unique_type_names.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ def check_type_name(self, node: TypeDefinitionNode, *_args):
5252
self.known_type_names[type_name] = node.name
5353
return self.SKIP
5454

55-
enter_scalar_type_definition = check_type_name
56-
enter_object_type_definition = check_type_name
57-
enter_interface_type_definition = check_type_name
58-
enter_union_type_definition = check_type_name
59-
enter_enum_type_definition = check_type_name
60-
enter_input_object_type_definition = check_type_name
55+
enter_scalar_type_definition = enter_object_type_definition = check_type_name
56+
enter_interface_type_definition = enter_union_type_definition = check_type_name
57+
enter_enum_type_definition = enter_input_object_type_definition = check_type_name

graphql/validation/specified_rules.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282

8383
# Schema definition language:
8484
from .rules.lone_schema_definition import LoneSchemaDefinitionRule
85+
from .rules.unique_operation_types import UniqueOperationTypesRule
8586
from .rules.unique_type_names import UniqueTypeNamesRule
8687
from .rules.unique_directive_names import UniqueDirectiveNamesRule
8788
from .rules.known_argument_names import KnownArgumentNamesOnDirectivesRule
@@ -126,6 +127,7 @@
126127

127128
specified_sdl_rules: List[RuleType] = [
128129
LoneSchemaDefinitionRule,
130+
UniqueOperationTypesRule,
129131
UniqueTypeNamesRule,
130132
UniqueDirectiveNamesRule,
131133
KnownDirectivesRule,

tests/utilities/test_build_ast_schema.py

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -776,67 +776,6 @@ def allows_to_disable_sdl_validation():
776776

777777

778778
def describe_failures():
779-
def allows_only_a_single_query_type():
780-
sdl = """
781-
schema {
782-
query: Hello
783-
query: Yellow
784-
}
785-
786-
type Hello {
787-
bar: String
788-
}
789-
790-
type Yellow {
791-
isColor: Boolean
792-
}
793-
"""
794-
with raises(TypeError) as exc_info:
795-
build_schema(sdl)
796-
msg = str(exc_info.value)
797-
assert msg == "Must provide only one query type in schema."
798-
799-
def allows_only_a_single_mutation_type():
800-
sdl = """
801-
schema {
802-
query: Hello
803-
mutation: Hello
804-
mutation: Yellow
805-
}
806-
807-
type Hello {
808-
bar: String
809-
}
810-
811-
type Yellow {
812-
isColor: Boolean
813-
}
814-
"""
815-
with raises(TypeError) as exc_info:
816-
build_schema(sdl)
817-
msg = str(exc_info.value)
818-
assert msg == "Must provide only one mutation type in schema."
819-
820-
def allows_only_a_single_subscription_type():
821-
sdl = """
822-
schema {
823-
query: Hello
824-
subscription: Hello
825-
subscription: Yellow
826-
}
827-
type Hello {
828-
bar: String
829-
}
830-
831-
type Yellow {
832-
isColor: Boolean
833-
}
834-
"""
835-
with raises(TypeError) as exc_info:
836-
build_schema(sdl)
837-
msg = str(exc_info.value)
838-
assert msg == "Must provide only one subscription type in schema."
839-
840779
def unknown_type_referenced():
841780
sdl = """
842781
schema {

tests/utilities/test_extend_schema.py

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,63 +1286,3 @@ def schema_extension_ast_are_available_from_schema_object():
12861286
extend schema @foo
12871287
"""
12881288
)
1289-
1290-
def does_not_allow_redefining_an_existing_root_type():
1291-
sdl = """
1292-
extend schema {
1293-
query: SomeType
1294-
}
1295-
1296-
type SomeType {
1297-
seeSomething: String
1298-
}
1299-
"""
1300-
with raises(TypeError) as exc_info:
1301-
extend_test_schema(sdl)
1302-
assert str(exc_info.value).startswith(
1303-
"Must provide only one query type in schema."
1304-
)
1305-
1306-
def does_not_allow_defining_a_root_operation_type_twice():
1307-
sdl = """
1308-
extend schema {
1309-
mutation: Mutation
1310-
}
1311-
1312-
extend schema {
1313-
mutation: Mutation
1314-
}
1315-
1316-
type Mutation {
1317-
doSomething: String
1318-
}
1319-
"""
1320-
with raises(TypeError) as exc_info:
1321-
extend_test_schema(sdl)
1322-
assert str(exc_info.value).startswith(
1323-
"Must provide only one mutation type in schema."
1324-
)
1325-
1326-
def does_not_allow_defining_root_operation_type_with_different_types():
1327-
sdl = """
1328-
extend schema {
1329-
mutation: Mutation
1330-
}
1331-
1332-
extend schema {
1333-
mutation: SomethingElse
1334-
}
1335-
1336-
type Mutation {
1337-
doSomething: String
1338-
}
1339-
1340-
type SomethingElse {
1341-
doSomethingElse: String
1342-
}
1343-
"""
1344-
with raises(TypeError) as exc_info:
1345-
extend_test_schema(sdl)
1346-
assert str(exc_info.value).startswith(
1347-
"Must provide only one mutation type in schema."
1348-
)

0 commit comments

Comments
 (0)