-
Notifications
You must be signed in to change notification settings - Fork 323
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: openapi ui for api exploration (#3041)
- Loading branch information
1 parent
dee6681
commit 5b22961
Showing
3 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
import json | ||
from typing import Any, Dict, Optional | ||
|
||
from starlette.responses import HTMLResponse | ||
|
||
swagger_ui_default_parameters: Dict[str, Any] = { | ||
"dom_id": "#swagger-ui", | ||
"layout": "BaseLayout", | ||
"deepLinking": True, | ||
"showExtensions": True, | ||
"showCommonExtensions": True, | ||
} | ||
|
||
|
||
def get_swagger_ui_html( | ||
*, | ||
openapi_url: str = "/schema", | ||
title: str, | ||
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js", | ||
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css", | ||
swagger_favicon_url: str = "/favicon.ico", | ||
oauth2_redirect_url: Optional[str] = None, | ||
init_oauth: Optional[str] = None, | ||
swagger_ui_parameters: Optional[Dict[str, Any]] = None, | ||
) -> HTMLResponse: | ||
""" | ||
Generate and return the HTML that loads Swagger UI for the interactive API | ||
docs (normally served at `/docs`). | ||
""" | ||
current_swagger_ui_parameters = swagger_ui_default_parameters.copy() | ||
if swagger_ui_parameters: | ||
current_swagger_ui_parameters.update(swagger_ui_parameters) | ||
|
||
html = f""" | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<link type="text/css" rel="stylesheet" href="{swagger_css_url}"> | ||
<link rel="shortcut icon" href="{swagger_favicon_url}"> | ||
<title>{title}</title> | ||
</head> | ||
<body> | ||
<div id="swagger-ui"> | ||
</div> | ||
<script src="{swagger_js_url}"></script> | ||
<!-- `SwaggerUIBundle` is now available on the page --> | ||
<script> | ||
const ui = SwaggerUIBundle({{ | ||
url: '{openapi_url}', | ||
""" | ||
|
||
for key, value in current_swagger_ui_parameters.items(): | ||
html += f"{json.dumps(key)}: {json.dumps(value)},\n" | ||
|
||
if oauth2_redirect_url: | ||
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}'," | ||
|
||
html += """ | ||
presets: [ | ||
SwaggerUIBundle.presets.apis, | ||
SwaggerUIBundle.SwaggerUIStandalonePreset | ||
], | ||
})""" | ||
|
||
if init_oauth: | ||
html += f""" | ||
ui.initOAuth({json.dumps(init_oauth)}) | ||
""" | ||
|
||
html += """ | ||
</script> | ||
</body> | ||
</html> | ||
""" | ||
return HTMLResponse(html) | ||
|
||
|
||
def get_redoc_html( | ||
*, | ||
openapi_url: str, | ||
title: str, | ||
redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js", | ||
redoc_favicon_url: str = "/favicon.ico", | ||
with_google_fonts: bool = True, | ||
) -> HTMLResponse: | ||
""" | ||
Generate and return the HTML response that loads ReDoc for the alternative | ||
API docs (normally served at `/redoc`). | ||
""" | ||
html = f""" | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>{title}</title> | ||
<!-- needed for adaptive design --> | ||
<meta charset="utf-8"/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
""" | ||
if with_google_fonts: | ||
html += """ | ||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet"> | ||
""" # noqa: E501 | ||
html += f""" | ||
<link rel="shortcut icon" href="{redoc_favicon_url}"> | ||
<!-- | ||
ReDoc doesn't change outer page styles | ||
--> | ||
<style> | ||
body {{ | ||
margin: 0; | ||
padding: 0; | ||
}} | ||
</style> | ||
</head> | ||
<body> | ||
<noscript> | ||
ReDoc requires Javascript to function. Please enable it to browse the documentation. | ||
</noscript> | ||
<redoc spec-url="{openapi_url}"></redoc> | ||
<script src="{redoc_js_url}"> </script> | ||
</body> | ||
</html> | ||
""" | ||
return HTMLResponse(html) | ||
|
||
|
||
# Not needed now but copy-pasting for future reference | ||
def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: | ||
""" | ||
Generate the HTML response with the OAuth2 redirection for Swagger UI. | ||
You normally don't need to use or change this. | ||
""" | ||
# copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html | ||
html = """ | ||
<!doctype html> | ||
<html lang="en-US"> | ||
<head> | ||
<title>Swagger UI: OAuth2 Redirect</title> | ||
</head> | ||
<body> | ||
<script> | ||
'use strict'; | ||
function run () { | ||
var oauth2 = window.opener.swaggerUIRedirectOauth2; | ||
var sentState = oauth2.state; | ||
var redirectUrl = oauth2.redirectUrl; | ||
var isValid, qp, arr; | ||
if (/code|token|error/.test(window.location.hash)) { | ||
qp = window.location.hash.substring(1).replace('?', '&'); | ||
} else { | ||
qp = location.search.substring(1); | ||
} | ||
arr = qp.split("&"); | ||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';}); | ||
qp = qp ? JSON.parse('{' + arr.join() + '}', | ||
function (key, value) { | ||
return key === "" ? value : decodeURIComponent(value); | ||
} | ||
) : {}; | ||
isValid = qp.state === sentState; | ||
if (( | ||
oauth2.auth.schema.get("flow") === "accessCode" || | ||
oauth2.auth.schema.get("flow") === "authorizationCode" || | ||
oauth2.auth.schema.get("flow") === "authorization_code" | ||
) && !oauth2.auth.code) { | ||
if (!isValid) { | ||
oauth2.errCb({ | ||
authId: oauth2.auth.name, | ||
source: "auth", | ||
level: "warning", | ||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server." | ||
}); | ||
} | ||
if (qp.code) { | ||
delete oauth2.state; | ||
oauth2.auth.code = qp.code; | ||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl}); | ||
} else { | ||
let oauthErrorMsg; | ||
if (qp.error) { | ||
oauthErrorMsg = "["+qp.error+"]: " + | ||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") + | ||
(qp.error_uri ? "More info: "+qp.error_uri : ""); | ||
} | ||
oauth2.errCb({ | ||
authId: oauth2.auth.name, | ||
source: "auth", | ||
level: "error", | ||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server." | ||
}); | ||
} | ||
} else { | ||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl}); | ||
} | ||
window.close(); | ||
} | ||
if (document.readyState !== 'loading') { | ||
run(); | ||
} else { | ||
document.addEventListener('DOMContentLoaded', function () { | ||
run(); | ||
}); | ||
} | ||
</script> | ||
</body> | ||
</html> | ||
""" # noqa: E501 | ||
return HTMLResponse(content=html) |