Skip to content

Commit ee1270f

Browse files
committed
Extract "did_you_mean" util function
Replicates graphql/graphql-js@f5a975f
1 parent 44b2269 commit ee1270f

21 files changed

+152
-204
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.5 of GraphQL-core-next is up-to-date with GraphQL.js version
16-
14.3.1. All parts of the API are covered by an extensive test suite of currently 1790
16+
14.3.1. All parts of the API are covered by an extensive test suite of currently 1784
1717
unit tests.
1818

1919

docs/modules/pyutils.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PyUtils
99
.. autofunction:: snake_to_camel
1010
.. autofunction:: cached_property
1111
.. autofunction:: dedent
12+
.. autofunction:: did_you_mean
1213
.. autoclass:: EventEmitter
1314
.. autoclass:: EventEmitterAsyncIterator
1415
.. autofunction:: inspect
@@ -17,8 +18,6 @@ PyUtils
1718
.. autofunction:: is_invalid
1819
.. autofunction:: is_nullish
1920
.. autoclass:: AwaitableOrValue
20-
.. autofunction:: or_list
21-
.. autofunction:: quoted_or_list
2221
.. autofunction:: suggestion_list
2322
.. autoclass:: FrozenError
2423
.. autoclass:: FrozenList

graphql/pyutils/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@
1111
from .convert_case import camel_to_snake, snake_to_camel
1212
from .cached_property import cached_property
1313
from .dedent import dedent
14+
from .did_you_mean import did_you_mean
1415
from .event_emitter import EventEmitter, EventEmitterAsyncIterator
1516
from .inspect import inspect
1617
from .is_finite import is_finite
1718
from .is_integer import is_integer
1819
from .is_invalid import is_invalid
1920
from .is_nullish import is_nullish
2021
from .awaitable_or_value import AwaitableOrValue
21-
from .or_list import or_list
22-
from .quoted_or_list import quoted_or_list
2322
from .suggestion_list import suggestion_list
2423
from .frozen_error import FrozenError
2524
from .frozen_list import FrozenList
@@ -30,6 +29,7 @@
3029
"snake_to_camel",
3130
"cached_property",
3231
"dedent",
32+
"did_you_mean",
3333
"EventEmitter",
3434
"EventEmitterAsyncIterator",
3535
"inspect",
@@ -38,8 +38,6 @@
3838
"is_invalid",
3939
"is_nullish",
4040
"AwaitableOrValue",
41-
"or_list",
42-
"quoted_or_list",
4341
"suggestion_list",
4442
"FrozenError",
4543
"FrozenList",

graphql/pyutils/did_you_mean.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import Sequence, List
2+
3+
__all__ = ["did_you_mean"]
4+
5+
MAX_LENGTH = 5
6+
7+
8+
def did_you_mean(suggestions: Sequence[str], sub_message: str = None) -> str:
9+
"""Given [ A, B, C ] return ' Did you mean A, B, or C?'"""
10+
parts: List[str] = []
11+
if suggestions:
12+
append = parts.append
13+
append(" Did you mean")
14+
if sub_message:
15+
append(sub_message)
16+
suggestions = suggestions[:MAX_LENGTH]
17+
if len(suggestions) > 1:
18+
append(", ".join(suggestions[:-1]))
19+
append("or")
20+
append(suggestions[-1] + "?")
21+
return " ".join(parts)

graphql/pyutils/or_list.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

graphql/pyutils/quoted_or_list.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

graphql/pyutils/suggestion_list.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from typing import Collection
1+
from typing import Collection, List
22

33
__all__ = ["suggestion_list"]
44

55

