validate.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. from typing import Collection, List, Optional, Type
  2. from ..error import GraphQLError
  3. from ..language import DocumentNode, ParallelVisitor, visit
  4. from ..type import GraphQLSchema, assert_valid_schema
  5. from ..pyutils import inspect, is_collection
  6. from ..utilities import TypeInfo, TypeInfoVisitor
  7. from .rules import ASTValidationRule
  8. from .specified_rules import specified_rules, specified_sdl_rules
  9. from .validation_context import SDLValidationContext, ValidationContext
  10. __all__ = ["assert_valid_sdl", "assert_valid_sdl_extension", "validate", "validate_sdl"]
  11. class ValidationAbortedError(RuntimeError):
  12. """Error when a validation has been aborted (error limit reached)."""
  13. def validate(
  14. schema: GraphQLSchema,
  15. document_ast: DocumentNode,
  16. rules: Optional[Collection[Type[ASTValidationRule]]] = None,
  17. max_errors: Optional[int] = None,
  18. type_info: Optional[TypeInfo] = None,
  19. ) -> List[GraphQLError]:
  20. """Implements the "Validation" section of the spec.
  21. Validation runs synchronously, returning a list of encountered errors, or an empty
  22. list if no errors were encountered and the document is valid.
  23. A list of specific validation rules may be provided. If not provided, the default
  24. list of rules defined by the GraphQL specification will be used.
  25. Each validation rule is a ValidationRule object which is a visitor object that holds
  26. a ValidationContext (see the language/visitor API). Visitor methods are expected to
  27. return GraphQLErrors, or lists of GraphQLErrors when invalid.
  28. Validate will stop validation after a ``max_errors`` limit has been reached.
  29. Attackers can send pathologically invalid queries to induce a DoS attack,
  30. so by default ``max_errors`` set to 100 errors.
  31. Providing a custom TypeInfo instance is deprecated and will be removed in v3.3.
  32. """
  33. if not document_ast or not isinstance(document_ast, DocumentNode):
  34. raise TypeError("Must provide document.")
  35. # If the schema used for validation is invalid, throw an error.
  36. assert_valid_schema(schema)
  37. if max_errors is None:
  38. max_errors = 100
  39. elif not isinstance(max_errors, int):
  40. raise TypeError("The maximum number of errors must be passed as an int.")
  41. if type_info is None:
  42. type_info = TypeInfo(schema)
  43. elif not isinstance(type_info, TypeInfo):
  44. raise TypeError(f"Not a TypeInfo object: {inspect(type_info)}.")
  45. if rules is None:
  46. rules = specified_rules
  47. elif not is_collection(rules) or not all(
  48. isinstance(rule, type) and issubclass(rule, ASTValidationRule) for rule in rules
  49. ):
  50. raise TypeError(
  51. "Rules must be specified as a collection of ASTValidationRule subclasses."
  52. )
  53. errors: List[GraphQLError] = []
  54. def on_error(error: GraphQLError) -> None:
  55. if len(errors) >= max_errors: # type: ignore
  56. errors.append(
  57. GraphQLError(
  58. "Too many validation errors, error limit reached."
  59. " Validation aborted."
  60. )
  61. )
  62. raise ValidationAbortedError
  63. errors.append(error)
  64. context = ValidationContext(schema, document_ast, type_info, on_error)
  65. # This uses a specialized visitor which runs multiple visitors in parallel,
  66. # while maintaining the visitor skip and break API.
  67. visitors = [rule(context) for rule in rules]
  68. # Visit the whole document with each instance of all provided rules.
  69. try:
  70. visit(document_ast, TypeInfoVisitor(type_info, ParallelVisitor(visitors)))
  71. except ValidationAbortedError:
  72. pass
  73. return errors
  74. def validate_sdl(
  75. document_ast: DocumentNode,
  76. schema_to_extend: Optional[GraphQLSchema] = None,
  77. rules: Optional[Collection[Type[ASTValidationRule]]] = None,
  78. ) -> List[GraphQLError]:
  79. """Validate an SDL document.
  80. For internal use only.
  81. """
  82. errors: List[GraphQLError] = []
  83. context = SDLValidationContext(document_ast, schema_to_extend, errors.append)
  84. if rules is None:
  85. rules = specified_sdl_rules
  86. visitors = [rule(context) for rule in rules]
  87. visit(document_ast, ParallelVisitor(visitors))
  88. return errors
  89. def assert_valid_sdl(document_ast: DocumentNode) -> None:
  90. """Assert document is valid SDL.
  91. Utility function which asserts a SDL document is valid by throwing an error if it
  92. is invalid.
  93. """
  94. errors = validate_sdl(document_ast)
  95. if errors:
  96. raise TypeError("\n\n".join(error.message for error in errors))
  97. def assert_valid_sdl_extension(
  98. document_ast: DocumentNode, schema: GraphQLSchema
  99. ) -> None:
  100. """Assert document is a valid SDL extension.
  101. Utility function which asserts a SDL document is valid by throwing an error if it
  102. is invalid.
  103. """
  104. errors = validate_sdl(document_ast, schema)
  105. if errors:
  106. raise TypeError("\n\n".join(error.message for error in errors))