Skip to main content

Task 08 — Add Input Validation with JSON Schema

Difficulty: Intermediate–Advanced
Goal: Add structured JSON Schema validation to a POST endpoint so malformed requests are rejected early with clear error messages, before they reach the service layer.


What you'll learn
  • How app/validators/ works (schema.py, validator_util.py, schemas/)
  • How to write a JSON Schema for a request body
  • How the @validate_values() decorator and input_validator.py fit together
  • How to write unit tests for the validator

Your task: Validate a "Create Alert Rule" request

Assume you have (or will build) a POST /api/admin/alerts/rules endpoint.
The request body should look like:

{
"industryId": "IND001",
"unitId": "U001",
"alertType": "HIGH_CONDUCTIVITY",
"threshold": 1500,
"channels": ["email", "sms"]
}

Write a JSON Schema that enforces:

  • industryId, unitId, alertType — required strings, no empty values
  • threshold — required number, minimum 0
  • channels — required array, at least one item, items must be one of ["email", "sms", "chat"]

Files to create / edit

FileWhat to do
app/validators/schemas/create_alert_rule.jsonJSON Schema definition
app/validators/schema.pyLoad and register the schema
app/routes/admin/alert_rules.pyCall the validator before the service

Schema file

{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "CreateAlertRule",
"type": "object",
"required": ["industryId", "unitId", "alertType", "threshold", "channels"],
"properties": {
"industryId": { "type": "string", "minLength": 1 },
"unitId": { "type": "string", "minLength": 1 },
"alertType": { "type": "string", "minLength": 1 },
"threshold": { "type": "number", "minimum": 0 },
"channels": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "enum": ["email", "sms", "chat"] }
}
},
"additionalProperties": false
}

Using the validator in the route

from flask_restx import marshal
from app.validators.validator_util import validate_request_body
from app.models.error_models import GenericErrorModel

@alertRulesNamespace.route('')
class AlertRulesRoute(Resource):
@jwt_required()
@admin_required()
def post(self):
body = request.get_json()
error = validate_request_body(body, 'create_alert_rule')
if error:
return marshal({'status': 'failed', 'status_code': 1403, 'message': error}, GenericErrorModel), 200

# proceed to service

Test cases to write

InputExpected result
Valid body200 success
Missing threshold1403 with descriptive message
channels: []1403 (minItems not met)
channels: ["fax"]1403 (enum violation)
threshold: -51403 (minimum violation)
Extra field "foo": "bar"1403 (additionalProperties)

Done when
  • Schema file exists and is registered
  • Valid bodies pass through to the service
  • Each invalid case returns 1403 with a message that names the failing field
  • Tests cover all cases in the table above