Repository: Neoteroi/BlackSheep
Pull request #288 (2022-12-12 06:44:14)

Fix openapi scheme generation for routes with value patterns
Sorry for the tenacity and bad english, but again about issue #286. The problem is that for routes with value patterns generated BAD schema. In spec: "Each template expression in the path MUST correspond to a path parameter that is included in the [Path Item](https://swagger.io/specification/#path-item-object) itself and/or in each of the Path Item's [Operations](https://swagger.io/specification/#operation-object)." BlackSheep make template expression "{uuid:image_id}", but path parameter name "image_id".


Patch:
diff --git a/blacksheep/server/routing.py b/blacksheep/server/routing.py
index 04cf0ab..4906016 100644
--- a/blacksheep/server/routing.py
+++ b/blacksheep/server/routing.py
@@ -247,9 +247,25 @@ class Route:
     def __repr__(self) -> str:
         return f"<Route {self.pattern.decode('utf8')}>"
 
+    @staticmethod
+    def _normalize_rich_parameter(match: re.Match):
+        matched_parameter = next(iter(match.groups()))
+        parts = matched_parameter.split(b":")
+        parameter_name = parts[1] if len(parts) > 1 else matched_parameter
+        return b"/{" + parameter_name + b"}"
+
     @property
     def mustache_pattern(self) -> str:
-        return _route_param_rx.sub(rb"/{\1}", self.pattern).decode("utf8")
+        pattern = self.pattern
+        if b"<" in pattern:
+            pattern = _angle_bracket_route_param_rx.sub(
+                self._normalize_rich_parameter, pattern
+            )
+        if b"{" in pattern:
+            pattern = _mustache_route_param_rx.sub(
+                self._normalize_rich_parameter, pattern
+            )
+        return _route_param_rx.sub(rb"/{\1}", pattern).decode("utf8")
 
     @property
     def full_pattern(self) -> bytes:


Test patch:
diff --git a/tests/test_openapi_v3.py b/tests/test_openapi_v3.py
index a0f5123..805bcdf 100644
--- a/tests/test_openapi_v3.py
+++ b/tests/test_openapi_v3.py
@@ -2,6 +2,7 @@ from dataclasses import dataclass
 from datetime import date, datetime
 from enum import IntEnum
 from typing import Generic, List, Optional, Sequence, TypeVar, Union
+from uuid import UUID
 
 import pytest
 from openapidocs.common import Format, Serializer
@@ -1916,6 +1917,10 @@ async def test_handles_ref_for_optional_type(
     def three(cat_id: int) -> Cat:
         ...
 
+    @app.route("/cats_value_pattern/{uuid:cat_id}")
+    def four(cat_id: UUID) -> Cat:
+        ...
+
     docs.bind_app(app)
     await app.start()
 
@@ -1979,6 +1984,25 @@ paths:
                     nullable: false
                 description: ''
                 required: true
+    /cats_value_pattern/{cat_id}:
+        get:
+            responses:
+                '200':
+                    description: Success response
+                    content:
+                        application/json:
+                            schema:
+                                $ref: '#/components/schemas/Cat'
+            operationId: four
+            parameters:
+            -   name: cat_id
+                in: path
+                schema:
+                    type: string
+                    format: uuid
+                    nullable: false
+                description: ''
+                required: true
 components:
     schemas:
         Cat:
diff --git a/tests/test_router.py b/tests/test_router.py
index fed8249..a8b4e60 100644
--- a/tests/test_router.py
+++ b/tests/test_router.py
@@ -793,6 +793,14 @@ def test_router_iterable():
             "/api/cats/:cat_id/friends/:friend_id",
             "/api/cats/{cat_id}/friends/{friend_id}",
         ],
+        [
+            "/api/cats/{int:cat_id}/friends/{uuid:friend_id}",
+            "/api/cats/{cat_id}/friends/{friend_id}",
+        ],
+        [
+            "/api/cats/<int:cat_id>/friends/<uuid:friend_id>",
+            "/api/cats/{cat_id}/friends/{friend_id}",
+        ],
     ],
 )
 def test_route_to_openapi_pattern(route_pattern, expected_pattern):