1/**
2 * Swagger documentation UI for declarative `HttpApi` contracts.
3 *
4 * This module mounts a browser-based Swagger UI route on an `HttpRouter`. The
5 * page renders the OpenAPI document derived from the supplied `HttpApi`, so a
6 * running application can expose interactive API documentation without writing a
7 * separate OpenAPI file.
8 *
9 * @stability unstable
10 * @since 4.0.0
11 */
12import * as Effect from "../Effect.ts"
13import * as Function from "../Function.ts"
14import * as HttpRouter from "../http/HttpRouter.ts"
15import * as HttpServerResponse from "../http/HttpServerResponse.ts"
16import type * as Layer from "../Layer.ts"
17import type * as HttpApi from "./HttpApi.ts"
18import type * as HttpApiGroup from "./HttpApiGroup.ts"
19import * as Html from "./internal/html.ts"
20import * as internal from "./internal/httpApiSwagger.ts"
21import * as OpenApi from "./OpenApi.ts"
22
23const makeHandler = <Id extends string, Groups extends HttpApiGroup.Constraint>(options: {
24 readonly api: HttpApi.HttpApi<Id, Groups>
25}) => {
26 const makeResponse = Function.memoize((api: HttpApi.HttpApi<Id, Groups>) => {
27 const spec = OpenApi.fromApi(api)
28 return HttpServerResponse.html(`<!DOCTYPE html>
29<html lang="en">
30<head>
31 <meta charset="utf-8" />
32 <meta name="viewport" content="width=device-width, initial-scale=1" />
33 <title>${Html.escape(spec.info.title)} Documentation</title>
34 <style>${internal.css}</style>
35</head>
36<body>
37 <div id="swagger-ui"></div>
38 <script id="swagger-spec" type="application/json">
39 ${Html.escapeJson(spec)}
40 </script>
41 <script>
42 ${internal.javascript}
43 window.onload = () => {
44 window.ui = SwaggerUIBundle({
45 spec: JSON.parse(document.getElementById("swagger-spec").textContent),
46 dom_id: "#swagger-ui",
47 });
48 };
49 </script>
50</body>
51</html>`)
52 })
53 return Effect.sync(() => makeResponse(options.api))
54}
55
56/**
57 * Mounts Swagger UI for an `HttpApi` at the configured path, defaulting to
58 * `/docs`, using the OpenAPI specification generated from the API.
59 *
60 * @stability unstable
61 * @category layers
62 * @since 4.0.0
63 */
64export const layer = <Id extends string, Groups extends HttpApiGroup.Constraint>(
65 api: HttpApi.HttpApi<Id, Groups>,
66 options?: {
67 readonly path?: `/${string}` | undefined
68 }
69): Layer.Layer<never, never, HttpRouter.HttpRouter> =>
70 HttpRouter.use(Effect.fnUntraced(function*(router) {
71 const handler = makeHandler({ api })
72 yield* router.add("GET", options?.path ?? "/docs", handler)
73 }))