api-json-server 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,183 +1,97 @@
1
- import type { FastifyInstance, FastifyRequest } from 'fastify'
2
- import type { MockSpecInferSchema, EndpointSpecInferSchema } from './spec.js'
3
- import { renderTemplate } from './template.js';
4
-
5
-
6
- function normalizeMethod(method: EndpointSpecInferSchema["method"]): Lowercase<EndpointSpecInferSchema["method"]> {
7
- return method.toLowerCase() as Lowercase<EndpointSpecInferSchema["method"]>;
8
- }
9
-
10
- function sleep(ms: number): Promise<void> {
11
- return new Promise((resolve) => setTimeout(resolve, ms));
12
- }
13
-
14
- function shouldFail(errorRate: number): boolean {
15
- if (errorRate <= 0) return false;
16
- if (errorRate >= 1) return true;
17
- return Math.random() < errorRate;
18
- }
19
-
20
-
21
- function resolveBehavior(spec: MockSpecInferSchema, endpoint: EndpointSpecInferSchema) {
22
- const settings = spec.settings;
23
-
24
- const delayMs = endpoint.delayMs ?? settings.delayMs;
25
- const errorRate = endpoint.errorRate ?? settings.errorRate;
26
-
27
- const errorStatus = endpoint.errorStatus ?? settings.errorStatus;
28
- const errorResponse = endpoint.errorResponse ?? settings.errorResponse;
29
-
30
- return { delayMs, errorRate, errorStatus, errorResponse };
31
- }
32
-
33
-
34
- function asRecord(value: unknown): Record<string, unknown> {
35
- if (value && typeof value === "object" && !Array.isArray(value)) return value as Record<string, unknown>;
36
- return {};
37
- }
38
-
39
- function queryMatches(req: FastifyRequest, endpoint: EndpointSpecInferSchema): boolean {
40
- const required = endpoint.match?.query;
41
- if (!required) return true;
42
-
43
- const q = asRecord(req.query);
44
-
45
- for (const [key, expected] of Object.entries(required)) {
46
- const actual = q[key];
47
-
48
- if (Array.isArray(actual)) return false;
49
-
50
- if (String(actual ?? "") !== String(expected)) return false;
51
- }
52
-
53
- return true;
54
- }
55
-
56
- function bodyMatches(req: FastifyRequest, expected?: Record<string, string | number | boolean>): boolean {
57
- if (!expected) return true;
58
-
59
- const b = req.body;
60
- if (!b || typeof b !== "object" || Array.isArray(b)) return false;
61
-
62
- const body = b as Record<string, unknown>;
63
-
64
- for (const [key, exp] of Object.entries(expected)) {
65
- const actual = body[key];
66
- if (String(actual ?? "") !== String(exp)) return false;
67
- }
68
- return true;
69
- }
70
-
71
- function matchRequest(req: FastifyRequest, match?: { query?: Record<string, any>; body?: Record<string, any> }): boolean {
72
- if (!match) return true;
73
-
74
- // query exact match
75
- const requiredQuery = match.query;
76
- if (requiredQuery) {
77
- const q = asRecord(req.query);
78
- for (const [key, expected] of Object.entries(requiredQuery)) {
79
- const actual = q[key];
80
- if (Array.isArray(actual)) return false;
81
- if (String(actual ?? "") !== String(expected)) return false;
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
2
+ import type { MockSpecInferSchema, EndpointSpecInferSchema, TemplateValue } from "./spec.js";
3
+ import { matchRequest, toRecord } from "./requestMatch.js";
4
+ import { createRenderContext, renderTemplateValue } from "./responseRenderer.js";
5
+ import { resolveBehavior, shouldFail, sleep, type BehaviorOverrides } from "./behavior.js";
6
+
7
+ type ResponseSource = {
8
+ status?: number;
9
+ response: TemplateValue;
10
+ } & BehaviorOverrides;
11
+
12
+ /**
13
+ * Select a response source from the first matching variant.
14
+ */
15
+ function selectVariant(req: FastifyRequest, endpoint: EndpointSpecInferSchema): ResponseSource | null {
16
+ if (!endpoint.variants || endpoint.variants.length === 0) return null;
17
+ for (const variant of endpoint.variants) {
18
+ if (matchRequest(req, variant.match)) {
19
+ return {
20
+ status: variant.status,
21
+ response: variant.response,
22
+ delayMs: variant.delayMs,
23
+ errorRate: variant.errorRate,
24
+ errorStatus: variant.errorStatus,
25
+ errorResponse: variant.errorResponse
26
+ };
82
27
  }
83
28
  }
84
-
85
- // body exact match (top-level)
86
- if (!bodyMatches(req, match.body)) return false;
87
-
88
- return true;
29
+ return null;
89
30
  }
90
31
 
32
+ /**
33
+ * Build a response source from the endpoint itself.
34
+ */
35
+ function selectEndpointSource(endpoint: EndpointSpecInferSchema): ResponseSource {
36
+ return {
37
+ status: endpoint.status,
38
+ response: endpoint.response,
39
+ delayMs: endpoint.delayMs,
40
+ errorRate: endpoint.errorRate,
41
+ errorStatus: endpoint.errorStatus,
42
+ errorResponse: endpoint.errorResponse
43
+ };
44
+ }
91
45
 
46
+ /**
47
+ * Register all endpoints defined in a mock spec.
48
+ */
92
49
  export function registerEndpoints(app: FastifyInstance, spec: MockSpecInferSchema): void {
93
50
  for (const endpoint of spec.endpoints) {
94
51
  app.route({
95
52
  method: endpoint.method,
96
53
  url: endpoint.path,
97
- handler: async (req, reply) => {
98
- // Decide which "response source" to use: variant or base endpoint
99
- let chosen:
100
- | {
101
- status?: number;
102
- response: unknown;
103
- delayMs?: number;
104
- errorRate?: number;
105
- errorStatus?: number;
106
- errorResponse?: unknown;
107
- }
108
- | null = null;
109
-
110
- // 1) Try variants first (first match wins)
111
- if (endpoint.variants && endpoint.variants.length > 0) {
112
- for (const v of endpoint.variants) {
113
- if (matchRequest(req, v.match)) {
114
- chosen = {
115
- status: v.status,
116
- response: v.response,
117
- delayMs: v.delayMs,
118
- errorRate: v.errorRate,
119
- errorStatus: v.errorStatus,
120
- errorResponse: v.errorResponse
121
- };
122
- break;
123
- }
124
- }
125
- }
126
-
127
- // 2) If no variant matched, fall back to endpoint-level match/response
128
- if (!chosen) {
129
- // Backward compatible: if your endpoint only has query match, this still works
130
- // If you've upgraded schema to endpoint.match (query/body), this uses it
131
- // If you haven't, you can replace endpoint.match with: { query: endpoint.match?.query }
132
- const endpointMatch = (endpoint as any).match ?? { query: (endpoint as any).match?.query };
133
-
134
- if (!matchRequest(req, endpointMatch)) {
135
- reply.code(404);
136
- return { error: "No matching mock for request" };
137
- }
54
+ handler: buildEndpointHandler(spec, endpoint)
55
+ });
138
56
 
139
- chosen = {
140
- status: endpoint.status,
141
- response: endpoint.response,
142
- delayMs: (endpoint as any).delayMs,
143
- errorRate: (endpoint as any).errorRate,
144
- errorStatus: (endpoint as any).errorStatus,
145
- errorResponse: (endpoint as any).errorResponse
146
- };
147
- }
57
+ app.log.info(
58
+ `Registered ${endpoint.method} ${endpoint.path} -> ${endpoint.status} (delay=${endpoint.delayMs ?? spec.settings.delayMs}ms, errorRate=${endpoint.errorRate ?? spec.settings.errorRate})`
59
+ );
60
+ }
61
+ }
148
62
 
149
- // 3) Resolve behavior: chosen overrides -> endpoint -> global settings
150
- const settings = spec.settings;
63
+ /**
64
+ * Build a Fastify handler for a single endpoint definition.
65
+ */
66
+ function buildEndpointHandler(spec: MockSpecInferSchema, endpoint: EndpointSpecInferSchema) {
67
+ return async (req: FastifyRequest, reply: FastifyReply) => {
68
+ const variant = selectVariant(req, endpoint);
151
69
 
152
- const delayMs = chosen.delayMs ?? (endpoint as any).delayMs ?? settings.delayMs;
153
- const errorRate = chosen.errorRate ?? (endpoint as any).errorRate ?? settings.errorRate;
154
- const errorStatus = chosen.errorStatus ?? (endpoint as any).errorStatus ?? settings.errorStatus;
155
- const errorResponse = chosen.errorResponse ?? (endpoint as any).errorResponse ?? settings.errorResponse;
70
+ if (!variant && !matchRequest(req, endpoint.match)) {
71
+ reply.code(404);
72
+ return { error: "No matching mock for request" };
73
+ }
156
74
 
157
- if (delayMs > 0) {
158
- await sleep(delayMs);
159
- }
75
+ const source = variant ?? selectEndpointSource(endpoint);
76
+ const behavior = resolveBehavior(spec.settings, endpoint, source);
160
77
 
161
- if (shouldFail(errorRate)) {
162
- reply.code(errorStatus);
163
- return errorResponse;
164
- }
78
+ if (behavior.delayMs > 0) {
79
+ await sleep(behavior.delayMs);
80
+ }
165
81
 
166
- // 4) Template rendering using request context
167
- const params = asRecord(req.params);
168
- const query = asRecord(req.query);
169
- const body = req.body;
82
+ const params = toRecord(req.params);
83
+ const query = toRecord(req.query);
84
+ const body = req.body;
85
+ const renderContext = createRenderContext({ params, query, body }, spec.settings.fakerSeed);
170
86
 
171
- const rendered = renderTemplate(chosen.response, { params, query, body });
87
+ if (shouldFail(behavior.errorRate)) {
88
+ reply.code(behavior.errorStatus);
89
+ return renderTemplateValue(behavior.errorResponse, renderContext);
90
+ }
172
91
 
173
- // 5) Status code precedence: chosen -> endpoint -> 200
174
- reply.code(chosen.status ?? endpoint.status ?? 200);
175
- return rendered;
176
- }
177
- });
92
+ const rendered = renderTemplateValue(source.response, renderContext);
178
93
 
179
- app.log.info(
180
- `Registered ${endpoint.method} ${endpoint.path} -> ${endpoint.status} (delay=${endpoint.delayMs ?? spec.settings.delayMs}ms, errorRate=${endpoint.errorRate ?? spec.settings.errorRate})`
181
- );
182
- }
94
+ reply.code(source.status ?? endpoint.status ?? 200);
95
+ return rendered;
96
+ };
183
97
  }
