diff --git a/graphql/backend/core.py b/graphql/backend/core.py index e8fc3099..1246da8b 100644 --- a/graphql/backend/core.py +++ b/graphql/backend/core.py @@ -5,12 +5,13 @@ from ..execution import execute, ExecutionResult from ..language.base import parse, print_ast from ..language import ast -from ..validation import validate +from ..validation import validate, specified_rules from .base import GraphQLBackend, GraphQLDocument # Necessary for static type checking if False: # flake8: noqa - from typing import Any, Optional, Union + from typing import Any, Optional, Union, List, Type + from ..validation.rules import ValidationRule from ..language.ast import Document from ..type.schema import GraphQLSchema from rx import Observable @@ -24,8 +25,9 @@ def execute_and_validate( ): # type: (...) -> Union[ExecutionResult, Observable] do_validation = kwargs.get("validate", True) + validation_rules = kwargs.get("validation_rules", specified_rules) if do_validation: - validation_errors = validate(schema, document_ast) + validation_errors = validate(schema, document_ast, validation_rules) if validation_errors: return ExecutionResult(errors=validation_errors, invalid=True) @@ -38,7 +40,14 @@ class GraphQLCoreBackend(GraphQLBackend): def __init__(self, executor=None): # type: (Optional[Any]) -> None - self.execute_params = {"executor": executor} + self.execute_params = { + "executor": executor, + "validation_rules": self.get_validation_rules(), + } + + def get_validation_rules(self): + # type: () -> List[Type[ValidationRule]] + return specified_rules def document_from_string(self, schema, document_string): # type: (GraphQLSchema, Union[Document, str]) -> GraphQLDocument diff --git a/graphql/backend/tests/test_core.py b/graphql/backend/tests/test_core.py index 257eb64b..39fb641c 100644 --- a/graphql/backend/tests/test_core.py +++ b/graphql/backend/tests/test_core.py @@ -3,14 +3,19 @@ """Tests for `graphql.backend.core` module.""" import pytest + +from graphql import GraphQLError from graphql.execution.executors.sync import SyncExecutor +from graphql.validation.rules.base import ValidationRule from ..base import GraphQLBackend, GraphQLDocument from ..core import GraphQLCoreBackend from .schema import schema if False: - from typing import Any + from pytest_mock import MockFixture + from typing import Any, List, Optional, Type + from graphql.language.ast import Document def test_core_backend(): @@ -52,3 +57,42 @@ def test_backend_can_execute_custom_executor(): assert not result.errors assert result.data == {"hello": "World"} assert executor.executed + + +class AlwaysFailValidator(ValidationRule): + # noinspection PyPep8Naming + def enter_Document(self, node, key, parent, path, ancestors): + # type: (Document, Optional[Any], Optional[Any], List, List) -> None + self.context.report_error(GraphQLError("Test validator failure", [node])) + + +class CustomValidatorBackend(GraphQLCoreBackend): + def get_validation_rules(self): + # type: () -> List[Type[ValidationRule]] + return [AlwaysFailValidator] + + +def test_backend_custom_validators_result(): + # type: () -> None + backend = CustomValidatorBackend() + assert isinstance(backend, CustomValidatorBackend) + document = backend.document_from_string(schema, "{ hello }") + assert isinstance(document, GraphQLDocument) + result = document.execute() + assert result.errors + assert len(result.errors) == 1 + assert result.errors[0].message == "Test validator failure" + + +def test_backend_custom_validators_in_validation_args(mocker): + # type: (MockFixture) -> None + mocked_validate = mocker.patch("graphql.backend.core.validate") + backend = CustomValidatorBackend() + assert isinstance(backend, CustomValidatorBackend) + document = backend.document_from_string(schema, "{ hello }") + assert isinstance(document, GraphQLDocument) + mocked_validate.assert_not_called() + result = document.execute() + mocked_validate.assert_called_once() + (args, kwargs) = mocked_validate.call_args + assert [AlwaysFailValidator] in args