Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions endpoint/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@
import json

import werkzeug.exceptions
import werkzeug.wrappers

from odoo.http import Response

class RequestValidationError(werkzeug.exceptions.BadRequest):
"""Bad request raised when the body fails JSON Schema validation.

Emits ``{"detail": [{"loc", "msg", "type"}, ...]}`` (FastAPI-style)
instead of the generic werkzeug HTML body.
class RequestValidationError(werkzeug.exceptions.HTTPException):
"""Raised when the body fails JSON/XML schema validation.

Emits ``{"detail": [{"loc", "msg", "type"}, ...]}`` (FastAPI-style) with a
400 status instead of the generic werkzeug HTML body.

``code`` is left as ``None`` on purpose: this makes Odoo treat the
exception as a ready-made response and run the normal ``post_dispatch``
hook on it, so route-managed headers (CORS, CSP, ...) are preserved. A real
``code`` (e.g. via ``BadRequest``) would route through ``handle_error``
instead and drop those headers.
"""

code = None

def __init__(self, detail):
super().__init__()
self.detail = detail

def get_response(self, environ=None, scope=None):
return werkzeug.wrappers.Response(
return Response(
json.dumps({"detail": self.detail}),
status=self.code,
status=400,
mimetype="application/json",
)
15 changes: 15 additions & 0 deletions endpoint/tests/test_endpoint_content_schema_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,21 @@ def test_json_invalid_body(self):
self.assertEqual(payload["detail"][0]["loc"], ["body", "data"])
self.assertEqual(payload["detail"][0]["type"], "type")

@mute_logger("endpoint.endpoint")
def test_validation_error_preserves_cors_headers(self):
response = self.url_open(
"/demo/schema",
data=json.dumps({"data": "not-an-array"}),
headers={
"Content-Type": "application/json",
"Origin": "https://editor.swagger.io",
},
)
self.assertEqual(response.status_code, 400)
# Route-managed CORS headers (default cors="*") must survive on the
# validation-error response, not only on a successful one.
self.assertEqual(response.headers.get("Access-Control-Allow-Origin"), "*")

@mute_logger("endpoint.endpoint")
def test_json_malformed_body(self):
response = self.url_open(
Expand Down
Loading