@@ -0,0 +1,62 @@
1
+ import type { FastifyRequest } from "fastify";
2
+ import type { Primitive } from "./spec.js";
3
+
4
+ export type MatchRule = {
5
+ query?: Record<string, Primitive>;
6
+ body?: Record<string, Primitive>;
7
+ };
8
+
9
+ /**
10
+ * Safely coerce a value to a plain object record.
11
+ */
12
+ /**
13
+ * Safely coerce a value to a plain object record.
14
+ */
15
+ export function toRecord(value: unknown): Record<string, unknown> {
16
+ if (value && typeof value === "object" && !Array.isArray(value)) return value as Record<string, unknown>;
17
+ return {};
18
+ }
19
+
20
+ /**
21
+ * Check if the request query matches the expected values.
22
+ */
23
+ function queryMatches(req: FastifyRequest, expected?: Record<string, Primitive>): boolean {
24
+ if (!expected) return true;
25
+ const query = toRecord(req.query);
26
+
27
+ for (const [key, exp] of Object.entries(expected)) {
28
+ const actual = query[key];
29
+ if (Array.isArray(actual)) return false;
30
+ if (String(actual ?? "") !== String(exp)) return false;
31
+ }
32
+
33
+ return true;
34
+ }
35
+
36
+ /**
37
+ * Check if the request body matches the expected top-level values.
38
+ */
39
+ function bodyMatches(req: FastifyRequest, expected?: Record<string, Primitive>): boolean {
40
+ if (!expected) return true;
41
+
42
+ const body = req.body;
43
+ if (!body || typeof body !== "object" || Array.isArray(body)) return false;
44
+
45
+ const record = body as Record<string, unknown>;
46
+ for (const [key, exp] of Object.entries(expected)) {
47
+ const actual = record[key];
48
+ if (String(actual ?? "") !== String(exp)) return false;
49
+ }
50
+
51
+ return true;
52
+ }
53
+
54
+ /**
55
+ * Check if a request matches query/body rules.
56
+ */
57
+ export function matchRequest(req: FastifyRequest, match?: MatchRule): boolean {
58
+ if (!match) return true;
59
+ if (!queryMatches(req, match.query)) return false;
60
+ if (!bodyMatches(req, match.body)) return false;
61
+ return true;
62
+ }
@@ -0,0 +1,112 @@
1
+ import { faker as baseFaker, type Faker } from "@faker-js/faker";
2
+ import type { FakerTemplate, RepeatTemplate, TemplateValue } from "./spec.js";
3
+ import { renderStringTemplate, type TemplateContext } from "./stringTemplate.js";
4
+
5
+ export type RenderContext = TemplateContext & {
6
+ faker: Faker;
7
+ };
8
+
9
+ /**
10
+ * Create a render context with an optional faker seed applied.
11
+ */
12
+ export function createRenderContext(ctx: TemplateContext, fakerSeed?: number): RenderContext {
13
+ if (typeof fakerSeed === "number") {
14
+ baseFaker.seed(fakerSeed);
15
+ }
16
+ return { ...ctx, faker: baseFaker };
17
+ }
18
+
19
+ /**
20
+ * Check if a template value is a faker directive.
21
+ */
22
+ function isFakerTemplate(value: TemplateValue): value is FakerTemplate {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "__faker" in value;
24
+ }
25
+
26
+ /**
27
+ * Check if a template value is a repeat directive.
28
+ */
29
+ function isRepeatTemplate(value: TemplateValue): value is RepeatTemplate {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "__repeat" in value;
31
+ }
32
+
33
+ /**
34
+ * Resolve a faker method path (e.g. "person.firstName") to a callable function.
35
+ */
36
+ function resolveFakerMethod(faker: Faker, methodPath: string): (...args: unknown[]) => unknown {
37
+ const parts = methodPath.split(".");
38
+ let current: unknown = faker;
39
+
40
+ for (const part of parts) {
41
+ if (current === null || current === undefined) {
42
+ throw new Error(`Faker method not found: ${methodPath}`);
43
+ }
44
+ if (typeof current !== "object" && typeof current !== "function") {
45
+ throw new Error(`Faker method not found: ${methodPath}`);
46
+ }
47
+
48
+ const record = current as Record<string, unknown>;
49
+ current = record[part];
50
+ }
51
+
52
+ if (typeof current !== "function") {
53
+ throw new Error(`Faker method is not callable: ${methodPath}`);
54
+ }
55
+
56
+ return current as (...args: unknown[]) => unknown;
57
+ }
58
+
59
+ /**
60
+ * Render a faker directive to a concrete value.
61
+ */
62
+ function renderFakerTemplate(template: FakerTemplate, ctx: RenderContext): unknown {
63
+ const faker = ctx.faker;
64
+ const raw = template.__faker;
65
+ const methodPath = typeof raw === "string" ? raw : raw.method;
66
+ const args = typeof raw === "string" ? [] : raw.args ?? [];
67
+ const renderedArgs = args.map((arg) => renderTemplateValue(arg, ctx));
68
+ const method = resolveFakerMethod(faker, methodPath);
69
+ return method(...renderedArgs);
70
+ }
71
+
72
+ /**
73
+ * Render a repeat directive to an array of rendered items.
74
+ */
75
+ function renderRepeatTemplate(template: RepeatTemplate, ctx: RenderContext): unknown[] {
76
+ const faker = ctx.faker;
77
+ const { count, min, max, template: itemTemplate } = template.__repeat;
78
+ const minValue = typeof min === "number" ? min : 0;
79
+
80
+ if (typeof count === "number") {
81
+ return Array.from({ length: count }, () => renderTemplateValue(itemTemplate, ctx));
82
+ }
83
+
84
+ const upper = typeof max === "number" ? max : minValue;
85
+ if (upper < minValue) {
86
+ throw new Error(`Repeat max must be >= min (min=${minValue}, max=${upper})`);
87
+ }
88
+
89
+ const total = faker.number.int({ min: minValue, max: upper });
90
+ return Array.from({ length: total }, () => renderTemplateValue(itemTemplate, ctx));
91
+ }
92
+
93
+ /**
94
+ * Render a template value into a concrete JSON-compatible value.
95
+ */
96
+ export function renderTemplateValue(value: TemplateValue, ctx: RenderContext): unknown {
97
+ if (typeof value === "string") return renderStringTemplate(value, ctx);
98
+ if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
99
+
100
+ if (Array.isArray(value)) {
101
+ return value.map((item) => renderTemplateValue(item, ctx));
102
+ }
103
+
104
+ if (isFakerTemplate(value)) return renderFakerTemplate(value, ctx);
105
+ if (isRepeatTemplate(value)) return renderRepeatTemplate(value, ctx);
106
+
107
+ const output: Record<string, unknown> = {};
108
+ for (const [key, item] of Object.entries(value)) {
109
+ output[key] = renderTemplateValue(item, ctx);
110
+ }
111
+ return output;
112
+ }
package/src/server.ts CHANGED
@@ -1,29 +1,178 @@
1
- import Fastify, { FastifyInstance } from "fastify";
2
- import { MockSpecInferSchema } from "./spec.js";
1
+ import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } from "fastify";
2
+ import type { MockSpecInferSchema } from "./spec.js";
3
3
  import { registerEndpoints } from "./registerEndpoints.js";
