Skip to content

Commit 59d3294

Browse files
committed
enum: serialize and parse_value/literal now throw on invalid values
Replicates graphql/graphql-js@8057e39
1 parent b3ad94b commit 59d3294

File tree

11 files changed

+178
-110
lines changed

11 files changed

+178
-110
lines changed

src/graphql/type/definition.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
overload,
1717
)
1818

19+
from ..error import GraphQLError
1920
from ..language import (
2021
EnumTypeDefinitionNode,
2122
EnumValueDefinitionNode,
@@ -39,17 +40,19 @@
3940
UnionTypeDefinitionNode,
4041
UnionTypeExtensionNode,
4142
ValueNode,
43+
print_ast,
4244
)
4345
from ..pyutils import (
4446
AwaitableOrValue,
4547
FrozenList,
4648
Path,
4749
cached_property,
50+
did_you_mean,
4851
inspect,
4952
is_collection,
5053
is_description,
54+
suggestion_list,
5155
Undefined,
52-
UndefinedType,
5356
)
5457
from ..utilities.value_from_ast_untyped import value_from_ast_untyped
5558

@@ -1100,40 +1103,56 @@ def _value_lookup(self) -> Dict[Any, str]:
11001103
pass # ignore unhashable values
11011104
return lookup
11021105

1103-
def serialize(self, output_value: Any) -> Union[str, None, UndefinedType]:
1106+
def serialize(self, output_value: Any) -> str:
11041107
try:
1105-
return self._value_lookup.get(output_value, Undefined)
1106-
except TypeError: # unhashable value
1108+
return self._value_lookup[output_value]
1109+
except KeyError: # hashable value not found
1110+
pass
1111+
except TypeError: # unhashable value, we need to scan all values
11071112
for enum_name, enum_value in self.values.items():
11081113
if enum_value.value == output_value:
11091114
return enum_name
1110-
return Undefined
1115+
raise GraphQLError(
1116+
f"Enum '{self.name}' cannot represent value: {inspect(output_value)}"
1117+
)
11111118

11121119
def parse_value(self, input_value: str) -> Any:
11131120
if isinstance(input_value, str):
11141121
try:
11151122
enum_value = self.values[input_value]
11161123
except KeyError:
1117-
return Undefined
1118-
if enum_value.value is None or enum_value.value is Undefined:
1119-
return input_value
1124+
raise GraphQLError(
1125+
f"Value '{input_value}' does not exist in '{self.name}' enum."
1126+
+ did_you_mean_enum_value(self, input_value)
1127+
)
11201128
return enum_value.value
1121-
return Undefined
1129+
value_str = inspect(input_value)
1130+
raise GraphQLError(
1131+
f"Enum '{self.name}' cannot represent non-string value: {value_str}."
1132+
+ did_you_mean_enum_value(self, value_str)
1133+
)
11221134

11231135
def parse_literal(
11241136
self, value_node: ValueNode, _variables: Optional[Dict[str, Any]] = None
11251137
) -> Any:
11261138
# Note: variables will be resolved before calling this method.
11271139
if isinstance(value_node, EnumValueNode):
1128-
value = value_node.value
11291140
try:
1130-
enum_value = self.values[value]
1141+
enum_value = self.values[value_node.value]
11311142
except KeyError:
1132-
return Undefined
1133-
if enum_value.value is None or enum_value.value is Undefined:
1134-
return value
1143+
value_str = print_ast(value_node)
1144+
raise GraphQLError(
1145+
f"Value '{value_str}' does not exist in '{self.name}' enum."
1146+
+ did_you_mean_enum_value(self, value_str),
1147+
value_node,
1148+
)
11351149
return enum_value.value
1136-
return Undefined
1150+
value_str = print_ast(value_node)
1151+
raise GraphQLError(
1152+
f"Enum '{self.name}' cannot represent non-enum value: {value_str}."
1153+
+ did_you_mean_enum_value(self, value_str),
1154+
value_node,
1155+
)
11371156

11381157

11391158
def is_enum_type(type_: Any) -> bool:
@@ -1146,6 +1165,11 @@ def assert_enum_type(type_: Any) -> GraphQLEnumType:
11461165
return cast(GraphQLEnumType, type_)
11471166

11481167

1168+
def did_you_mean_enum_value(enum_type: GraphQLEnumType, unknown_value_str: str) -> str:
1169+
suggested_values = suggestion_list(unknown_value_str, enum_type.values)
1170+
return did_you_mean(suggested_values, "the enum value")
1171+
1172+
11491173
class GraphQLEnumValue:
11501174

11511175
value: Any

src/graphql/utilities/coerce_input_value.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,14 @@
1111
Undefined,
1212
)
1313
from ..type import (
14-
GraphQLEnumType,
1514
GraphQLInputObjectType,
1615
GraphQLInputType,
1716
GraphQLList,
1817
GraphQLScalarType,
19-
is_enum_type,
18+
is_leaf_type,
2019
is_input_object_type,
2120
is_list_type,
2221
is_non_null_type,
23-
is_scalar_type,
2422
GraphQLNonNull,
2523
)
2624

@@ -130,7 +128,7 @@ def coerce_input_value(
130128
)
131129
return type_.out_type(coerced_dict)
132130

133-
if is_scalar_type(type_):
131+
if is_leaf_type(type_):
134132
# Scalars determine if a value is valid via `parse_value()`, which can throw to
135133
# indicate failure. If it throws, maintain a reference to the original error.
136134
type_ = cast(GraphQLScalarType, type_)
@@ -156,23 +154,5 @@ def coerce_input_value(
156154
)
157155
return parse_result
158156

