Skip to content

Commit 75108ce

Browse files
Fix #60: resolve $ref in response headers pointing to #/components/headers/ (#72)
Response header definitions using $ref (e.g. $ref: '#/components/headers/foo') were passed to Jinja templates unresolved, causing an UndefinedError when the template accessed .schema or .description. Added get_response_headers() method to OpenAPIV3DocumentationHandler that calls _resolve_opt_ref() on each header value before returning the dict. Updated both mkdocs and markdown request-responses templates to call this method instead of iterating definition.headers directly. Added regression test with a fixture (example9-openapi.yaml) covering both $ref headers and inline headers in the same response. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f39ef60 commit 75108ce

File tree

6 files changed

+76
-2
lines changed

6 files changed

+76
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
fragment), reported by @mbklein.
2424
- Fix [#55](https://github.com/Neoteroi/essentials-openapi/issues/55): `jsonSchemaDialect`
2525
is not required and should not have a default value.
26+
- Fix [#60](https://github.com/Neoteroi/essentials-openapi/issues/60): resolve `$ref`
27+
values in response headers pointing to `#/components/headers/...` to avoid
28+
`UndefinedError` when rendering response tables, reported by @copiousfreetime.
2629

2730
## [1.3.0] - 2025-11-19
2831

openapidocs/mk/v3/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,19 @@ def get_parameters(self, operation) -> List[dict]:
607607

608608
return results
609609

610+
def get_response_headers(self, response_definition: dict) -> dict:
611+
"""
612+
Returns the headers of a response definition, resolving any $ref values
613+
so that the template can access fields like schema and description directly.
614+
"""
615+
headers = response_definition.get("headers")
616+
if not headers:
617+
return {}
618+
return {
619+
name: self._resolve_opt_ref(header_def)
620+
for name, header_def in headers.items()
621+
}
622+
610623
def write(self) -> str:
611624
return self._writer.write(
612625
self.doc,

openapidocs/mk/v3/views_markdown/partial/request-responses.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
{%- if definition.headers %}
2626

2727
{% with rows = [[texts.name, texts.description, texts.schema]] %}
28-
{%- for header_name, header_definition in definition.headers.items() -%}
28+
{%- for header_name, header_definition in handler.get_response_headers(definition).items() -%}
2929
{%- set _ = rows.append([header_name, header_definition.description, header_definition.schema.type]) -%}
3030
{%- endfor -%}
3131
{{ rows | table }}

openapidocs/mk/v3/views_mkdocs/partial/request-responses.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
</tr>
3333
</thead>
3434
<tbody>
35-
{%- for header_name, header_definition in definition.headers.items() %}
35+
{%- for header_name, header_definition in handler.get_response_headers(definition).items() %}
3636
<tr>
3737
<td><code>{{header_name}}</code></td>
3838
<td>{{header_definition.description}}</td>

tests/res/example9-openapi.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
openapi: 3.0.0
3+
info:
4+
title: Header Ref Example
5+
version: v1
6+
paths:
7+
/pets:
8+
get:
9+
summary: List all pets
10+
operationId: listPets
11+
tags:
12+
- Pets
13+
responses:
14+
"200":
15+
description: A paged array of pets
16+
headers:
17+
Current-Page:
18+
$ref: '#/components/headers/current-page'
19+
X-Rate-Limit:
20+
description: Rate limit per hour
21+
schema:
22+
type: integer
23+
content:
24+
application/json:
25+
schema:
26+
type: array
27+
items:
28+
type: object
29+
properties:
30+
id:
31+
type: integer
32+
name:
33+
type: string
34+
components:
35+
headers:
36+
current-page:
37+
description: The current page of total pages this response represents.
38+
example: '1'
39+
style: simple
40+
schema:
41+
type: string
42+
required: true

tests/test_mk_v3.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ def test_swagger2_raises_not_supported():
8484
OpenAPIV3DocumentationHandler({"swagger": "2.0", "info": {}, "paths": {}})
8585

8686

87+
def test_v3_response_header_ref():
88+
"""
89+
Regression test for https://github.com/Neoteroi/essentials-openapi/issues/60
90+
Response headers that use $ref to #/components/headers/... should be resolved
91+
without raising an UndefinedError.
92+
"""
93+
data = get_file_yaml("example9-openapi.yaml")
94+
handler = OpenAPIV3DocumentationHandler(data)
95+
output = handler.write()
96+
97+
assert "Current-Page" in output
98+
assert "The current page of total pages this response represents." in output
99+
assert "X-Rate-Limit" in output
100+
assert "Rate limit per hour" in output
101+
102+
87103
@pytest.mark.parametrize(
88104
"input,expected_result",
89105
[

0 commit comments

Comments
 (0)