diff --git a/src/graphql/validation/validate.py b/src/graphql/validation/validate.py index d1b5818f..8f301396 100644 --- a/src/graphql/validation/validate.py +++ b/src/graphql/validation/validate.py @@ -2,7 +2,7 @@ from ..error import GraphQLError from ..language import DocumentNode, ParallelVisitor, visit -from ..pyutils import is_collection +from ..pyutils import inspect, is_collection from ..type import GraphQLSchema, assert_valid_schema from ..utilities import TypeInfo, TypeInfoVisitor from .rules import ASTValidationRule @@ -22,6 +22,7 @@ def validate( document_ast: DocumentNode, rules: Optional[Collection[Type[ASTValidationRule]]] = None, max_errors: Optional[int] = None, + type_info: Optional[TypeInfo] = None, ) -> List[GraphQLError]: """Implements the "Validation" section of the spec. @@ -38,6 +39,8 @@ def validate( Validate will stop validation after a ``max_errors`` limit has been reached. Attackers can send pathologically invalid queries to induce a DoS attack, so by default ``max_errors`` set to 100 errors. + + Providing a custom TypeInfo instance is deprecated and will be removed in v3.3. """ if not document_ast or not isinstance(document_ast, DocumentNode): raise TypeError("Must provide document.") @@ -47,6 +50,10 @@ def validate( max_errors = 100 elif not isinstance(max_errors, int): raise TypeError("The maximum number of errors must be passed as an int.") + if type_info is None: + type_info = TypeInfo(schema) + elif not isinstance(type_info, TypeInfo): + raise TypeError(f"Not a TypeInfo object: {inspect(type_info)}.") if rules is None: rules = specified_rules elif not is_collection(rules) or not all( @@ -69,7 +76,6 @@ def on_error(error: GraphQLError) -> None: raise ValidationAbortedError errors.append(error) - type_info = TypeInfo(schema) context = ValidationContext(schema, document_ast, type_info, on_error) # This uses a specialized visitor which runs multiple visitors in parallel, diff --git a/tests/validation/test_validation.py b/tests/validation/test_validation.py index 5c23ec69..0f7d80e6 100644 --- a/tests/validation/test_validation.py +++ b/tests/validation/test_validation.py @@ -2,7 +2,7 @@ from graphql.error import GraphQLError from graphql.language import parse -from graphql.utilities import build_schema +from graphql.utilities import TypeInfo, build_schema from graphql.validation import ValidationRule, validate from .harness import test_schema @@ -15,6 +15,14 @@ def rejects_invalid_documents(): assert validate(test_schema, None) # type: ignore assert str(exc_info.value) == "Must provide document." + def rejects_invalid_type_info(): + with raises(TypeError) as exc_info: + # noinspection PyTypeChecker + assert validate( + test_schema, parse("query { name }"), type_info={} # type: ignore + ) + assert str(exc_info.value) == "Not a TypeInfo object: {}." + def rejects_invalid_rules(): with raises(TypeError) as exc_info: # noinspection PyTypeChecker @@ -72,6 +80,37 @@ def detects_unknown_fields(): {"message": "Cannot query field 'unknown' on type 'QueryRoot'."} ] + def deprecated_validates_using_a_custom_type_info(): + # This TypeInfo will never return a valid field. + type_info = TypeInfo(test_schema, None, lambda *args: None) + + doc = parse( + """ + query { + human { + pets { + ... on Cat { + meowsVolume + } + ... on Dog { + barkVolume + } + } + } + } + """ + ) + + errors = validate(test_schema, doc, None, None, type_info) + + assert [error.message for error in errors] == [ + "Cannot query field 'human' on type 'QueryRoot'. Did you mean 'human'?", + "Cannot query field 'meowsVolume' on type 'Cat'." + " Did you mean 'meowsVolume'?", + "Cannot query field 'barkVolume' on type 'Dog'." + " Did you mean 'barkVolume'?", + ] + def validates_using_a_custom_rule(): schema = build_schema( """