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 andinput_validator.pyfit 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 valuesthreshold— required number, minimum0channels— required array, at least one item, items must be one of["email", "sms", "chat"]
Files to create / edit
| File | What to do |
|---|---|
app/validators/schemas/create_alert_rule.json | JSON Schema definition |
app/validators/schema.py | Load and register the schema |
app/routes/admin/alert_rules.py | Call 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
| Input | Expected result |
|---|---|
| Valid body | 200 success |
Missing threshold | 1403 with descriptive message |
channels: [] | 1403 (minItems not met) |
channels: ["fax"] | 1403 (enum violation) |
threshold: -5 | 1403 (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
1403with a message that names the failing field - Tests cover all cases in the table above