html_url,issue_url,id,node_id,user,user_label,created_at,updated_at,author_association,body,reactions,issue,issue_label,performed_via_github_app https://github.com/simonw/datasette/issues/1949#issuecomment-1352411327,https://api.github.com/repos/simonw/datasette/issues/1949,1352411327,IC_kwDOBm6k_c5QnCi_,9599,simonw,2022-12-15T00:46:27Z,2022-12-15T00:46:27Z,OWNER,"I got this far: ```diff diff --git a/datasette/handle_exception.py b/datasette/handle_exception.py index 8b7e83e3..31d41e00 100644 --- a/datasette/handle_exception.py +++ b/datasette/handle_exception.py @@ -54,7 +54,17 @@ def handle_exception(datasette, request, exception): headers = {} if datasette.cors: add_cors_headers(headers) - if request.path.split(""?"")[0].endswith("".json""): + # Return JSON error under certain conditions + should_return_json = ( + # URL ends in .json + request.path.split(""?"")[0].endswith("".json"") + or + # Hints from incoming request headers + request.headers.get(""content-type"") == ""application/json"" + or ""application/json"" in request.headers.get(""accept"", """") + ) + breakpoint() + if should_return_json: return Response.json(info, status=status, headers=headers) else: template = datasette.jinja_env.select_template(templates) diff --git a/tests/test_api_write.py b/tests/test_api_write.py index f27d143f..982543a6 100644 --- a/tests/test_api_write.py +++ b/tests/test_api_write.py @@ -1140,6 +1140,38 @@ async def test_create_table_permissions( assert data[""errors""] == expected_errors +@pytest.mark.asyncio +@pytest.mark.parametrize( + ""headers,expect_json"", + ( + ({}, False), + ({""Accept"": ""text/html""}, True), + ({""Accept"": ""application/json""}, True), + ({""Content-Type"": ""application/json""}, True), + ({""Accept"": ""application/json, text/plain, */*""}, True), + ({""Content-Type"": ""application/json""}, True), + ({""accept"": ""application/json, text/plain, */*""}, True), + ({""content-type"": ""application/json""}, True), + ), +) +async def test_permission_errors_html_and_json(ds_write, headers, expect_json): + request_headers = {""Authorization"": ""Bearer bad_token""} + request_headers.update(headers) + response = await ds_write.client.post( + ""/data/-/create"", + json={}, + headers=request_headers, + ) + assert response.status_code == 403 + if expect_json: + data = response.json() + assert data[""ok""] is False + assert data[""errors""] == [""Permission denied""] + else: + assert response.headers[""Content-Type""] == ""text/html; charset=utf-8"" + assert ""Permission denied"" in response.text + + @pytest.mark.asyncio @pytest.mark.parametrize( ""input,expected_rows_after"", ``` Then decided I would punt this until the next milestone.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",1493471221,`.json` errors should be returned as JSON, https://github.com/simonw/datasette/issues/1949#issuecomment-1352378370,https://api.github.com/repos/simonw/datasette/issues/1949,1352378370,IC_kwDOBm6k_c5Qm6gC,9599,simonw,2022-12-15T00:02:08Z,2022-12-15T00:04:54Z,OWNER,"I fixed this issue to help research this further: - https://github.com/simonw/datasette-ripgrep/issues/26 Now this search works: I wish I had this feature! - https://github.com/simonw/datasette-ripgrep/issues/24 Looks like I have both `_error()` and `_errors()` functions in there! ","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",1493471221,`.json` errors should be returned as JSON,