159-
if is_enum_type(type_):
160-
type_ = cast(GraphQLEnumType, type_)
161-
values = type_.values
162-
if isinstance(input_value, str):
163-
enum_value = values.get(input_value)
164-
if enum_value:
165-
return enum_value.value
166-
suggestions = suggestion_list(str(input_value), values)
167-
on_error(
168-
path.as_list() if path else [],
169-
input_value,
170-
GraphQLError(
171-
f"Expected type '{type_.name}'."
172-
+ did_you_mean(suggestions, "the enum value")
173-
),
174-
)
175-
return Undefined
176-
177157
# Not reachable. All possible input types have been considered.
178158
raise TypeError(f"Unexpected input type: {inspect(type_)}.") # pragma: no cover

src/graphql/utilities/value_from_ast.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import Any, Dict, List, Optional, cast
22

33
from ..language import (
4-
EnumValueNode,
54
ListValueNode,
65
NullValueNode,
76
ObjectValueNode,
@@ -10,17 +9,15 @@
109
)
1110
from ..pyutils import inspect, is_invalid, Undefined
1211
from ..type import (
13-
GraphQLEnumType,
1412
GraphQLInputObjectType,
1513
GraphQLInputType,
1614
GraphQLList,
1715
GraphQLNonNull,
1816
GraphQLScalarType,
19-
is_enum_type,
2017
is_input_object_type,
18+
is_leaf_type,
2119
is_list_type,
2220
is_non_null_type,
23-
is_scalar_type,
2421
)
2522

2623
__all__ = ["value_from_ast"]
@@ -125,18 +122,7 @@ def value_from_ast(
125122

126123
return type_.out_type(coerced_obj)
127124

128-
if is_enum_type(type_):
129-
if not isinstance(value_node, EnumValueNode):
130-
return Undefined
131-
type_ = cast(GraphQLEnumType, type_)
132-
value_name = value_node.value
133-
enum_value = type_.values.get(value_name)
134-
if not enum_value:
135-
return Undefined
136-
value = enum_value.value
137-
return value_name if value is Undefined else value
138-
139-
if is_scalar_type(type_):
125+
if is_leaf_type(type_):
140126
# Scalars fulfill parsing a literal value via `parse_literal()`. Invalid values
141127
# represent a failure to parse correctly, in which case Undefined is returned.
142128
type_ = cast(GraphQLScalarType, type_)
@@ -148,8 +134,6 @@ def value_from_ast(
148134
result = type_.parse_literal(value_node)
149135
except Exception:
150136
return Undefined
151-
if is_invalid(result):
152-
return Undefined
153137
return result
154138

155139
# Not reachable. All possible input types have been considered.

src/graphql/validation/rules/values_of_correct_type.py

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@
1919
GraphQLScalarType,
2020
get_named_type,
2121
get_nullable_type,
22-
is_enum_type,
2322
is_input_object_type,
23+
is_leaf_type,
2424
is_list_type,
2525
is_non_null_type,
2626
is_required_input_field,
27-
is_scalar_type,
2827
)
2928
from . import ValidationRule
3029

@@ -116,21 +115,7 @@ def is_valid_value_node(self, node: ValueNode) -> None:
116115

117116
type_ = get_named_type(location_type)
118117

119-
if is_enum_type(type_):
120-
if not isinstance(node, EnumValueNode) or node.value not in type_.values:
121-
all_names = list(type_.values)
122-
suggested_values = suggestion_list(print_ast(node), all_names)
123-
self.report_error(
124-
GraphQLError(
125-
f"Expected value of type '{type_.name}',"
126-
f" found {print_ast(node)}."
127-
+ did_you_mean(suggested_values, "the enum value"),
128-
node,
129-
)
130-
)
131-
return
132-
133-
if not is_scalar_type(type_):
118+
if not is_leaf_type(type_):
134119
self.report_error(
135120
GraphQLError(
136121
f"Expected value of type '{location_type}',"

tests/execution/test_executor.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from graphql.error import GraphQLError
77
from graphql.execution import execute
8-
from graphql.language import parse, OperationDefinitionNode, FieldNode
9-
from graphql.pyutils import inspect
8+
from graphql.language import parse, FieldNode, OperationDefinitionNode
9+
from graphql.pyutils import inspect, Undefined
1010
from graphql.type import (
1111
GraphQLArgument,
1212
GraphQLBoolean,
@@ -18,6 +18,7 @@
1818
GraphQLObjectType,
1919
GraphQLResolveInfo,
2020
GraphQLSchema,
21+
GraphQLScalarType,
2122
GraphQLString,
2223
ResponsePath,
2324
)
@@ -904,6 +905,34 @@ async def async_is_special():
904905
awaited_result = await async_result
905906
assert awaited_result == result
906907

908+
def fails_when_serialize_of_custom_scalar_does_not_return_a_value():
909+
custom_scalar = GraphQLScalarType(
910+
"CustomScalar", serialize=lambda _value: Undefined # returns nothing
911+
)
912+
schema = GraphQLSchema(
913+
GraphQLObjectType(
914+
"Query",
915+
{
916+
"customScalar": GraphQLField(
917+
custom_scalar, resolve=lambda *_args: "CUSTOM_VALUE"
918+
)
919+
},
920+
)
921+
)
922+
923+
result = execute(schema, parse("{ customScalar }"))
924+
assert result == (
925+
{"customScalar": None},
926+
[
927+
{
928+
"message": "Expected a value of type 'CustomScalar'"
929+
" but received: 'CUSTOM_VALUE'",
930+
"locations": [(1, 3)],
931+
"path": ["customScalar"],
932+
}
933+
],
934+
)
935+
907936
def executes_ignoring_invalid_non_executable_definitions():
908937
schema = GraphQLSchema(
909938
GraphQLObjectType("Query", {"foo": GraphQLField(GraphQLString)})

0 commit comments

Comments
 (0)