4
+ import swaggerUiDist from "swagger-ui-dist";
5
+ import { generateOpenApi } from "./openapi.js";
6
+ import fastifyStatic from "@fastify/static";
7
+ import YAML from "yaml";
4
8
 
5
- export function buildServer(spec: MockSpecInferSchema, meta?: { specPath?: string; loadedAt?: string }): FastifyInstance {
9
+ type SwaggerUiDistModule = {
10
+ getAbsoluteFSPath?: () => string;
11
+ };
12
+
13
+ /**
14
+ * Resolve the path to swagger-ui-dist assets.
15
+ */
16
+ function getSwaggerUiRoot(): string {
17
+ // swagger-ui-dist can be CJS or ESM depending on environment.
18
+ // CJS: require("swagger-ui-dist").getAbsoluteFSPath()
19
+ // ESM: default export may itself be the function.
20
+ const mod: unknown = swaggerUiDist;
21
+
22
+ if (typeof mod === "function") return mod();
23
+ if (mod && typeof (mod as SwaggerUiDistModule).getAbsoluteFSPath === "function") {
24
+ return (mod as SwaggerUiDistModule).getAbsoluteFSPath!();
25
+ }
26
+
27
+ throw new Error("swagger-ui-dist: cannot determine absolute FS path to dist assets");
28
+ }
29
+
30
+ /**
31
+ * Resolve the server URL for OpenAPI output.
32
+ */
33
+ function resolveServerUrl(req: FastifyRequest, baseUrl?: string): string {
34
+ if (baseUrl && baseUrl.trim().length > 0) return baseUrl.trim();
35
+
36
+ const host = req.headers.host ?? "localhost";
37
+ const protocol = req.protocol ?? "http";
38
+ return `${protocol}://${host}`;
39
+ }
40
+
41
+ /**
42
+ * Create a Fastify server with mock endpoints and docs.
43
+ */
44
+ export function buildServer(
45
+ spec: MockSpecInferSchema,
46
+ meta?: { specPath?: string; loadedAt?: string; baseUrl?: string }
47
+ ): FastifyInstance {
6
48
  const app = Fastify({
7
- logger: true
49
+ logger: true,
50
+ trustProxy: true
8
51
  });
9
52
 
53
+ /**
54
+ * Handler for the /__spec route with bound metadata.
55
+ */
56
+ function specRouteHandler() {
57
+ return specHandler(spec, meta);
58
+ }
59
+
60
+ /**
61
+ * Handler for the /__openapi.json route.
62
+ */
63
+ function openApiJsonRouteHandler(req: FastifyRequest) {
64
+ return openApiJsonHandler(req, spec, meta?.baseUrl);
65
+ }
66
+
67
+ /**
68
+ * Handler for the /__openapi.yaml route.
69
+ */
70
+ function openApiYamlRouteHandler(req: FastifyRequest, reply: FastifyReply): void {
71
+ return openApiYamlHandler(req, reply, spec, meta?.baseUrl);
72
+ }
73
+
74
+ /**
75
+ * Handler for the /docs route.
76
+ */
77
+ function docsRouteHandler(req: FastifyRequest, reply: FastifyReply): void {
78
+ return docsHandler(req, reply);
79
+ }
80
+
10
81
  // Basic sanity route
11
- app.get("/health", async () => {
12
- return { ok: true };
13
- });
82
+ app.get("/health", healthHandler);
14
83
 
15
84
  // Internal inspection endpoint
16
- app.get("/__spec", async () => {
17
- return {
18
- meta: {
19
- specPath: meta?.specPath ?? null,
20
- loadedAt: meta?.loadedAt ?? null
21
- },
22
- spec
23
- };
85
+ app.get("/__spec", specRouteHandler);
86
+
87
+ app.get("/__openapi.json", openApiJsonRouteHandler);
88
+
89
+ app.get("/__openapi.yaml", openApiYamlRouteHandler);
90
+
91
+ app.register(fastifyStatic, {
92
+ root: getSwaggerUiRoot(),
93
+ prefix: "/docs/assets/",
94
+ decorateReply: false
24
95
  });
25
96
 
97
+ app.get("/docs", docsRouteHandler);
98
+
26
99
  registerEndpoints(app, spec);
27
100
 
28
101
  return app;
29
102
  }
103
+
104
+ /**
105
+ * Health check handler.
106
+ */
107
+ function healthHandler(): { ok: true } {
108
+ return { ok: true };
109
+ }
110
+
111
+ /**
112
+ * Debug handler that exposes the current spec and metadata.
113
+ */
114
+ function specHandler(spec: MockSpecInferSchema, meta?: { specPath?: string; loadedAt?: string }) {
115
+ return {
116
+ meta: {
117
+ specPath: meta?.specPath ?? null,
118
+ loadedAt: meta?.loadedAt ?? null
119
+ },
120
+ spec
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Serve the OpenAPI document as JSON.
126
+ */
127
+ function openApiJsonHandler(req: FastifyRequest, spec: MockSpecInferSchema, baseUrl?: string) {
128
+ const serverUrl = resolveServerUrl(req, baseUrl);
129
+ return generateOpenApi(spec, serverUrl);
130
+ }
131
+
132
+ /**
133
+ * Serve the OpenAPI document as YAML.
134
+ */
135
+ function openApiYamlHandler(
136
+ req: FastifyRequest,
137
+ reply: FastifyReply,
138
+ spec: MockSpecInferSchema,
139
+ baseUrl?: string
140
+ ): void {
141
+ const serverUrl = resolveServerUrl(req, baseUrl);
142
+ const doc = generateOpenApi(spec, serverUrl);
143
+ const yaml = YAML.stringify(doc);
144
+ reply.type("application/yaml; charset=utf-8").send(yaml);
145
+ }
146
+
147
+ /**
148
+ * Serve the Swagger UI HTML page.
149
+ */
150
+ function docsHandler(_req: FastifyRequest, reply: FastifyReply): void {
151
+ const html = `<!DOCTYPE html>
152
+ <html lang="en">
153
+ <head>
154
+ <meta charset="UTF-8" />
155
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
156
+ <title>mockserve docs</title>
157
+ <link rel="stylesheet" href="/docs/assets/swagger-ui.css" />
158
+ <style>
159
+ html, body { margin: 0; padding: 0; }
160
+ </style>
161
+ </head>
162
+ <body>
163
+ <div id="swagger-ui"></div>
164
+
165
+ <script src="/docs/assets/swagger-ui-bundle.js"></script>
166
+ <script src="/docs/assets/swagger-ui-standalone-preset.js"></script>
167
+ <script>
168
+ window.ui = SwaggerUIBundle({
169
+ url: "/__openapi.json",
170
+ dom_id: "#swagger-ui",
171
+ presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
172
+ layout: "BaseLayout"
173
+ });
174
+ </script>
175
+ </body>
176
+ </html>`;
177
+ reply.type("text/html; charset=utf-8").send(html);
178
+ }