6-
def suggestion_list(input_: str, options: Collection[str]):
6+
def suggestion_list(input_: str, options: Collection[str]) -> List[str]:
77
"""Get list with suggestions for a given input.
88
99
Given an invalid input string and list of valid options, returns a filtered list

graphql/utilities/coerce_value.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ..error import GraphQLError, INVALID
44
from ..language import Node
5-
from ..pyutils import inspect, is_invalid, or_list, suggestion_list
5+
from ..pyutils import did_you_mean, inspect, is_invalid, suggestion_list
66
from ..type import (
77
GraphQLEnumType,
88
GraphQLInputObjectType,
@@ -75,7 +75,7 @@ def coerce_value(
7575
f"Expected type {type_.name}",
7676
blame_node,
7777
path,
78-
str(error),
78+
f" {error}",
7979
error,
8080
)
8181
]
@@ -89,11 +89,13 @@ def coerce_value(
8989
if enum_value:
9090
return of_value(value if enum_value.value is None else enum_value.value)
9191
suggestions = suggestion_list(str(value), values)
92-
did_you_mean = f"did you mean {or_list(suggestions)}?" if suggestions else None
9392
return of_errors(
9493
[
9594
coercion_error(
96-
f"Expected type {type_.name}", blame_node, path, did_you_mean
95+
f"Expected type {type_.name}",
96+
blame_node,
97+
path,
98+
did_you_mean(suggestions),
9799
)
98100
]
99101
)
@@ -160,16 +162,13 @@ def coerce_value(
160162
for field_name in value:
161163
if field_name not in fields:
162164
suggestions = suggestion_list(field_name, fields)
163-
did_you_mean = (
164-
f"did you mean {or_list(suggestions)}?" if suggestions else None
165-
)
166165
errors = add(
167166
errors,
168167
coercion_error(
169168
f"Field '{field_name}' is not defined by type {type_.name}",
170169
blame_node,
171170
path,
172-
did_you_mean,
171+
did_you_mean(suggestions),
173172
),
174173
)
175174

@@ -205,15 +204,17 @@ def coercion_error(
205204
original_error: Exception = None,
206205
) -> GraphQLError:
207206
"""Return a GraphQLError instance"""
208-
if path:
209-
path_str = print_path(path)
207+
path_str = print_path(path)
208+
if path_str:
210209
message += f" at {path_str}"
211-
message += f"; {sub_message}" if sub_message else "."
210+
message += "."
211+
if sub_message:
212+
message += sub_message
212213
# noinspection PyArgumentEqualDefault
213214
return GraphQLError(message, blame_node, None, None, None, original_error)
214215

215216

216-
def print_path(path: Path) -> str:
217+
def print_path(path: Optional[Path]) -> str:
217218
"""Build string describing the path into the value where error was found"""
218219
path_str = ""
219220
current_path: Optional[Path] = path

graphql/validation/rules/fields_on_correct_type.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
)
1212
from ...error import GraphQLError
1313
from ...language import FieldNode
14-
from ...pyutils import quoted_or_list, suggestion_list
14+
from ...pyutils import did_you_mean, suggestion_list
1515
from . import ValidationRule
1616

1717
__all__ = ["FieldsOnCorrectTypeRule", "undefined_field_message"]
@@ -23,14 +23,10 @@ def undefined_field_message(
2323
suggested_type_names: List[str],
2424
suggested_field_names: List[str],
2525
) -> str:
26-
message = f"Cannot query field '{field_name}' on type '{type_}'."
27-
if suggested_type_names:
28-
suggestions = quoted_or_list(suggested_type_names)
29-
message += f" Did you mean to use an inline fragment on {suggestions}?"
30-
elif suggested_field_names:
31-
suggestions = quoted_or_list(suggested_field_names)
32-
message += f" Did you mean {suggestions}?"
33-
return message
26+
hint = did_you_mean(
27+
[f"'{s}'" for s in suggested_type_names], "to use an inline fragment on"
28+
) or did_you_mean([f"'{s}'" for s in suggested_field_names])
29+
return f"Cannot query field '{field_name}' on type '{type_}'.{hint}"
3430

3531

3632
class FieldsOnCorrectTypeRule(ValidationRule):

graphql/validation/rules/known_argument_names.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ...error import GraphQLError
44
from ...language import ArgumentNode, DirectiveDefinitionNode, DirectiveNode, SKIP
5-
from ...pyutils import quoted_or_list, suggestion_list
5+
from ...pyutils import did_you_mean, suggestion_list
66
from ...type import specified_directives
77
from . import ASTValidationRule, SDLValidationContext, ValidationContext
88

@@ -17,22 +17,18 @@
1717
def unknown_arg_message(
1818
arg_name: str, field_name: str, type_name: str, suggested_args: List[str]
1919
) -> str:
20-
message = (
20+
hint = did_you_mean([f"'{s}'" for s in suggested_args])
21+
return (
2122
f"Unknown argument '{arg_name}' on field '{field_name}'"
22-
f" of type '{type_name}'."
23+
f" of type '{type_name}'.{hint}"
2324
)
24-
if suggested_args:
25-
message += f" Did you mean {quoted_or_list(suggested_args)}?"
26-
return message
2725

2826

2927
def unknown_directive_arg_message(
3028
arg_name: str, directive_name: str, suggested_args: List[str]
3129
) -> str:
32-
message = f"Unknown argument '{arg_name}' on directive '@{directive_name}'."
33-
if suggested_args:
34-
message += f" Did you mean {quoted_or_list(suggested_args)}?"
35-
return message
30+
hint = did_you_mean([f"'{s}'" for s in suggested_args])
31+
return f"Unknown argument '{arg_name}' on directive '@{directive_name}'. {hint}"
3632

3733

3834
class KnownArgumentNamesOnDirectivesRule(ASTValidationRule):

0 commit comments

Comments
 (0)