Skip to content

Commit ceabd24

Browse files
committed
Extract check for unique type names into separate rule
Replicates graphql/graphql-js@257797a
1 parent 152e90e commit ceabd24

File tree

8 files changed

+176
-72
lines changed

8 files changed

+176
-72
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 1662
16+
14.0.2. All parts of the API are covered by an extensive test suite of currently 1668
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
@@ -104,8 +104,6 @@ def build_ast_schema(
104104
elif isinstance(def_, TypeDefinitionNode):
105105
def_ = cast(TypeDefinitionNode, def_)
106106
type_name = def_.name.value
107-
if type_name in node_map:
108-
raise TypeError(f"Type '{type_name}' was defined more than once.")
109107
append_type_def(def_)
110108
node_map[type_name] = def_
111109
elif isinstance(def_, DirectiveDefinitionNode):

graphql/utilities/extend_schema.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,7 @@ def extend_schema(
108108
elif isinstance(def_, SchemaExtensionNode):
109109
schema_extensions.append(def_)
110110
elif isinstance(def_, TypeDefinitionNode):
111-
# Sanity check that none of the defined types conflict with the schema's
112-
# existing types.
113111
type_name = def_.name.value
114-
if schema.get_type(type_name):
115-
raise GraphQLError(
116-
f"Type '{type_name}' already exists in the schema."
117-
" It cannot also be defined in this type definition.",
118-
[def_],
119-
)
120112
type_definition_map[type_name] = def_
121113
elif isinstance(def_, TypeExtensionNode):
122114
# Sanity check that this type extension exists within the schema's existing
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from typing import Dict
2+
3+
from ...error import GraphQLError
4+
from ...language import NameNode, TypeDefinitionNode
5+
from . import SDLValidationContext, SDLValidationRule
6+
7+
__all__ = ["UniqueTypeNamesRule", "existed_type_name_message"]
8+
9+
10+
def duplicate_type_name_message(type_name: str) -> str:
11+
return f"There can be only one type named '{type_name}'."
12+
13+
14+
def existed_type_name_message(type_name: str) -> str:
15+
return (
16+
f"Type '{type_name}' already exists in the schema."
17+
" It cannot also be defined in this type definition."
18+
)
19+
20+
21+
class UniqueTypeNamesRule(SDLValidationRule):
22+
"""Unique type names
23+
24+
A GraphQL document is only valid if all defined types have unique names.
25+
"""
26+
27+
def __init__(self, context: SDLValidationContext) -> None:
28+
super().__init__(context)
29+
self.known_type_names: Dict[str, NameNode] = {}
30+
self.schema = context.schema
31+
32+
def check_type_name(self, node: TypeDefinitionNode, *_args):
33+
type_name = node.name.value
34+
35+
if self.schema and self.schema.get_type(type_name):
36+
self.report_error(
37+
GraphQLError(existed_type_name_message(type_name), [node.name])
38+
)
39+
else:
40+
if type_name in self.known_type_names:
41+
self.report_error(
42+
GraphQLError(
43+
duplicate_type_name_message(type_name),
44+
[self.known_type_names[type_name], node.name],
45+
)
46+
)
47+
else:
48+
self.known_type_names[type_name] = node.name
49+
return self.SKIP
50+
51+
enter_scalar_type_definition = check_type_name
52+
enter_object_type_definition = check_type_name
53+
enter_interface_type_definition = check_type_name
54+
enter_union_type_definition = check_type_name
55+
enter_enum_type_definition = check_type_name
56+
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_type_names import UniqueTypeNamesRule
8586
from .rules.known_argument_names import KnownArgumentNamesOnDirectivesRule
8687
from .rules.provided_required_arguments import ProvidedRequiredArgumentsOnDirectivesRule
8788

@@ -124,6 +125,7 @@
124125

125126
specified_sdl_rules: List[RuleType] = [
126127
LoneSchemaDefinitionRule,
128+
UniqueTypeNamesRule,
127129
KnownDirectivesRule,
128130
UniqueDirectivesPerLocationRule,
129131
KnownArgumentNamesOnDirectivesRule,

tests/utilities/test_build_ast_schema.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -965,22 +965,3 @@ def does_not_consider_fragment_names():
965965
build_schema(sdl)
966966
msg = str(exc_info.value)
967967
assert msg == "Specified query type 'Foo' not found in document."
968-
969-
def forbids_duplicate_type_definitions():
970-
sdl = """
971-
schema {
972-
query: Repeated
973-
}
974-
975-
type Repeated {
976-
id: Int
977-
}
978-
979-
type Repeated {
980-
id: String
981-
}
982-
"""
983-
with raises(TypeError) as exc_info:
984-
build_schema(sdl)
985-
msg = str(exc_info.value)
986-
assert msg == "Type 'Repeated' was defined more than once."

tests/utilities/test_extend_schema.py

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,48 +1075,6 @@ def does_not_allow_replacing_a_custom_directive():
10751075
"Directive 'meow' already exists in the schema. It cannot be redefined."
10761076
)
10771077

1078-
def does_not_allow_replacing_an_existing_type():
1079-
def existing_type_error(type_):
1080-
return (
1081-
f"Type '{type_}' already exists in the schema."
1082-
" It cannot also be defined in this type definition."
1083-
)
1084-
1085-
type_sdl = """
1086-
type Bar
1087-
"""
1088-
with raises(GraphQLError) as exc_info:
1089-
assert extend_test_schema(type_sdl)
1090-
assert str(exc_info.value).startswith(existing_type_error("Bar"))
1091-
1092-
scalar_sdl = """
1093-
scalar SomeScalar
1094-
"""
1095-
with raises(GraphQLError) as exc_info:
1096-
assert extend_test_schema(scalar_sdl)
1097-
assert str(exc_info.value).startswith(existing_type_error("SomeScalar"))
1098-
1099-
enum_sdl = """
1100-
enum SomeEnum
1101-
"""
1102-
with raises(GraphQLError) as exc_info:
1103-
assert extend_test_schema(enum_sdl)
1104-
assert str(exc_info.value).startswith(existing_type_error("SomeEnum"))
1105-
1106-
union_sdl = """
1107-
union SomeUnion
1108-
"""
1109-
with raises(GraphQLError) as exc_info:
1110-
assert extend_test_schema(union_sdl)
1111-
assert str(exc_info.value).startswith(existing_type_error("SomeUnion"))
1112-
1113-
input_sdl = """
1114-
input SomeInput
1115-
"""
1116-
with raises(GraphQLError) as exc_info:
1117-
assert extend_test_schema(input_sdl)
1118-
assert str(exc_info.value).startswith(existing_type_error("SomeInput"))
1119-
11201078
def does_not_allow_replacing_an_existing_field():
11211079
def existing_field_error(type_, field):
11221080
return (
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from functools import partial
2+
3+
from graphql.utilities import build_schema
4+
from graphql.validation.rules.unique_type_names import (
5+
UniqueTypeNamesRule,
6+
duplicate_type_name_message,
7+
existed_type_name_message,
8+
)
9+
10+
from .harness import assert_sdl_validation_errors
11+
12+
assert_errors = partial(assert_sdl_validation_errors, UniqueTypeNamesRule)
13+
14+
assert_valid = partial(assert_errors, errors=[])
15+
16+
17+
def duplicate_type(name, l1, c1, l2, c2):
18+
return {
19+
"message": duplicate_type_name_message(name),
20+
"locations": [(l1, c1), (l2, c2)],
21+
}
22+
23+
24+
def existed_type(name, line, col):
25+
return {"message": existed_type_name_message(name), "locations": [(line, col)]}
26+
27+
28+
def describe_validate_unique_type_names():
29+
def no_types():
30+
assert_valid(
31+
"""
32+
directive @test on SCHEMA
33+
"""
34+
)
35+
36+
def one_type():
37+
assert_valid(
38+
"""
39+
type Foo
40+
"""
41+
)
42+
43+
def many_types():
44+
assert_valid(
45+
"""
46+
type Foo
47+
type Bar
48+
type Baz
49+
"""
50+
)
51+
52+
def type_and_non_type_definitions_named_the_same():
53+
assert_valid(
54+
"""
55+
query Foo { __typename }
56+
fragment Foo on Query { __typename }
57+
directive @Foo on SCHEMA
58+
59+
type Foo
60+
"""
61+
)
62+
63+
def types_named_the_same():
64+
assert_errors(
65+
"""
66+
type Foo
67+
68+
scalar Foo
69+
type Foo
70+
interface Foo
71+
union Foo
72+
enum Foo
73+
input Foo
74+
""",
75+
[
76+
duplicate_type("Foo", 2, 18, 4, 20),
77+
duplicate_type("Foo", 2, 18, 5, 18),
78+
duplicate_type("Foo", 2, 18, 6, 23),
79+
duplicate_type("Foo", 2, 18, 7, 19),
80+
duplicate_type("Foo", 2, 18, 8, 18),
81+
duplicate_type("Foo", 2, 18, 9, 19),
82+
],
83+
)
84+
85+
def adding_new_type_to_existing_schema():
86+
schema = build_schema("type Foo")
87+
88+
assert_valid("type Bar", schema=schema)
89+
90+
def adding_new_type_to_existing_schema_with_same_named_directive():
91+
schema = build_schema("directive @Foo on SCHEMA")
92+
93+
assert_valid("type Foo", schema=schema)
94+
95+
def adding_conflicting_types_to_existing_schema():
96+
schema = build_schema("type Foo")
97+
sdl = """
98+
scalar Foo
99+
type Foo
100+
interface Foo
101+
union Foo
102+
enum Foo
103+
input Foo
104+
"""
105+
106+
assert_errors(
107+
sdl,
108+
[
109+
existed_type("Foo", 2, 20),
110+
existed_type("Foo", 3, 18),
111+
existed_type("Foo", 4, 23),
112+
existed_type("Foo", 5, 19),
113+
existed_type("Foo", 6, 18),
114+
existed_type("Foo", 7, 19),
115+
],
116+
schema,
117+
)

0 commit comments

Comments
 (0)