Skip to content

Commit 218ff57

Browse files
committed
Extract check for possible extensions into a separate rule
Replicates graphql/graphql-js@e6a3f08
1 parent 381ef4b commit 218ff57

File tree

6 files changed

+370
-96
lines changed

6 files changed

+370
-96
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 1683
16+
14.0.2. All parts of the API are covered by an extensive test suite of currently 1690
1717
unit tests.
1818

1919

graphql/utilities/extend_schema.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@
33
from itertools import chain
44
from typing import Any, Callable, Dict, List, Optional, Union, Tuple, cast
55

6-
from ..error import GraphQLError
76
from ..language import (
87
DirectiveDefinitionNode,
98
DocumentNode,
10-
EnumTypeExtensionNode,
11-
InputObjectTypeExtensionNode,
12-
InterfaceTypeExtensionNode,
13-
ObjectTypeExtensionNode,
149
OperationType,
1510
SchemaExtensionNode,
1611
SchemaDefinitionNode,
1712
TypeDefinitionNode,
18-
UnionTypeExtensionNode,
1913
TypeExtensionNode,
2014
)
2115
from ..type import (
@@ -110,17 +104,7 @@ def extend_schema(
110104
type_name = def_.name.value
111105
type_definition_map[type_name] = def_
112106
elif isinstance(def_, TypeExtensionNode):
113-
# Sanity check that this type extension exists within the schema's existing
114-
# types.
115107
extended_type_name = def_.name.value
116-
existing_type = schema.get_type(extended_type_name)
117-
if not existing_type:
118-
raise GraphQLError(
119-
f"Cannot extend type '{extended_type_name}'"
120-
" because it does not exist in the existing schema.",
121-
[def_],
122-
)
123-
check_extension_node(existing_type, def_)
124108
type_extensions_map[extended_type_name].append(def_)
125109
elif isinstance(def_, DirectiveDefinitionNode):
126110
directive_definitions.append(def_)
@@ -502,25 +486,3 @@ def resolve_type(type_name: str) -> GraphQLNamedType:
502486
ast_node=schema.ast_node,
503487
extension_ast_nodes=schema_extension_ast_nodes,
504488
)
505-
506-
507-
def check_extension_node(type_: GraphQLNamedType, node: TypeExtensionNode):
508-
if isinstance(node, ObjectTypeExtensionNode):
509-
if not is_object_type(type_):
510-
raise GraphQLError(f"Cannot extend non-object type '{type_.name}'.", [node])
511-
elif isinstance(node, InterfaceTypeExtensionNode):
512-
if not is_interface_type(type_):
513-
raise GraphQLError(
514-
f"Cannot extend non-interface type '{type_.name}'.", [node]
515-
)
516-
elif isinstance(node, EnumTypeExtensionNode):
517-
if not is_enum_type(type_):
518-
raise GraphQLError(f"Cannot extend non-enum type '{type_.name}'.", [node])
519-
elif isinstance(node, UnionTypeExtensionNode):
520-
if not is_union_type(type_):
521-
raise GraphQLError(f"Cannot extend non-union type '{type_.name}'.", [node])
522-
elif isinstance(node, InputObjectTypeExtensionNode):
523-
if not is_input_object_type(type_):
524-
raise GraphQLError(
525-
f"Cannot extend non-input object type '{type_.name}'.", [node]
526-
)
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import re
2+
from functools import partial
3+
from typing import Any, List
4+
5+
from ...error import GraphQLError
6+
from ...language import TypeDefinitionNode, TypeExtensionNode
7+
from ...pyutils import quoted_or_list, suggestion_list
8+
from ...type import (
9+
is_enum_type,
10+
is_input_object_type,
11+
is_interface_type,
12+
is_object_type,
13+
is_scalar_type,
14+
is_union_type,
15+
)
16+
from . import SDLValidationContext, SDLValidationRule
17+
18+
__all__ = [
19+
"PossibleTypeExtensionsRule",
20+
"extending_unknown_type_message",
21+
"extending_different_type_kind_message",
22+
]
23+
24+
25+
def extending_unknown_type_message(type_name: str, suggested_types: List[str]) -> str:
26+
message = f"Cannot extend type '{type_name}' because it is not defined."
27+
if suggested_types:
28+
message += f" Did you mean {quoted_or_list(suggested_types)}?"
29+
return message
30+
31+
32+
def extending_different_type_kind_message(type_name: str, kind: str) -> str:
33+
return f"Cannot extend non-{kind} type {type_name}"
34+
35+
36+
class PossibleTypeExtensionsRule(SDLValidationRule):
37+
"""Possible type extension
38+
39+
A type extension is only valid if the type is defined and has the same kind.
40+
"""
41+
42+
def __init__(self, context: SDLValidationContext) -> None:
43+
super().__init__(context)
44+
self.schema = context.schema
45+
self.defined_types = {
46+
def_.name.value: def_
47+
for def_ in context.document.definitions
48+
if isinstance(def_, TypeDefinitionNode)
49+
}
50+
51+
def check_extension(self, node: TypeExtensionNode, *_args):
52+
schema = self.schema
53+
type_name = node.name.value
54+
def_node = self.defined_types.get(type_name)
55+
existing_type = schema.get_type(type_name) if schema else None
56+
57+
if def_node:
58+
expected_kind = def_kind_to_ext_kind(def_node.kind)
59+
if expected_kind != node.kind:
60+
self.report_error(
61+
GraphQLError(
62+
extending_different_type_kind_message(
63+
type_name, extension_kind_to_type_name(expected_kind)
64+
),
65+
[def_node, node],
66+
)
67+
)
68+
elif existing_type:
69+
expected_kind = type_to_ext_kind(existing_type)
70+
if expected_kind != node.kind:
71+
self.report_error(
72+
GraphQLError(
73+
extending_different_type_kind_message(
74+
type_name, extension_kind_to_type_name(expected_kind)
75+
),
76+
[node],
77+
)
78+
)
79+
else:
80+
all_type_names = list(self.defined_types)
81+
if self.schema:
82+
all_type_names.extend(self.schema.type_map)
83+
suggested_types = suggestion_list(type_name, all_type_names)
84+
self.report_error(
85+
GraphQLError(
86+
extending_unknown_type_message(type_name, suggested_types),
87+
[node.name],
88+
)
89+
)
90+
91+
enter_scalar_type_extension = enter_object_type_extension = check_extension
92+
enter_interface_type_extension = enter_union_type_extension = check_extension
93+
enter_enum_type_extension = enter_input_object_type_extension = check_extension
94+
95+
96+
def_kind_to_ext_kind = partial(re.compile("(?<=_type_)definition$").sub, "extension")
97+
98+
99+
def type_to_ext_kind(type_: Any) -> str:
100+
if is_scalar_type(type_):
101+
return "scalar_type_extension"
102+
elif is_object_type(type_):
103+
return "object_type_extension"
104+
elif is_interface_type(type_):
105+
return "interface_type_extension"
106+
elif is_union_type(type_):
107+
return "union_type_extension"
108+
elif is_enum_type(type_):
109+
return "enum_type_extension"
110+
elif is_input_object_type(type_):
111+
return "input_object_type_extension"
112+
else:
113+
return "unknown_type_extension"
114+
115+
116+
_type_names_for_extension_kinds = {
117+
"scalar_type_extension": "scalar",
118+
"object_type_extension": "object",
119+
"interface_type_extension": "interface",
120+
"union_type_extension": "union",
121+
"enum_type_extension": "enum",
122+
"input_object_type_extension": "input object",
123+
}
124+
125+
126+
def extension_kind_to_type_name(kind: str) -> str:
127+
return _type_names_for_extension_kinds.get(kind, "unknown type")

graphql/validation/specified_rules.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
from .rules.unique_enum_value_names import UniqueEnumValueNamesRule
8888
from .rules.unique_field_definition_names import UniqueFieldDefinitionNamesRule
8989
from .rules.unique_directive_names import UniqueDirectiveNamesRule
90+
from .rules.possible_type_extensions import PossibleTypeExtensionsRule
9091
from .rules.known_argument_names import KnownArgumentNamesOnDirectivesRule
9192
from .rules.provided_required_arguments import ProvidedRequiredArgumentsOnDirectivesRule
9293

@@ -137,6 +138,7 @@
137138
KnownTypeNamesRule,
138139
KnownDirectivesRule,
139140
UniqueDirectivesPerLocationRule,
141+
PossibleTypeExtensionsRule,
140142
KnownArgumentNamesOnDirectivesRule,
141143
UniqueArgumentNamesRule,
142144
UniqueInputFieldNamesRule,

tests/utilities/test_extend_schema.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from pytest import raises
22

33
from graphql import graphql_sync
4-
from graphql.error import GraphQLError
54
from graphql.language import parse, print_ast, DirectiveLocation, DocumentNode
65
from graphql.pyutils import dedent
76
from graphql.type import (
@@ -1068,62 +1067,6 @@ def does_not_allow_replacing_an_existing_enum_value():
10681067
" It cannot also be defined in this type extension."
10691068
)
10701069

1071-
def does_not_allow_extending_an_unknown_type():
1072-
for sdl in [
1073-
"extend scalar UnknownType @foo",
1074-
"extend type UnknownType @foo",
1075-
"extend interface UnknownType @foo",
1076-
"extend enum UnknownType @foo",
1077-
"extend union UnknownType @foo",
1078-
"extend input UnknownType @foo",
1079-
]:
1080-
with raises(GraphQLError) as exc_info:
1081-
extend_test_schema(sdl)
1082-
assert str(exc_info.value).startswith(
1083-
"Cannot extend type 'UnknownType'"
1084-
" because it does not exist in the existing schema."
1085-
)
1086-
1087-
def it_does_not_allow_extending_a_mismatch_type():
1088-
type_sdl = """
1089-
extend type SomeInterface @foo
1090-
"""
1091-
with raises(GraphQLError) as exc_info:
1092-
extend_test_schema(type_sdl)
1093-
assert str(exc_info.value).startswith(
1094-
"Cannot extend non-object type 'SomeInterface'."
1095-
)
1096-
1097-
interface_sdl = """
1098-
extend interface Foo @foo
1099-
"""
1100-
with raises(GraphQLError) as exc_info:
1101-
extend_test_schema(interface_sdl)
1102-
assert str(exc_info.value).startswith("Cannot extend non-interface type 'Foo'.")
1103-
1104-
enum_sdl = """
1105-
extend enum Foo @foo
1106-
"""
1107-
with raises(GraphQLError) as exc_info:
1108-
extend_test_schema(enum_sdl)
1109-
assert str(exc_info.value).startswith("Cannot extend non-enum type 'Foo'.")
1110-
1111-
union_sdl = """
1112-
extend union Foo @foo
1113-
"""
1114-
with raises(GraphQLError) as exc_info:
1115-
extend_test_schema(union_sdl)
1116-
assert str(exc_info.value).startswith("Cannot extend non-union type 'Foo'.")
1117-
1118-
input_sdl = """
1119-
extend input Foo @foo
1120-
"""
1121-
with raises(GraphQLError) as exc_info:
1122-
extend_test_schema(input_sdl)
1123-
assert str(exc_info.value).startswith(
1124-
"Cannot extend non-input object type 'Foo'."
1125-
)
1126-
11271070
def describe_can_add_additional_root_operation_types():
11281071
def does_not_automatically_include_common_root_type_names():
11291072
schema = extend_test_schema(

0 commit comments

Comments
 (0)