API Request, Payload & DTO validation
API Request, Payload & DTO validation
Schema validation for incoming API request can be done by using utility functions and decorators provided in frappe_utils
Example
Lets consider we have to make an API that accepts following persons
information.
{
"name": "Jon",
"age": 18,
"email": "jon@castlecraft.com",
"addresses": [
{
"city": "Mumbai",
"state": "MH",
"zip_code": "400068"
}
]
}
We create a whitelisted
endpoint in frappe to accept this request
@frappe.whitelist(methods=["POST"])
def create_person():
# your logic
return True
There are few validation that we would we want to make sure before the API call reaches the business logic such as - All required fields are provided - Fields with email are valid emails - Fields length are as expected - Nested fields are not null - etc
For defining our schema we can use python classes with marshmallow
Note: If your not using
frappe_utils
as a part of your frappe site you would have to installmarshmallow
or add it to your respective project dependency.
Create a schema for your anticipated request
from marshmallow import Schema, fields
class AddressSchema(Schema):
street = fields.Str(required=True, validate=lambda s: len(s) > 0)
city = fields.Str(required=True, validate=lambda s: len(s) > 0)
zip_code = fields.Str(required=True, validate=lambda s: len(s) > 0)
class PersonSchema(Schema):
name = fields.Str(
required=True,
)
age = fields.Int(required=True)
email = fields.Email(required=True)
addresses = fields.List(fields.Nested(AddressSchema), required=True)
We can now append this schema validation onto our whitelisted function
import frappe
from frappe_utils.request_validator.request_validator import request_validator
@frappe.whitelist(methods=["POST"])
@request_validator(PersonSchema)
def create_person(*args, **kwargs):
# body = kwargs.get(RequestAttributes.body.value)
# your logic
return True
Above will make sure to validate incoming request with validation as per the schema definitions.
Note: Request body is always available as a part of
kwargs
and could be fetched as shown above.
Custom Validation
Lets consider a few cases
- Custom error message mapping (default we give string message)
- Have addition attributes as a part of kwargs
you can use provided config and overwrite options to achieve the same
Schema
Complete docs on writing custom validation
# Schema with custom validation and allowed unknown fields
class PersonSchemaCustom(Schema):
name = fields.Str(required=True, validate=lambda s: len(s) > 0)
age = fields.Int(required=True, validate=lambda n: 18 <= n <= 150)
email = fields.Email(required=True)
addresses = fields.List(fields.Nested(AddressSchema), required=True)
# Custom validation for any field
@validates_schema
def validate_addresses(self, data, **kwargs):
addresses = data.get("addresses")
if not addresses or len(addresses) == 0:
raise ValidationError("Addresses must not be empty")
# To allow fields that are not defined in schema
class Meta:
unknown = True
Python Options
def my_custom_error_function(e):
return "CUSTOM ERROR MESSAGE!"
@frappe.whitelist(methods=["POST"])
@request_validator(
PersonSchemaCustom,
{
"validation_error_parser": my_custom_error_function,
"request_attributes": [
RequestAttributes.body,
RequestAttributes.user,
RequestAttributes.request,
RequestAttributes.query,
],
},
)
def create_person(*args, **kwargs):
body = kwargs.get(RequestAttributes.body.value)
user = kwargs.get(RequestAttributes.user.value)
query = kwargs.get(RequestAttributes.query.value)
# Full Request
request = kwargs.get(RequestAttributes.request.value)
return body, user, query
Overwrite options for request_validator
Value | Defaults | Details |
---|---|---|
validation_error_parser |
Returns readable error string | Accepts a callable function that takes 1 argument of type ValidateError from Marshmallow , function provided to this option will be called when there is error in validation of request and whatever the function returns will be appended as the error for API response. Current: {"status_code": 400, "error": "Field: name, Errors: Missing data for required field.\n.."} Overwritten: {"status_code": 400, "error": "Your Custom Error"} |
request_attributes |
[RequestAttributes.body ] |
Provide options for any attributes that you would need and it would be appended to kwargs parameter. Default body is available as in kwargs , you can append RequestAttributes.user For getting session user RequestAttributes.request For complete request RequestAttributes.query For request query |