Git Product home page Git Product logo

graphene-validator's Introduction

Graphene input validator

Quickstart

pip install graphene-validator

from graphene_validator.decorators import validated

@validated
class MyMutation(graphene.Mutation):
    ...

Description

The GraphQL Python ecosystem (i.e. graphene) lacks a proper way of validating input and returning meaningful errors to the client. This PoC aims at solving that. The client will know it needs to look into extensions for validation errors because of the error message ValidationError.

This library provides a class decorator @validated, for mutations, that allows for field level and input level validation similarly to DRF serializers' validate methods. To validate a field you'll need to declare a static method named validate_{field_name}. Input wide validation (e.g. for fields that depend on other fields) can be performed in the validate method. validate will only be called if all field level validation methods succeed.

It also supports recursive validation so that you can use nested InputFields and validation will be performed all the way down to the scalars.

To indicate an invalid value the corresponding validation method should raise an instance of a subclass of ValidationError. Validation methods also allow to manipulate the value on the fly (for example to minimize DB queries by swapping an ID for the corresponding object) which will then replace the corresponding value in the main input (to be used in validate and the mutation itself).

A ValidationError subclass can report one or more validation errors. Its error_details attribute must be an iterable of dictionaries, providing the details for the validation errors. The error detail mappings can contain any members, but as a convention code member is encouraged to be included.

For field level errors the error details will be amended with a path member that helps the client determine which slice of input is invalid, useful for rich forms and field highlighting on the UI.

A SingleValidationError class is provided for validation errors that only contain a single error detail. This class also supports a meta error detail property, to inform the clients of potential constraints on the input itself.

Note that verbose messages aren't supported because I strongly believe those should be handled on the client (together with localization).

Usage

Validating a mutation's input

Here is an example usage (which you can find in tests.py as well):

import graphene
from graphene_validator.decorators import validated

class TestInput(graphene.InputObjectType):
    email = graphene.String()
    people = graphene.List(PersonalDataInput)
    numbers = graphene.List(graphene.Int)
    person = graphene.InputField(PersonalDataInput)

    @staticmethod
    def validate_email(email, info, **input):
        if "@" not in email:
            raise InvalidEmailFormat
        return email.strip(" ")

    @staticmethod
    def validate_numbers(numbers, info, **input):
        if len(numbers) < 2:
            raise LengthNotInRange(min=2)
        for n in numbers:
            if n < 0 or n > 9:
                raise NotInRange(min=0, max=9)
        return numbers

    @staticmethod
    def validate(input, info):
        if input.get("people") and input.get("email"):
            first_person_name_and_age = (
                f"{input['people'][0]['the_name']}{input['people'][0]['the_age']}"
            )
            if input["email"].split("@")[0] != first_person_name_and_age:
                raise NameAndAgeInEmail
        return input


@validated
class TestMutation(graphene.Mutation):
    class Arguments:
        input = TestInput()

    result = graphene.String()

    def mutate(self, _info, input):
        return TestMutation(result="ok"))

And this is an example output:

{
            "errors": [
                {
                    "message": "ValidationError",
                    ...
                    "extensions": {
                        "validationErrors": [
                            {
                                "code": "InvalidEmailFormat",
                                "path": [
                                    "email"
                                ]
                            },
                            {
                                "code": "LengthNotInRange",
                                "path": [
                                    "people",
                                    0,
                                    "name"
                                ],
                                "meta": {"min": 1, "max": 300}
                            }
                        ]
                    }
                }
            ],
            ...
        }

Validating a field that depends on other fields or the request's context

class TestInput(graphene.InputObjectType):
    first_field = graphene.String()
    second_field = graphene.String()

    @staticmethod
    def validate_first_field(first_field, info, **input):
        second_field = input.get("second_field")
        if second_field != "desired value":
            raise InvalidSecondField
        if info.context.user.role != "admin":
            raise Unauthorized
        return first_field

    ...

Running tests

pip install -e .

pytest tests.py

Limitations

Since errors are listed in the extensions field of a generic GraphQLError, instead of using the typical union based errors, errors aren't automatically discoverable. The ideal solution would be a hybrid that allows to decorate the mutation and obtain a union that can be used by the client for autodiscovery of the error types and metadata.

An example graphene-django query is added to schema.py to allow the client to discover error types and their metadata (the latter is a TODO).

graphene-validator's People

Contributors

akikoskinen avatar chpmrc avatar dependabot[bot] avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.