Skip to content
Merged
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
11 changes: 8 additions & 3 deletions docs/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ OxyRoute maintains a small **OpenAPI 3.0**-shaped JSON document in Rust while ro

## Constructor and toggles

- **`App(..., include_openapi=True)`** (Python) maps to the native `App` constructor. When `include_openapi` is false, the special **`GET /openapi.json`** response from the engine is not served, and the document may be empty in those modes depending on the implementation.
- **`set_openapi_served(False)`** can disable serving the document at runtime by flipping the same flag in state (naming mirrors the native `set_openapi_served`).
Served vs built: one flag, two ways to set it.

- **`include_openapi`** (constructor) and **`set_openapi_served(enabled)`** both update the same native field (`AppState::include_openapi`). Use the constructor to choose the default; use **`set_openapi_served(False)`** to turn off HTTP serving later (or `True` to re-enable) without recreating the app.
- **While serving is off** (`include_openapi=False` at build time, or after `set_openapi_served(False)`):
- The engine does **not** return the spec for **`GET /openapi.json`** or **`HEAD /openapi.json`**. The request is **not** special-cased, so it goes through normal routing. Unless you add your own handler for that path, the client usually gets **404 Not Found**.
- Route registration still **merges** operations into the in-memory OpenAPI document. The document is not discarded.
- **Export:** **`openapi_json()`** on the Python `App` still returns the current JSON as a string (handy in tests, admin tools, or a custom response), regardless of the serving toggle.

## Title and export

- **`set_openapi_title`** is applied from `App(..., title="...")` at construction.
- **`openapi_json()`** on the Python `App` returns the current document as a **string** (for debugging or an alternate transport).
- **`openapi_json()`** is described above; it is independent of whether `/openapi.json` is exposed over HTTP.

## What is in the document today

Expand Down
42 changes: 42 additions & 0 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import asyncio
import json

import httpx
from oxyroute import App


Expand All @@ -25,3 +27,43 @@ def m() -> str:

doc = json.loads(app.openapi_json())
assert doc["paths"]["/m"]["patch"]["operationId"] == "m"


def test_openapi_serving_off_constructor_get_and_head_404_openapi_json_still_filled() -> None:
app = App(title="S", include_openapi=False)

@app.get("/a")
def a() -> str:
return "1"

async def _run() -> None:
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
g = await c.get("/openapi.json")
h = await c.request("HEAD", "/openapi.json")
assert g.status_code == 404
assert h.status_code == 404

asyncio.run(_run())

doc = json.loads(app.openapi_json())
assert doc["info"]["title"] == "S"
assert "/a" in doc["paths"]


def test_set_openapi_served_false_stops_serving_still_exports_json() -> None:
app = App()
app.set_openapi_served(False)

@app.get("/z")
def z() -> str:
return "z"

async def _run() -> None:
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
r = await c.get("/openapi.json")
assert r.status_code == 404

asyncio.run(_run())
assert "/z" in json.loads(app.openapi_json())["paths"]
Loading