Package: graphene-validator
Graphene input validatorImportant: this is a proof of concept and most likely not ready for production use.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 intoextensionsfor validation errors because of the error messageValidationError.This library provides a class decorator@validated, for mutations, that allows for field level and input level validation similarly toDRFserializers'validatemethods. To validate a field you'll need to declare a static method namedvalidate_{field_name}. Input wide validation (e.g. for fields that depend on other fields) can be performed in thevalidatemethod.validatewill only be called if all field level validation methods succeed.It also supports recursive validation so that you can use nestedInputFields 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 ofValidationError. 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 invalidateand the mutation itself).AValidationErrorsubclass can report one or more validation errors. Itserror_detailsattribute must be an iterable of dictionaries, providing the details for the validation errors. The error detail mappings can contain any members, but as a conventioncodemember is encouraged to be included.For field level errors the error details will be amended with apathmember that helps the client determine which slice of input is invalid, useful for rich forms and field highlighting on the UI.ASingleValidationErrorclass is provided for validation errors that only contain a single error detail. This class also supports ametaerror 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).UsageValidating a mutation's inputHere is an example usage (which you can find intests.pyas well):importgraphenefromgraphene_validator.decoratorsimportvalidatedclassTestInput(graphene.InputObjectType):email=graphene.String()people=graphene.List(PersonalDataInput)numbers=graphene.List(graphene.Int)person=graphene.InputField(PersonalDataInput)@staticmethoddefvalidate_email(email,info,**input):if"@"notinemail:raiseInvalidEmailFormatreturnemail.strip(" ")@staticmethoddefvalidate_numbers(numbers,info,**input):iflen(numbers)<2:raiseLengthNotInRange(min=2)forninnumbers:ifn<0orn>9:raiseNotInRange(min=0,max=9)returnnumbers@staticmethoddefvalidate(input,info):ifinput.get("people")andinput.get("email"):first_person_name_and_age=(f"{input['people'][0]['the_name']}{input['people'][0]['the_age']}")ifinput["email"].split("@")[0]!=first_person_name_and_age:raiseNameAndAgeInEmailreturninput@validatedclassTestMutation(graphene.Mutation):classArguments:input=TestInput()result=graphene.String()defmutate(self,_info,input):returnTestMutation(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 contextclassTestInput(graphene.InputObjectType):first_field=graphene.String()second_field=graphene.String()@staticmethoddefvalidate_first_field(first_field,info,**input):second_field=input.get("second_field")ifsecond_field!="desired value":raiseInvalidSecondFieldifinfo.context.user.role!="admin":raiseUnauthorizedreturnfirst_field...Running testspip install -e .pytest tests.pyLimitationsSince errors are listed in theextensionsfield of a genericGraphQLError, instead of using the typicalunion 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 toschema.pyto allow the client to discover error types and their metadata (the latter is a TODO).