Skip to content

feat: support multiple response types in OpenAPI documentation#1373

Open
sansyrox wants to merge 2 commits intomainfrom
feat/openapi-multiple-responses
Open

feat: support multiple response types in OpenAPI documentation#1373
sansyrox wants to merge 2 commits intomainfrom
feat/openapi-multiple-responses

Conversation

@sansyrox
Copy link
Copy Markdown
Member

@sansyrox sansyrox commented Apr 11, 2026

Summary

  • Adds an openapi_responses parameter to all route decorators (@app.get, @app.post, etc.) and SubRouter methods, allowing users to document additional HTTP response codes in the generated OpenAPI spec.
  • Merges user-provided response definitions into the OpenAPI path object after the default "200" response, supporting both full response objects and simple description strings.
  • Adds unit tests verifying that openapi_responses is correctly stored on routes and defaults to None.

Usage

@app.get("/users/:id", openapi_responses={
    404: {"description": "User not found"},
    422: {"description": "Validation error", "content": {"application/json": {"schema": {"type": "object"}}}},
})
async def get_user(request):
    ...

Test plan

  • test_openapi_responses_registered — verifies responses dict is stored on the route
  • test_openapi_responses_default_none — verifies routes without the parameter default to None

Closes #1257

Made with Cursor

Summary by CodeRabbit

  • New Features
    • Added optional parameter to define custom OpenAPI response specifications per route
    • Enables detailed per-route HTTP response metadata by status code in auto-generated OpenAPI documentation
    • Supported across all HTTP method decorators and core route registration

Route decorators now accept an `openapi_responses` parameter for
documenting additional response codes in the OpenAPI spec:

    @app.get("/users/:id", openapi_responses={
        404: {"description": "User not found"},
        422: {"description": "Validation error"},
    })

Closes #1257

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
robyn Canceled Canceled Apr 11, 2026 11:39pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

The changes extend Robyn's route registration and OpenAPI spec generation to support per-route custom response definitions. A new optional openapi_responses: Optional[dict] = None parameter is added to the route decorator chain and propagated through to OpenAPI metadata generation, enabling routes to define multiple status code responses in their API documentation.

Changes

Cohort / File(s) Summary
Route Decorator Methods
robyn/__init__.py
Added openapi_responses parameter to BaseRobyn.add_route() and all HTTP decorators (get, post, put, delete, patch, head, options, connect, trace), as well as corresponding SubRouter methods. Parameter is passed through to underlying add_route implementation.
OpenAPI Response Processing
robyn/openapi.py
Extended add_openapi_path_obj() and get_path_obj() to accept optional openapi_responses parameter. When provided, response entries are merged into the generated OpenAPI operation's responses map, with status codes coerced to strings and values wrapped in {"description": ...} if not already dictionaries.
Route Registration & Storage
robyn/router.py
Added openapi_responses: Optional[dict] = None field to Route NamedTuple. Updated Router.add_route() to accept and propagate the parameter, and modified prepare_routes_openapi() to pass route-specific response metadata to OpenAPI generation.
Unit Tests
unit_tests/test_openapi_multiple_responses.py
Added two test functions validating openapi_responses behavior: one confirming routes with custom responses store the metadata correctly, and one confirming routes without custom responses default to None.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Through decorators hopping, responses now flow,
Each route can declare what status codes show—
Multiple types in the OpenAPI dance,
Per-route definitions get their sweet chance!
OpenAPI specs bloom with clarity's glow. 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature addition: supporting multiple response types in OpenAPI documentation, which aligns with the changeset's core objective.
Description check ✅ Passed The description includes a clear summary of changes, usage example, test plan with test names, and references the closed issue #1257, meeting the template requirements.
Linked Issues check ✅ Passed The PR fully implements the objective from issue #1257: enabling multiple HTTP response type documentation in OpenAPI specs through the new openapi_responses parameter across all route decorators and SubRouter methods [#1257].
Out of Scope Changes check ✅ Passed All changes are directly related to adding openapi_responses parameter support across the codebase and corresponding tests, with no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openapi-multiple-responses

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
robyn/router.py (1)

345-355: ⚠️ Potential issue | 🟡 Minor

Freeze openapi_responses at registration time.

Line 345 and Line 355 store the caller’s dict by reference. If that dict is mutated later, route metadata (and eventual OpenAPI output) can change unexpectedly.

Proposed fix
+import copy
...
-        if inspect.iscoroutinefunction(handler):
+        frozen_openapi_responses = copy.deepcopy(openapi_responses) if openapi_responses is not None else None
+
+        if inspect.iscoroutinefunction(handler):
             function = FunctionInfo(
                 async_inner_handler,
                 True,
                 len(params),
                 params,
                 new_injected_dependencies,
             )
-            self.routes.append(Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags, openapi_responses))
+            self.routes.append(
+                Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags, frozen_openapi_responses)
+            )
             return async_inner_handler
         else:
             function = FunctionInfo(
                 inner_handler,
                 False,
                 len(params),
                 params,
                 new_injected_dependencies,
             )
