Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom validators #268

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions graphql/backend/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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
Expand Down
46 changes: 45 additions & 1 deletion graphql/backend/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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