Add some validation to input arguments

This commit is contained in:
Jonathan Kim 2020-06-27 13:33:30 +01:00
parent 48f249af3b
commit 11e04df9c1
2 changed files with 159 additions and 4 deletions

View File

@ -1,9 +1,33 @@
from typing import List
from graphene.types.field import Field from graphene.types.field import Field
from graphene.types.inputobjecttype import InputObjectType
from graphene.types.scalars import Scalar
from graphene.utils.str_converters import to_camel_case from graphene.utils.str_converters import to_camel_case
class MutationInvalidArgumentsError(Exception):
def __init__(self, mutation_name: str, invalid_arguments: List[str]):
invalid_arguments = sorted(invalid_arguments)
if len(invalid_arguments) == 1:
message = (
f"Argument `{invalid_arguments[0]}` is not a valid type "
f"in mutation `{mutation_name}`. "
)
else:
head = ", ".join(invalid_arguments[:-1])
message = (
f"Arguments `{head}` and `{invalid_arguments[-1]}` are not valid types "
f"in mutation `{mutation_name}`. "
)
message += "Arguments to a mutation need to be either a Scalar type or an InputObjectType."
super().__init__(message)
def mutation(return_type, arguments=None, **kwargs): def mutation(return_type, arguments=None, **kwargs):
# TODO: validate input arguments
if arguments is None: if arguments is None:
arguments = {} arguments = {}
@ -11,13 +35,23 @@ def mutation(return_type, arguments=None, **kwargs):
name = kwargs.pop("name", None) or resolver_function.__name__ name = kwargs.pop("name", None) or resolver_function.__name__
description = kwargs.pop("description", None) or resolver_function.__doc__ description = kwargs.pop("description", None) or resolver_function.__doc__
invalid_arguments = []
for argument_name, argument in arguments.items():
if not (
isinstance(argument, Scalar) or isinstance(argument, InputObjectType)
):
invalid_arguments.append(argument_name)
if len(invalid_arguments) > 0:
raise MutationInvalidArgumentsError(name, invalid_arguments)
return Field( return Field(
return_type, return_type,
args=arguments, args=arguments,
name=to_camel_case(name), name=to_camel_case(name),
resolver=resolver_function, resolver=resolver_function,
description=description, description=description,
**kwargs **kwargs,
) )
return decorate return decorate

View File

@ -1,8 +1,10 @@
from textwrap import dedent from textwrap import dedent
from graphene import String, ObjectType, Schema, Union, Field import pytest
from ..mutation import mutation from graphene import Boolean, Field, InputObjectType, ObjectType, Schema, String, Union
from ..mutation import mutation, MutationInvalidArgumentsError
def test_mutation_basic(): def test_mutation_basic():
@ -160,3 +162,122 @@ def test_mutation_complex_return():
} }
""" """
) )
def test_mutation_complex_input():
class User(ObjectType):
name = String(required=True)
email = String(required=True)
class CreateUserSuccess(ObjectType):
user = Field(User, required=True)
class CreateUserError(ObjectType):
error_message = String(required=True)
class CreateUserOutput(Union):
class Meta:
types = [
CreateUserSuccess,
CreateUserError,
]
class CreateUserInput(InputObjectType):
name = String(required=True)
email = String(required=True)
@mutation(
CreateUserOutput,
required=True,
arguments={"user": CreateUserInput(required=True)},
)
def create_user(root, info, user):
return CreateUserSuccess(user=User(**user))
class Query(ObjectType):
a = String()
schema = Schema(query=Query, mutations=[create_user])
result = schema.execute(
"""
mutation CreateUserMutation {
createUser(user: { name: "Kate", email: "kate@example.com" }) {
__typename
... on CreateUserSuccess {
user {
name
}
}
}
}
"""
)
assert not result.errors
assert result.data == {
"createUser": {"__typename": "CreateUserSuccess", "user": {"name": "Kate"}}
}
assert str(schema) == dedent(
"""\
type Query {
a: String
}
type Mutation {
createUser(user: CreateUserInput!): CreateUserOutput!
}
union CreateUserOutput = CreateUserSuccess | CreateUserError
type CreateUserSuccess {
user: User!
}
type User {
name: String!
email: String!
}
type CreateUserError {
errorMessage: String!
}
input CreateUserInput {
name: String!
email: String!
}
"""
)
def test_raises_error_invalid_input():
class User(ObjectType):
name = String(required=True)
email = String(required=True)
with pytest.raises(MutationInvalidArgumentsError) as validation_error:
@mutation(
Boolean, required=True, arguments={"user": User},
)
def create_user(root, info, user):
return True
assert str(validation_error.value) == (
"Argument `user` is not a valid type in mutation `create_user`. "
"Arguments to a mutation need to be either a Scalar type or an InputObjectType."
)
with pytest.raises(MutationInvalidArgumentsError) as validation_error:
@mutation(
Boolean, required=True, arguments={"user": User, "user2": User},
)
def create_user2(root, info, user):
return True
assert str(validation_error.value) == (
"Arguments `user` and `user2` are not valid types in mutation `create_user2`. "
"Arguments to a mutation need to be either a Scalar type or an InputObjectType."
)