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
8 changes: 7 additions & 1 deletion src/fastapi_problem/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,15 @@ def wrapper() -> dict:
"""Wrapper."""
res = func()

if "components" not in res:
if not res["paths"]:
# If there are no paths, we don't need to add any responses
return res

if "components" not in res:
res["components"] = {"schemas": {}}
elif "schemas" not in res["components"]:
res["components"]["schemas"] = {}

validation_error = problem_component(
"RequestValidationError",
required=["errors"],
Expand Down
187 changes: 185 additions & 2 deletions tests/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

import httpx
import pytest
from fastapi import FastAPI
from fastapi import Depends, FastAPI
from fastapi.exceptions import RequestValidationError
from fastapi.security import HTTPBearer
from starlette.exceptions import HTTPException

from fastapi_problem import error, handler
Expand Down Expand Up @@ -702,7 +703,7 @@ async def status(_a: str) -> dict:
}


async def test_customise_openapi_handles_no_components():
async def test_customise_openapi_handles_no_components_no_paths():
app = FastAPI()

app.openapi = handler.customise_openapi(app.openapi)
Expand All @@ -712,6 +713,188 @@ async def test_customise_openapi_handles_no_components():
assert "components" not in res


async def test_customise_openapi_handles_no_components_no_422():
app = FastAPI()

@app.get("/status")
async def status() -> dict:
return {}

app.openapi = handler.customise_openapi(app.openapi)

res = app.openapi()

assert res["components"]["schemas"]["HTTPValidationError"] == {
"properties": {
"title": {
"type": "string",
"title": "Problem title",
},
"type": {
"type": "string",
"title": "Problem type",
},
"status": {
"type": "integer",
"title": "Status code",
},
"errors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError",
},
},
},
"type": "object",
"required": [
"type",
"title",
"status",
"errors",
],
"title": "RequestValidationError",
}
assert "Problem" in res["components"]["schemas"]

assert res["paths"]["/status"]["get"]["responses"] == {
"200": {
"content": {
"application/json": {
"schema": {
"title": "Response Status Status Get",
"type": "object",
},
},
},
"description": "Successful Response",
},
"4XX": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem",
},
"example": {
"title": "User facing error message.",
"detail": "Additional error context.",
"type": "client-error-type",
"status": 400,
},
},
},
"description": "Client Error",
},
"5XX": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem",
},
"example": {
"title": "User facing error message.",
"detail": "Additional error context.",
"type": "server-error-type",
"status": 500,
},
},
},
"description": "Server Error",
},
}


async def test_customise_openapi_handles_security_components_no_422():
bearer_scheme = HTTPBearer(bearerFormat="JWT")
app = FastAPI()

@app.get("/status")
async def status(bearer: str = Depends(bearer_scheme)) -> dict: # noqa: ARG001
return {}

app.openapi = handler.customise_openapi(app.openapi)

res = app.openapi()

assert res["components"]["schemas"]["HTTPValidationError"] == {
"properties": {
"title": {
"type": "string",
"title": "Problem title",
},
"type": {
"type": "string",
"title": "Problem type",
},
"status": {
"type": "integer",
"title": "Status code",
},
"errors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError",
},
},
},
"type": "object",
"required": [
"type",
"title",
"status",
"errors",
],
"title": "RequestValidationError",
}
assert "Problem" in res["components"]["schemas"]
assert "securitySchemes" in res["components"]

assert res["paths"]["/status"]["get"]["responses"] == {
"200": {
"content": {
"application/json": {
"schema": {
"title": "Response Status Status Get",
"type": "object",
},
},
},
"description": "Successful Response",
},
"4XX": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem",
},
"example": {
"title": "User facing error message.",
"detail": "Additional error context.",
"type": "client-error-type",
"status": 400,
},
},
},
"description": "Client Error",
},
"5XX": {
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/Problem",
},
"example": {
"title": "User facing error message.",
"detail": "Additional error context.",
"type": "server-error-type",
"status": 500,
},
},
},
"description": "Server Error",
},
}


async def test_customise_openapi_generic_opt_out():
app = FastAPI()

Expand Down
Loading