Skip to content

Commit

Permalink
Add some validation to input arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
jkimbo committed Jun 27, 2020
1 parent 48f249a commit 11e04df
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 4 deletions.
38 changes: 36 additions & 2 deletions graphene/experimental/decorators/mutation.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
from typing import List

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


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):
# TODO: validate input arguments
if arguments is None:
arguments = {}

def decorate(resolver_function):
name = kwargs.pop("name", None) or resolver_function.__name__
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_type,
args=arguments,
name=to_camel_case(name),
resolver=resolver_function,
description=description,
**kwargs
**kwargs,
)

return decorate
125 changes: 123 additions & 2 deletions graphene/experimental/decorators/tests/test_mutation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
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():
Expand Down Expand Up @@ -160,3 +162,122 @@ class Query(ObjectType):
}
"""
)


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."
)

0 comments on commit 11e04df

Please sign in to comment.