diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..97230dcfdb --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,17 @@ +Release type: minor + +This release adds a new configuration to disable field suggestions in the error +response. + +```python +@strawberry.type +class Query: + name: str + + +schema = strawberry.Schema( + query=Query, config=StrawberryConfig(disable_field_suggestions=True) +) +``` + +Trying to query `{ nam }` will not suggest to query `name` instead. diff --git a/TWEET.md b/TWEET.md new file mode 100644 index 0000000000..e1bb707825 --- /dev/null +++ b/TWEET.md @@ -0,0 +1,6 @@ +🆕 Release $version is out! Thanks to $contributor for the PR 👏 + +This release allows to disable field suggestions when sending an operation with +the wrong field. + +Get it here 👉 $release_url diff --git a/docs/types/schema-configurations.mdx b/docs/types/schema-configurations.mdx index 8a6811dc7f..099153238e 100644 --- a/docs/types/schema-configurations.mdx +++ b/docs/types/schema-configurations.mdx @@ -5,8 +5,7 @@ title: Schema Configurations # Schema Configurations Strawberry allows to customise how the schema is generated by passing -configurations. At the moment we only allow to disable auto camel casing of -fields and arguments names. +configurations. To customise the schema you can create an instance of `StrawberryConfig`, as shown in the example below: @@ -33,3 +32,76 @@ type Query { example_field: String! } ``` + +## Available configurations + +Here's a list of the available configurations: + +### auto_camel_case + +By default Strawberry will convert the field names to camel case, so a field +like `example_field` will be converted to `exampleField`. You can disable this +feature by setting `auto_camel_case` to `False`. + +```python +schema = strawberry.Schema(query=Query, config=StrawberryConfig(auto_camel_case=False)) +``` + +### default_resolver + +By default Strawberry will use the `getattr` function as the default resolver. +You can customise this by setting the `default_resolver` configuration. + +This can be useful in cases you want to allow returning a dictionary from a +resolver. + +```python +import strawberry + +from strawberry.schema.config import StrawberryConfig + + +def custom_resolver(obj, field): + try: + return obj[field] + except (KeyError, TypeError): + return getattr(obj, field) + + +@strawberry.type +class User: + name: str + + +@strawberry.type +class Query: + @strawberry.field + def user(self, info) -> User: # this won't type check, but will work at runtime + return {"name": "Patrick"} + + +schema = strawberry.Schema( + query=Query, config=StrawberryConfig(default_resolver=custom_resolver) +) +``` + +### relay_max_results + +By default Strawberry's max limit for relay connections is 100. You can +customise this by setting the `relay_max_results` configuration. + +```python +schema = strawberry.Schema(query=Query, config=StrawberryConfig(relay_max_results=50)) +``` + +### disable_field_suggestions + +By default Strawberry will suggest fields when a field is not found in the +schema. You can disable this feature by setting `disable_field_suggestions` to +`True`. + +```python +schema = strawberry.Schema( + query=Query, config=StrawberryConfig(disable_field_suggestions=True) +) +``` diff --git a/strawberry/schema/base.py b/strawberry/schema/base.py index a1c286c6d0..d70194e30e 100644 --- a/strawberry/schema/base.py +++ b/strawberry/schema/base.py @@ -87,6 +87,25 @@ def get_directive_by_name(self, graphql_name: str) -> Optional[StrawberryDirecti def as_str(self) -> str: raise NotImplementedError + @staticmethod + def remove_field_suggestion(error: GraphQLError) -> None: + if ( + error.message.startswith("Cannot query field") + and "Did you mean" in error.message + ): + error.message = error.message.split("Did you mean")[0].strip() + + def _process_errors( + self, + errors: List[GraphQLError], + execution_context: Optional[ExecutionContext] = None, + ) -> None: + if self.config.disable_field_suggestions: + for error in errors: + self.remove_field_suggestion(error) + + self.process_errors(errors, execution_context) + def process_errors( self, errors: List[GraphQLError], diff --git a/strawberry/schema/config.py b/strawberry/schema/config.py index 826a083493..75e93094d4 100644 --- a/strawberry/schema/config.py +++ b/strawberry/schema/config.py @@ -12,6 +12,7 @@ class StrawberryConfig: name_converter: NameConverter = field(default_factory=NameConverter) default_resolver: Callable[[Any, str], object] = getattr relay_max_results: int = 100 + disable_field_suggestions: bool = False def __post_init__( self, diff --git a/strawberry/schema/schema.py b/strawberry/schema/schema.py index e4f0a8402b..32014e9d8a 100644 --- a/strawberry/schema/schema.py +++ b/strawberry/schema/schema.py @@ -269,7 +269,7 @@ async def execute( execution_context_class=self.execution_context_class, execution_context=execution_context, allowed_operation_types=allowed_operation_types, - process_errors=self.process_errors, + process_errors=self._process_errors, ) return result @@ -301,7 +301,7 @@ def execute_sync( execution_context_class=self.execution_context_class, execution_context=execution_context, allowed_operation_types=allowed_operation_types, - process_errors=self.process_errors, + process_errors=self._process_errors, ) return result diff --git a/tests/schema/test_execution_errors.py b/tests/schema/test_execution_errors.py index 513904e4b6..1c93e4a4cb 100644 --- a/tests/schema/test_execution_errors.py +++ b/tests/schema/test_execution_errors.py @@ -1,6 +1,7 @@ import pytest import strawberry +from strawberry.schema.config import StrawberryConfig def test_runs_parsing(): @@ -64,3 +65,70 @@ class Query: assert len(result.errors) == 1 assert result.errors[0].message == "Syntax Error: Expected Name, found ." + + +def test_suggests_fields_by_default(): + @strawberry.type + class Query: + name: str + + schema = strawberry.Schema(query=Query) + + query = """ + query { + ample + } + """ + + result = schema.execute_sync(query) + + assert len(result.errors) == 1 + assert ( + result.errors[0].message + == "Cannot query field 'ample' on type 'Query'. Did you mean 'name'?" + ) + + +def test_can_disable_field_suggestions(): + @strawberry.type + class Query: + name: str + + schema = strawberry.Schema( + query=Query, config=StrawberryConfig(disable_field_suggestions=True) + ) + + query = """ + query { + ample + } + """ + + result = schema.execute_sync(query) + + assert len(result.errors) == 1 + assert result.errors[0].message == "Cannot query field 'ample' on type 'Query'." + + +def test_can_disable_field_suggestions_multiple_fields(): + @strawberry.type + class Query: + name: str + age: str + + schema = strawberry.Schema( + query=Query, config=StrawberryConfig(disable_field_suggestions=True) + ) + + query = """ + query { + ample + ag + } + """ + + result = schema.execute_sync(query) + + assert len(result.errors) == 2 + assert result.errors[0].message == "Cannot query field 'ample' on type 'Query'." + assert result.errors[1].message == "Cannot query field 'ag' on type 'Query'."