Skip to content

Commit 205f412

Browse files
authored
fix: server-prefix respects path segment boundaries (#322)
A server url like /v was being treated as a prefix of any route that merely started with those characters, so /version got mangled into "ersion" and falsely reported as a missing endpoint. Only strip a prefix when it lands on a real path segment boundary. Fixes #146
1 parent 214e9e8 commit 205f412

2 files changed

Lines changed: 89 additions & 3 deletions

File tree

pyramid_openapi3/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -456,9 +456,8 @@ def check_all_routes(event: ApplicationCreated) -> None:
456456
def remove_prefixes(path: str) -> str:
457457
path = f"/{path}" if not path.startswith("/") else path
458458
for prefix in prefixes:
459-
if path.startswith(prefix):
460-
prefix_length = len(prefix)
461-
return path[prefix_length:]
459+
if path == prefix or path.startswith(prefix + "/"):
460+
return path[len(prefix) :] or "/"
462461
return path
463462

464463
app = event.app

pyramid_openapi3/tests/test_app_construction.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,43 @@
9393
description: A bar
9494
"""
9595

96+
# A test for when a `server.url` prefix is a string-prefix, but not a
97+
# path-segment-prefix, of a route's pattern (see GH #146): neither `/v` nor
98+
# `/api` should be treated as a prefix of `/version`/`/apidocs` respectively
99+
# (the two literal cases from the issue). `/foo` and `/` are genuinely under
100+
# the `/v` prefix (registered as routes `/v/foo` and `/v` respectively) and
101+
# must still be recognized as such.
102+
PREFIX_BOUNDARY_DOCUMENT = b"""
103+
openapi: "3.1.0"
104+
info:
105+
version: "1.0.0"
106+
title: Foo API
107+
servers:
108+
- url: /v
109+
- url: /api
110+
paths:
111+
/version:
112+
get:
113+
responses:
114+
200:
115+
description: Not under the /v prefix, just shares a string prefix
116+
/apidocs:
117+
get:
118+
responses:
119+
200:
120+
description: Not under the /api prefix, just shares a string prefix
121+
/foo:
122+
get:
123+
responses:
124+
200:
125+
description: A foo, genuinely under the /v prefix
126+
/:
127+
get:
128+
responses:
129+
200:
130+
description: Root, registered at a route matching the prefix exactly
131+
"""
132+
96133

97134
def foo_view(request: Request) -> str:
98135
"""Return a dummy string."""
@@ -138,6 +175,16 @@ def root_server_document() -> t.Generator[t.IO, None, None]:
138175
yield document
139176

140177

178+
@pytest.fixture
179+
def prefix_boundary_document() -> t.Generator[t.IO, None, None]:
180+
"""Load the PREFIX_BOUNDARY_DOCUMENT into a temp file."""
181+
with tempfile.NamedTemporaryFile() as document:
182+
document.write(PREFIX_BOUNDARY_DOCUMENT)
183+
document.seek(0)
184+
185+
yield document
186+
187+
141188
@pytest.fixture
142189
def simple_config() -> t.Generator[Configurator, None, None]:
143190
"""Config fixture."""
@@ -178,6 +225,17 @@ def root_server_app_config(
178225
return simple_config
179226

180227

228+
@pytest.fixture
229+
def prefix_boundary_app_config(
230+
simple_config: Configurator, prefix_boundary_document: t.IO
231+
) -> Configurator:
232+
"""Incremented fixture loading the PREFIX_BOUNDARY_DOCUMENT above into the config."""
233+
simple_config.pyramid_openapi3_spec(
234+
prefix_boundary_document.name, route="/foo.yaml", route_name="foo_api_spec"
235+
)
236+
return simple_config
237+
238+
181239
app_config = pytest.mark.parametrize(
182240
"app_config",
183241
[
@@ -325,3 +383,32 @@ def test_root_server_routes(root_server_app_config: Configurator) -> None:
325383
)
326384

327385
root_server_app_config.make_wsgi_app()
386+
387+
388+
def test_prefix_boundary_routes(prefix_boundary_app_config: Configurator) -> None:
389+
"""Test case for GH #146.
390+
391+
A server prefix that is a string-prefix, but not a path-segment-prefix,
392+
of a route pattern must not be stripped from it -- for both literal cases
393+
from the issue (`/v`+`/version`, `/api`+`/apidocs`). Also covers routes
394+
that are genuinely under a prefix (`/v/foo`), and a route that matches a
395+
prefix exactly (`/v`).
396+
"""
397+
prefix_boundary_app_config.add_route(name="version", pattern="/version")
398+
prefix_boundary_app_config.add_route(name="apidocs", pattern="/apidocs")
399+
prefix_boundary_app_config.add_route(name="foo", pattern="/v/foo")
400+
prefix_boundary_app_config.add_route(name="root", pattern="/v")
401+
prefix_boundary_app_config.add_view(
402+
foo_view, route_name="version", renderer="string", request_method="GET"
403+
)
404+
prefix_boundary_app_config.add_view(
405+
foo_view, route_name="apidocs", renderer="string", request_method="GET"
406+
)
407+
prefix_boundary_app_config.add_view(
408+
foo_view, route_name="foo", renderer="string", request_method="GET"
409+
)
410+
prefix_boundary_app_config.add_view(
411+
foo_view, route_name="root", renderer="string", request_method="GET"
412+
)
413+
414+
prefix_boundary_app_config.make_wsgi_app()

0 commit comments

Comments
 (0)