Summary
When limit_to_content_types is set on a ResponseSpec registered in DMR_SETTINGS[Settings.responses] (global error responses), and the global error handler returns an HttpResponse whose Content-Type differs from the request's negotiated renderer content type, dmr raises a ResponseSchemaError and returns a 422 instead of the intended error response.
When limit_to_content_types is set application/problem+json


When limit_to_content_types is set None


Environment
- dmr version: 0.2.0
- Python: 3.12
- Django: 5.2
Steps to reproduce
# settings.py
DMR_SETTINGS = {
Settings.responses: [
ResponseSpec(
ProblemDetail,
status_code=HTTPStatus.UNAUTHORIZED,
description="Unauthorized",
limit_to_content_types=frozenset({"application/problem+json"}),
),
],
Settings.global_error_handler: "myapp.error_handler.dmr_global_error_handler",
}
# error_handler.py
def dmr_global_error_handler(endpoint, controller, exc):
if isinstance(exc, NotAuthenticatedError):
return JsonResponse(
{"type": "about:blank", "title": "Unauthorized",
"status": 401},
status=401,
content_type="application/problem+json",
)
raise
- Send a request without authentication to any protected endpoint.
- Client sends
Accept: application/json (or Accept: */* header (ex.: Postman)).
Expected behavior
The global error handler returns a 401 response with Content-Type: application/problem+json. Since the ResponseSpec for 401 allows application/problem+json, validation should pass and the client should receive the 401
Actual behavior
Client receives 422 Unprocessable Entity with Content-Type: application/json:
{
"detail": [
{
"msg": "Response 401 is not allowed for
'application/json', only for ['application/problem+json']",
"type": "value_error"
}
]
}
Setting the parameter:
Settings.renderers: [JsonRenderer(), ProblemJsonRenderer()]
class ProblemJsonRenderer(JsonRenderer):
"""Renderer for RFC 7807 Problem Details responses."""
content_type = "application/problem+json"
@property
@override
def validation_parser(self) -> JsonParser:
return JsonParser()
does not solve the problem.
Setting the parameter:
Settings.renderers: [ProblemJsonRenderer(), JsonRenderer()]
may solve the problem, but other endpoints start returning Content-Type:application/problem+json as the default header.
In my opinion, the root cause might be the following (though I could be wrong):
In dmr/validation/response.py, method _maybe_validate_body (line ~185):
content_type=getattr(renderer, 'content_type', parser.content_type)
Here, renderer is determined from the request's Accept header
(e.g. application/json), rather than from the actual
HttpResponse.headers['Content-Type']. As a result,
_validate_body compares limit_to_content_types against the
negotiated content type of the request, not the one that the
error handler actually set in the response.
When _validate_body raises ResponseSchemaError, it gets caught
by _make_http_response in endpoint.py (line ~444) — but at
that point there is nowhere to delegate handling further, so
controller.to_error(...) is called directly, which produces a
422 with the serializer's default content type (application/json).
Summary
When
limit_to_content_typesis set on aResponseSpecregistered inDMR_SETTINGS[Settings.responses](global error responses), and the global error handler returns anHttpResponsewhoseContent-Typediffers from the request's negotiated renderer content type, dmr raises aResponseSchemaErrorand returns a 422 instead of the intended error response.When


limit_to_content_typesis set application/problem+jsonWhen


limit_to_content_typesis set NoneEnvironment
Steps to reproduce
Accept: application/json(orAccept: */*header (ex.: Postman)).Expected behavior
The global error handler returns a 401 response with
Content-Type: application/problem+json. Since theResponseSpecfor 401 allowsapplication/problem+json, validation should pass and the client should receive the 401Actual behavior
Client receives 422 Unprocessable Entity with
Content-Type:application/json:{ "detail": [ { "msg": "Response 401 is not allowed for 'application/json', only for ['application/problem+json']", "type": "value_error" } ] }Setting the parameter:
Settings.renderers: [JsonRenderer(), ProblemJsonRenderer()]does not solve the problem.
Setting the parameter:
Settings.renderers: [ProblemJsonRenderer(), JsonRenderer()]may solve the problem, but other endpoints start returning
Content-Type:application/problem+jsonas the default header.In my opinion, the root cause might be the following (though I could be wrong):
In
dmr/validation/response.py, method_maybe_validate_body(line ~185):Here,
rendereris determined from the request'sAcceptheader(e.g.
application/json), rather than from the actualHttpResponse.headers['Content-Type']. As a result,_validate_bodycompareslimit_to_content_typesagainst thenegotiated content type of the request, not the one that the
error handler actually set in the response.
When
_validate_bodyraisesResponseSchemaError, it gets caughtby
_make_http_responseinendpoint.py(line ~444) — but atthat point there is nowhere to delegate handling further, so
controller.to_error(...)is called directly, which produces a422 with the serializer's default content type (
application/json).