diff --git a/graphene_django/views.py b/graphene_django/views.py index c6090b08c..d7d74b8e9 100644 --- a/graphene_django/views.py +++ b/graphene_django/views.py @@ -9,10 +9,18 @@ from django.utils.decorators import method_decorator from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import View -from graphql import OperationType, get_operation_ast, parse +from graphql import ( + ExecutionResult, + OperationType, + execute, + get_operation_ast, + parse, + validate_schema, +) from graphql.error import GraphQLError -from graphql.execution import ExecutionResult from graphql.execution.middleware import MiddlewareManager +from graphql.language import OperationDefinitionNode +from graphql.validation import validate from graphene import Schema from graphene_django.constants import MUTATION_ERRORS_FLAG @@ -295,56 +303,82 @@ def execute_graphql_request( return None raise HttpError(HttpResponseBadRequest("Must provide query string.")) + schema_validation_errors = validate_schema(self.schema.graphql_schema) + if schema_validation_errors: + return ExecutionResult(data=None, errors=schema_validation_errors) + try: document = parse(query) except Exception as e: return ExecutionResult(errors=[e]) - if request.method.lower() == "get": - operation_ast = get_operation_ast(document, operation_name) - if operation_ast and operation_ast.operation != OperationType.QUERY: - if show_graphiql: - return None + operation_ast = get_operation_ast(document, operation_name) - raise HttpError( - HttpResponseNotAllowed( - ["POST"], - "Can only perform a {} operation from a POST request.".format( - operation_ast.operation.value - ), - ) + if not operation_ast: + ops_count = len( + [ + x + for x in document.definitions + if isinstance(x, OperationDefinitionNode) + ] + ) + if ops_count > 1: + op_error = ( + "Must provide operation name if query contains multiple operations." ) - try: - extra_options = {} - if self.execution_context_class: - extra_options["execution_context_class"] = self.execution_context_class + elif operation_name: + op_error = f"Unknown operation named '{operation_name}'." + else: + op_error = "Must provide a valid operation." + + return ExecutionResult(errors=[GraphQLError(op_error)]) + + if ( + request.method.lower() == "get" + and operation_ast.operation != OperationType.QUERY + ): + if show_graphiql: + return None + + raise HttpError( + HttpResponseNotAllowed( + ["POST"], + "Can only perform a {} operation from a POST request.".format( + operation_ast.operation.value + ), + ) + ) + + execute_args = (self.schema.graphql_schema, document) + validation_errors = validate(*execute_args) + + if validation_errors: + return ExecutionResult(data=None, errors=validation_errors) - options = { - "source": query, + try: + execute_options = { "root_value": self.get_root_value(request), + "context_value": self.get_context(request), "variable_values": variables, "operation_name": operation_name, - "context_value": self.get_context(request), "middleware": self.get_middleware(request), } - options.update(extra_options) + if self.execution_context_class: + execute_options[ + "execution_context_class" + ] = self.execution_context_class - operation_ast = get_operation_ast(document, operation_name) if ( - operation_ast - and operation_ast.operation == OperationType.MUTATION - and ( - graphene_settings.ATOMIC_MUTATIONS is True - or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True - ) - ): + graphene_settings.ATOMIC_MUTATIONS is True + or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True + ) and operation_ast.operation == OperationType.MUTATION: with transaction.atomic(): - result = self.schema.execute(**options) + result = execute(*execute_args, **execute_options) if getattr(request, MUTATION_ERRORS_FLAG, False) is True: transaction.set_rollback(True) return result - return self.schema.execute(**options) + return execute(*execute_args, **execute_options) except Exception as e: return ExecutionResult(errors=[e])