Repository: NRWLDev/fastapi-problem

Issue:
Swagger doc of fastapi is not updated regarding error examples
Hello,

thanks for the nice module, it seems to work well for me in the Fastapi Swagger, but the Validation Error example remains with the old format, not the RFC9457 one :

![Image](https://github.com/user-attachments/assets/c0fedaa3-d816-4b23-b8d5-622eca7282e3)


Discussion:
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/NRWLDev/fastapi-problem/pull/29?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`e090904`)](https://app.codecov.io/gh/NRWLDev/fastapi-problem/commit/e090904b1e62b5fe2434c6a21a0546ce05bdd065?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev) to head [(`05da422`)](https://app.codecov.io/gh/NRWLDev/fastapi-problem/commit/05da422d8d61f66e704def2f7f1a63a5a1cd762f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main       #29   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         4           
  Lines           33        45   +12     
  Branches         1         2    +1     
=========================================
+ Hits            33        45   +12     
```

| [Flag](https://app.codecov.io/gh/NRWLDev/fastapi-problem/pull/29/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/NRWLDev/fastapi-problem/pull/29/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev) | `100.00% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/NRWLDev/fastapi-problem/pull/29?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NRWLDev).


Patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 35f6386..aad3011 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -21,7 +21,7 @@ jobs:
 
     - name: Publish
       env:
-        TWINE_USERNAME: EdgyEdgemond
+        TWINE_USERNAME: __token__
         TWINE_PASSWORD: ${{ secrets.PYPI_PUBLISH_TOKEN }}
       run: |
         uv build
diff --git a/src/fastapi_problem/handler.py b/src/fastapi_problem/handler.py
index ebc84f7..e0e8684 100644
--- a/src/fastapi_problem/handler.py
+++ b/src/fastapi_problem/handler.py
@@ -27,6 +27,90 @@ if t.TYPE_CHECKING:
     from fastapi_problem.cors import CorsConfiguration
 
 
+def customise_openapi(func: t.Callable[..., dict]) -> t.Callable[..., dict]:
+    """Customize OpenAPI schema."""
+
+    def wrapper() -> dict:
+        """Wrapper."""
+        res = func()
+
+        if "components" not in res:
+            return res
+
+        validation_error = {
+            "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",
+                "errors",
+                "status",
+            ],
+            "title": "ValidationError",
+        }
+        problem = {
+            "properties": {
+                "title": {
+                    "type": "string",
+                    "title": "Problem Title",
+                },
+                "type": {
+                    "type": "string",
+                    "title": "Problem type",
+                },
+                "status": {
+                    "type": "integer",
+                    "title": "Status code",
+                },
+                "detail": {
+                    "anyOf": [
+                        {
+                            "type": "string",
+                        },
+                        {
+                            "type": "null",
+                        },
+                    ],
+                    "title": "Problem detail",
+                },
+            },
+            "type": "object",
+            "required": [
+                "type",
+                "title",
+                "detail",
+                "status",
+            ],
+            "title": "Problem",
+        }
+
+        res["components"]["schemas"]["HTTPValidationError"] = validation_error
+        res["components"]["schemas"]["Problem"] = problem
+
+        return res
+
+    return wrapper
+
+
 def request_validation_handler(
     eh: ExceptionHandler,
     _request: Request,
@@ -88,6 +172,9 @@ def add_exception_handler(  # noqa: PLR0913
     app.add_exception_handler(HTTPException, eh)
     app.add_exception_handler(RequestValidationError, eh)
 
+    # Override default 422 with Problem schema
+    app.openapi = customise_openapi(app.openapi)
+
     return eh
 
 


Test patch:
diff --git a/tests/test_handler.py b/tests/test_handler.py
index bb9f067..f3277a6 100644
--- a/tests/test_handler.py
+++ b/tests/test_handler.py
@@ -439,3 +439,57 @@ async def test_exception_handler_in_app_post_register():
         "detail": "Not Found",
         "status": 404,
     }
+
+
+async def test_customise_openapi():
+    app = FastAPI()
+
+    app.openapi = handler.customise_openapi(app.openapi)
+
+    @app.get("/status")
+    async def status(_a: str) -> dict:
+        return {}
+
+    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",
+            "errors",
+            "status",
+        ],
+        "title": "ValidationError",
+    }
+    assert "Problem" in res["components"]["schemas"]
+    assert "ValidationError" in res["components"]["schemas"]
+
+
+async def test_customise_openapi_handles_no_components():
+    app = FastAPI()
+
+    app.openapi = handler.customise_openapi(app.openapi)
+
+    res = app.openapi()
+    assert res["paths"] == {}
+    assert "components" not in res