-            self.routes.append(Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags, openapi_responses))
+            self.routes.append(
+                Route(route_type, endpoint, function, is_const, auth_required, openapi_name, openapi_tags, frozen_openapi_responses)
+            )
             return inner_handler
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@robyn/router.py` around lines 345 - 355, The route registration stores the
caller's openapi_responses dict by reference (see openapi_responses passed into
Route in both branches), so later mutations affect route metadata; fix by
freezing/copying it at registration time—replace the direct openapi_responses
argument in self.routes.append(Route(..., openapi_responses)) with a copy (e.g.,
openapi_responses.copy() or copy.deepcopy(openapi_responses) if nested
structures) in both the async and non-async branches where Route is constructed
(symbols: openapi_responses, Route, self.routes.append, FunctionInfo,
inner_handler).
🧹 Nitpick comments (1)
unit_tests/test_openapi_multiple_responses.py (1)

4-30: Add a spec-level assertion test for response merging.

These tests validate route storage, but they don’t verify that openapi_responses is actually merged into the generated OpenAPI operation (responses). Adding one spec-level test would cover the feature’s user-facing behavior.

Proposed test addition
+def test_openapi_responses_are_merged_into_generated_spec():
+    app = Robyn(__file__)
+
+    `@app.get`(
+        "/items/:id",
+        openapi_name="Get item",
+        openapi_responses={
+            404: {"description": "Not found"},
+            422: "Validation error",
+        },
+    )
+    async def get_item():
+        return {"id": 1}
+
+    app.router.prepare_routes_openapi(app.openapi, app.included_routers)
+    spec = app.openapi.get_openapi_config()
+    op = spec["paths"]["/items/{id}"]["get"]
+
+    assert "200" in op["responses"]
+    assert op["responses"]["404"]["description"] == "Not found"
+    assert op["responses"]["422"]["description"] == "Validation error"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unit_tests/test_openapi_multiple_responses.py` around lines 4 - 30, Add a
spec-level test (e.g., test_openapi_responses_merged_into_spec) that registers a
route on Robyn with openapi_responses (reuse the "/items/:id" handler from the
diff), generate the OpenAPI spec from the app (call the app's OpenAPI/spec
generation method such as app.openapi() or the project-specific get_openapi_spec
helper), and assert that the resulting operation for the route's path includes a
"responses" mapping that contains the 404 and 422 entries from
openapi_responses; ensure you reference the same path and method used in the
route registration and verify the merged entries exist in the spec-level
operation responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@robyn/__init__.py`:
- Around line 792-890: SubRouter is missing an override for connect so CONNECT
routes bypass the prefix; add a connect method on the same class that mirrors
the other HTTP decorators: define def connect(self, endpoint: str,
auth_required: bool = False, openapi_name: str = "", openapi_tags: List[str] =
["connect"], openapi_responses: Optional[dict] = None) and call return
super().connect(endpoint=self.__add_prefix(endpoint),
auth_required=auth_required, openapi_name=openapi_name,
openapi_tags=openapi_tags, openapi_responses=openapi_responses); use the
existing __add_prefix helper and the same parameter names as the other overrides
to keep behavior consistent with BaseRobyn.connect.

---

Outside diff comments:
In `@robyn/router.py`:
- Around line 345-355: The route registration stores the caller's
openapi_responses dict by reference (see openapi_responses passed into Route in
both branches), so later mutations affect route metadata; fix by
freezing/copying it at registration time—replace the direct openapi_responses
argument in self.routes.append(Route(..., openapi_responses)) with a copy (e.g.,
openapi_responses.copy() or copy.deepcopy(openapi_responses) if nested
structures) in both the async and non-async branches where Route is constructed
(symbols: openapi_responses, Route, self.routes.append, FunctionInfo,
inner_handler).

---

Nitpick comments:
In `@unit_tests/test_openapi_multiple_responses.py`:
- Around line 4-30: Add a spec-level test (e.g.,
test_openapi_responses_merged_into_spec) that registers a route on Robyn with
openapi_responses (reuse the "/items/:id" handler from the diff), generate the
OpenAPI spec from the app (call the app's OpenAPI/spec generation method such as
app.openapi() or the project-specific get_openapi_spec helper), and assert that
the resulting operation for the route's path includes a "responses" mapping that
contains the 404 and 422 entries from openapi_responses; ensure you reference
the same path and method used in the route registration and verify the merged
entries exist in the spec-level operation responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 66a80765-dc1c-4388-ada3-4d414ce86bf8

📥 Commits

Reviewing files that changed from the base of the PR and between 3e04c65 and e4d1b12.

📒 Files selected for processing (4)
  • robyn/__init__.py
  • robyn/openapi.py
  • robyn/router.py
  • unit_tests/test_openapi_multiple_responses.py

Comment thread robyn/__init__.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support multiple return types in open api

1 participant