Files
JQC_multi_tenant/app/api/errors.py
T

99 lines
2.7 KiB
Python

"""
app/api/errors.py
-----------------
Consistent JSON error responses for every API endpoint.
Every response — success or failure — uses the same envelope:
{
"ok": true | false,
"data": { ... } | null,
"error": null | "Human-readable message"
}
Usage
-----
from app.api.errors import api_error, api_ok
return api_ok({'inspection': {...}})
return api_error('Inspection not found', 404)
"""
from flask import jsonify
def api_ok(data=None, status=200):
"""Return a successful JSON response."""
return jsonify({
'ok': True,
'data': data,
'error': None,
}), status
def api_error(message: str, status: int = 400, extra: dict | None = None):
"""Return an error JSON response.
`extra` merges additional top-level keys into the envelope — for flags a
client must branch on rather than parse out of the message, e.g.
`mfa_required` on a login that needs a second factor. Reserved keys
(ok/data/error) always win, so a caller cannot accidentally rewrite the
envelope's shape.
"""
payload = {
'ok': False,
'data': None,
'error': message,
}
if extra:
for k, v in extra.items():
payload.setdefault(k, v)
return jsonify(payload), status
# ── Registered error handlers (attached to the api blueprint) ─────────────────
def register_error_handlers(bp):
"""
Attach JSON error handlers to the given blueprint so that Flask
exceptions (404, 405, 500, etc.) return JSON instead of HTML within
the /api/v1/ prefix.
"""
from werkzeug.exceptions import HTTPException
@bp.errorhandler(400)
def bad_request(e):
return api_error(str(e.description) if hasattr(e, 'description') else 'Bad request', 400)
@bp.errorhandler(401)
def unauthorized(e):
return api_error('Authentication required', 401)
@bp.errorhandler(403)
def forbidden(e):
return api_error('Access denied', 403)
@bp.errorhandler(404)
def not_found(e):
return api_error('Resource not found', 404)
@bp.errorhandler(405)
def method_not_allowed(e):
return api_error('Method not allowed', 405)
@bp.errorhandler(413)
def payload_too_large(e):
return api_error('Uploaded file is too large', 413)
@bp.errorhandler(422)
def unprocessable(e):
return api_error('Unprocessable request', 422)
@bp.errorhandler(500)
def internal_error(e):
return api_error('Internal server error', 500)
@bp.errorhandler(HTTPException)
def generic_http(e):
return api_error(e.description or e.name, e.code)