Skip to content

Commit

Permalink
feat(event_handler): add support to download OpenAPI spec file (#3571)
Browse files Browse the repository at this point in the history
* Add option to download OpenAPI spec file

* Remove Optional from enable_download_spec

Co-authored-by: Leandro Damascena <[email protected]>
Signed-off-by: Thomas McKanna <[email protected]>

* Add test for enable_download_spec

* Add test for custom path with enable_download_spec

* Show OpenAPI spec with conditional query string

* Resolving conflicts and minor changes

---------

Signed-off-by: Thomas McKanna <[email protected]>
Co-authored-by: Thomas <[email protected]>
Co-authored-by: Leandro Damascena <[email protected]>
Co-authored-by: Thomas <[email protected]>
  • Loading branch information
4 people authored Jan 10, 2024
1 parent e34f719 commit 035cf88
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 22 deletions.
24 changes: 23 additions & 1 deletion aws_lambda_powertools/event_handler/api_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,7 @@ def enable_swagger(
middlewares: List[Callable[..., Response]], optional
List of middlewares to be used for the swagger route.
"""
from aws_lambda_powertools.event_handler.openapi.compat import model_json
from aws_lambda_powertools.event_handler.openapi.models import Server

if not swagger_base_url:
Expand Down Expand Up @@ -1640,7 +1641,28 @@ def swagger_handler():
license_info=license_info,
)

body = generate_swagger_html(spec, swagger_js, swagger_css)
# The .replace('</', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
# </script> or similar tags. Escaping the forward slash in </ as <\/ ensures that the JSON does not
# inadvertently close the script tag, and the JSON remains a valid string within the JavaScript code.
escaped_spec = model_json(
spec,
by_alias=True,
exclude_none=True,
indent=2,
).replace("</", "<\\/")

# Check for query parameters; if "format" is specified as "json",
# respond with the JSON used in the OpenAPI spec
# Example: https://www.example.com/swagger?format=json
query_params = self.current_event.query_string_parameters or {}
if query_params.get("format") == "json":
return Response(
status_code=200,
content_type="application/json",
body=escaped_spec,
)

body = generate_swagger_html(escaped_spec, path, swagger_js, swagger_css)

return Response(
status_code=200,
Expand Down
27 changes: 6 additions & 21 deletions aws_lambda_powertools/event_handler/openapi/swagger_ui/html.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,19 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from aws_lambda_powertools.event_handler.openapi.models import OpenAPI


def generate_swagger_html(spec: "OpenAPI", js_url: str, css_url: str) -> str:
def generate_swagger_html(spec: str, path: str, js_url: str, css_url: str) -> str:
"""
Generate Swagger UI HTML page
Parameters
----------
spec: OpenAPI
spec: str
The OpenAPI spec
path: str
The path to the Swagger documentation
js_url: str
The URL to the Swagger UI JavaScript file
css_url: str
The URL to the Swagger UI CSS file
"""

from aws_lambda_powertools.event_handler.openapi.compat import model_json

# The .replace('</', '<\\/') part is necessary to prevent a potential issue where the JSON string contains
# </script> or similar tags. Escaping the forward slash in </ as <\/ ensures that the JSON does not inadvertently
# close the script tag, and the JSON remains a valid string within the JavaScript code.
escaped_spec = model_json(
spec,
by_alias=True,
exclude_none=True,
indent=2,
).replace("</", "<\\/")

return f"""
<!DOCTYPE html>
<html>
Expand Down Expand Up @@ -60,7 +44,7 @@ def generate_swagger_html(spec: "OpenAPI", js_url: str, css_url: str) -> str:
layout: "BaseLayout",
showExtensions: true,
showCommonExtensions: true,
spec: {escaped_spec},
spec: {spec},
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
Expand All @@ -71,6 +55,7 @@ def generate_swagger_html(spec: "OpenAPI", js_url: str, css_url: str) -> str:
}}
var ui = SwaggerUIBundle(swaggerUIOptions)
ui.specActions.updateUrl('{path}?format=json');
</script>
</html>
""".strip()
31 changes: 31 additions & 0 deletions tests/functional/event_handler/test_openapi_swagger.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import json
from typing import Dict

from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from tests.functional.utils import load_event

Expand Down Expand Up @@ -68,3 +71,31 @@ def test_openapi_swagger_with_custom_base_url_no_embedded_assets():
LOAD_GW_EVENT["path"] = "/swagger.js"
result = app(LOAD_GW_EVENT, {})
assert result["statusCode"] == 404


def test_openapi_swagger_json_view_with_default_path():
app = APIGatewayRestResolver(enable_validation=True)
app.enable_swagger(title="OpenAPI JSON View")
LOAD_GW_EVENT["path"] = "/swagger"
LOAD_GW_EVENT["queryStringParameters"] = {"format": "json"}

result = app(LOAD_GW_EVENT, {})

assert result["statusCode"] == 200
assert result["multiValueHeaders"]["Content-Type"] == ["application/json"]
assert isinstance(json.loads(result["body"]), Dict)
assert "OpenAPI JSON View" in result["body"]


def test_openapi_swagger_json_view_with_custom_path():
app = APIGatewayRestResolver(enable_validation=True)
app.enable_swagger(path="/fizzbuzz/foobar", title="OpenAPI JSON View")
LOAD_GW_EVENT["path"] = "/fizzbuzz/foobar"
LOAD_GW_EVENT["queryStringParameters"] = {"format": "json"}

result = app(LOAD_GW_EVENT, {})

assert result["statusCode"] == 200
assert result["multiValueHeaders"]["Content-Type"] == ["application/json"]
assert isinstance(json.loads(result["body"]), Dict)
assert "OpenAPI JSON View" in result["body"]

0 comments on commit 035cf88

Please sign in to comment.