Skip to content

Commit

Permalink
Use media handlers to serialize errors by accepted content type
Browse files Browse the repository at this point in the history
  • Loading branch information
copalco committed Dec 3, 2023
1 parent ff5be58 commit 0e4b6f8
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 deletions.
20 changes: 12 additions & 8 deletions falcon/app_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

"""Utilities for the App class."""

from collections import OrderedDict
from inspect import iscoroutinefunction
from typing import IO, Iterable, List, Tuple
from typing import Optional

from falcon import util
from falcon.constants import MEDIA_JSON
Expand Down Expand Up @@ -226,14 +228,14 @@ def default_serialize_error(req: Request, resp: Response, exception: HTTPError):
resp: Instance of ``falcon.Response``
exception: Instance of ``falcon.HTTPError``
"""
preferred = _negotiate_preffered_media_type(req)
preferred = _negotiate_preffered_media_type(req, resp)

Check warning on line 231 in falcon/app_helpers.py

View check run for this annotation

Codecov / codecov/patch

falcon/app_helpers.py#L231

Added line #L231 was not covered by tests

if preferred is not None:
if preferred == MEDIA_JSON:
handler, _, _ = resp.options.media_handlers._resolve(
MEDIA_JSON, MEDIA_JSON, raise_not_found=False
)
resp.data = exception.to_json(handler)
handler, _, _ = resp.options.media_handlers._resolve(

Check warning on line 234 in falcon/app_helpers.py

View check run for this annotation

Codecov / codecov/patch

falcon/app_helpers.py#L234

Added line #L234 was not covered by tests
preferred, MEDIA_JSON, raise_not_found=False
)
if handler:
resp.data = handler.serialize(exception.to_dict(OrderedDict), preferred)

Check warning on line 238 in falcon/app_helpers.py

View check run for this annotation

Codecov / codecov/patch

falcon/app_helpers.py#L238

Added line #L238 was not covered by tests
else:
resp.data = exception.to_xml()

Check warning on line 240 in falcon/app_helpers.py

View check run for this annotation

Codecov / codecov/patch

falcon/app_helpers.py#L240

Added line #L240 was not covered by tests

Expand All @@ -244,8 +246,10 @@ def default_serialize_error(req: Request, resp: Response, exception: HTTPError):
resp.append_header('Vary', 'Accept')

Check warning on line 246 in falcon/app_helpers.py

View check run for this annotation

Codecov / codecov/patch

falcon/app_helpers.py#L246

Added line #L246 was not covered by tests


def _negotiate_preffered_media_type(req: Request) -> str:
preferred = req.client_prefers((MEDIA_XML, 'text/xml', MEDIA_JSON))
def _negotiate_preffered_media_type(req: Request, resp: Response) -> Optional[str]:
supported = {MEDIA_XML, 'text/xml', MEDIA_JSON}
supported.update(set(resp.options.media_handlers.keys()))
preferred = req.client_prefers(supported)

Check warning on line 252 in falcon/app_helpers.py

View check run for this annotation

Codecov / codecov/patch

falcon/app_helpers.py#L250-L252

Added lines #L250 - L252 were not covered by tests
if preferred is None:
# NOTE(kgriffs): See if the client expects a custom media
# type based on something Falcon supports. Returning something
Expand Down
39 changes: 35 additions & 4 deletions tests/test_app_helpers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import pytest

from falcon import HTTPNotFound
from falcon import ResponseOptions
from falcon.app_helpers import default_serialize_error
from falcon.media import BaseHandler
from falcon.media import Handlers
from falcon.request import Request
from falcon.response import Response
from falcon.testing import create_environ

JSON = ('application/json', 'application/json', b'{"title": "404 Not Found"}')
JSON_CONTENT = b'{"title": "404 Not Found"}'
JSON = ('application/json', 'application/json', JSON_CONTENT)
XML = (
'application/xml',
'application/xml',
Expand All @@ -15,7 +19,7 @@
b'<error><title>404 Not Found</title></error>'
),
)
CUSTOM_JSON = ('custom/any+json', 'application/json', b'{"title": "404 Not Found"}')
CUSTOM_JSON = ('custom/any+json', 'application/json', JSON_CONTENT)

CUSTOM_XML = (
'custom/any+xml',
Expand All @@ -26,21 +30,48 @@
),
)

YAML = (
'application/yaml',
'application/yaml',
(b'error:\n' b' title: 404 Not Found'),
)


class FakeYamlMediaHandler(BaseHandler):
def serialize(self, media: object, content_type: str) -> bytes:
return b'error:\n' b' title: 404 Not Found'


class TestDefaultSerializeError:
def test_if_no_content_type_and_accept_fall_back_to_json(self) -> None:
response = Response()
default_serialize_error(
Request(env=(create_environ())),
response,
HTTPNotFound(),
)
assert response.content_type == 'application/json'
assert response.headers['vary'] == 'Accept'
assert response.data == JSON_CONTENT

@pytest.mark.parametrize(
'accept, content_type, data',
(
JSON,
XML,
CUSTOM_JSON,
CUSTOM_XML,
YAML,
),
)
def test_serializes_error_to_preffered_by_sender(
def test_serializes_error_to_preferred_by_sender(
self, accept, content_type, data
) -> None:
response = Response()
handlers = Handlers()
handlers['application/yaml'] = FakeYamlMediaHandler()
options = ResponseOptions()
options.media_handlers = handlers
response = Response(options=options)
default_serialize_error(
Request(env=(create_environ(headers={'accept': accept}))),
response,
Expand Down
14 changes: 14 additions & 0 deletions tests/test_media_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,20 @@ def on_get(self, req, resp):
assert result.json == falcon.HTTPForbidden().to_dict()


def test_handlers_include_new_media_handlers_in_resolving() -> None:
class FakeHandler:
...

handlers = media.Handlers({falcon.MEDIA_URLENCODED: media.URLEncodedFormHandler()})
handler = FakeHandler()
handlers['application/yaml'] = handler
resolved, _, _ = handlers._resolve(
'application/yaml', 'application/json', raise_not_found=False
)
assert resolved.__class__.__name__ == handler.__class__.__name__
assert resolved == handler


class TestBaseHandler:
def test_defaultError(self):
h = media.BaseHandler()
Expand Down

0 comments on commit 0e4b6f8

Please sign in to comment.