mirror of
https://github.com/simonw/datasette.git
synced 2026-07-10 09:34:35 +02:00
All JSON error responses now use a single format built by the new
datasette.utils.error_body() helper:
{"ok": false, "error": "...", "errors": ["..."], "status": 400}
- error is all messages joined with '; ', errors is the full list,
status always matches the HTTP status code
- The exception handler no longer emits the legacy title key in JSON
(it is still available to the HTML error template)
- The permission debug endpoints (/-/allowed, /-/rules, /-/check,
POST /-/permissions) no longer return bare {"error": ...} objects
- JSON renderer SQL errors keep their rows/truncated context keys but
now include the canonical keys as well
- _shape=object misuse (queries or tables without primary keys) now
returns HTTP 400 instead of 200 with an error body
- Method-not-allowed 405 responses use the canonical shape
Adds tests/test_error_shape.py covering all four previous shape
producers, updates affected tests, and documents the format in a new
'Error responses' section of docs/json_api.rst.
Implements section 1 of stable-api-recommendations.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ
77 lines
2.1 KiB
Python
77 lines
2.1 KiB
Python
from datasette import hookimpl, Response
|
|
from .utils import add_cors_headers, error_body
|
|
from .utils.asgi import (
|
|
Base400,
|
|
)
|
|
from .views.base import DatasetteError
|
|
from markupsafe import Markup
|
|
import traceback
|
|
|
|
try:
|
|
import ipdb as pdb
|
|
except ImportError:
|
|
import pdb
|
|
|
|
try:
|
|
import rich
|
|
except ImportError:
|
|
rich = None
|
|
|
|
|
|
@hookimpl(trylast=True)
|
|
def handle_exception(datasette, request, exception):
|
|
async def inner():
|
|
if datasette.pdb:
|
|
pdb.post_mortem(exception.__traceback__)
|
|
|
|
if rich is not None:
|
|
rich.get_console().print_exception(show_locals=True)
|
|
|
|
title = None
|
|
if isinstance(exception, Base400):
|
|
status = exception.status
|
|
info = {}
|
|
message = exception.args[0]
|
|
elif isinstance(exception, DatasetteError):
|
|
status = exception.status
|
|
info = exception.error_dict
|
|
message = exception.message
|
|
if exception.message_is_html:
|
|
message = Markup(message)
|
|
title = exception.title
|
|
else:
|
|
status = 500
|
|
info = {}
|
|
message = str(exception)
|
|
traceback.print_exc()
|
|
templates = [f"{status}.html", "error.html"]
|
|
headers = {}
|
|
if datasette.cors:
|
|
add_cors_headers(headers)
|
|
if request.path.split("?")[0].endswith(".json"):
|
|
body = dict(info)
|
|
body.update(error_body(message, status))
|
|
return Response.json(body, status=status, headers=headers)
|
|
info.update(
|
|
{
|
|
"ok": False,
|
|
"error": message,
|
|
"status": status,
|
|
"title": title,
|
|
}
|
|
)
|
|
environment = datasette.get_jinja_environment(request)
|
|
template = environment.select_template(templates)
|
|
return Response.html(
|
|
await template.render_async(
|
|
dict(
|
|
info,
|
|
urls=datasette.urls,
|
|
menu_links=lambda: [],
|
|
)
|
|
),
|
|
status=status,
|
|
headers=headers,
|
|
)
|
|
|
|
return inner
|