@visulima/api-platform 1.0.2 → 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.
- package/CHANGELOG.md +23 -0
- package/bin/index.js +46 -0
- package/dist/chunk-AOL5OFCG.mjs +284 -0
- package/dist/chunk-AOL5OFCG.mjs.map +1 -0
- package/dist/chunk-ATZDRT65.js +284 -0
- package/dist/chunk-ATZDRT65.js.map +1 -0
- package/dist/{chunk-AJKZCWFG.js → chunk-F7RHRCUQ.js} +2 -2
- package/dist/chunk-F7RHRCUQ.js.map +1 -0
- package/dist/{chunk-XXZ56SKG.mjs → chunk-RPHC5ZGB.mjs} +2 -2
- package/dist/chunk-RPHC5ZGB.mjs.map +1 -0
- package/dist/index-server.d.ts +4 -14
- package/dist/index-server.js +9 -10
- package/dist/index-server.js.map +1 -1
- package/dist/index-server.mjs +7 -8
- package/dist/index-server.mjs.map +1 -1
- package/dist/next/cli/index.js +8 -8
- package/dist/next/cli/index.js.map +1 -1
- package/dist/next/cli/index.mjs +8 -8
- package/dist/next/cli/index.mjs.map +1 -1
- package/dist/next/index-browser.js +2 -2
- package/dist/next/index-browser.mjs +1 -1
- package/dist/next/index-server.d.ts +14 -13
- package/dist/next/index-server.js +4 -4
- package/dist/next/index-server.js.map +1 -1
- package/dist/next/index-server.mjs +2 -2
- package/dist/next/index-server.mjs.map +1 -1
- package/dist/swagger-handler-ffed72c2.d.ts +19 -0
- package/next/cli/package.json +12 -3
- package/next/package.json +9 -0
- package/package.json +20 -20
- package/recipes/api/swagger.ts +12 -0
- package/recipes/pages/redoc-ui.tsx +5 -0
- package/recipes/pages/swagger-ui.tsx +5 -0
- package/dist/chunk-2LATTLUM.mjs +0 -166
- package/dist/chunk-2LATTLUM.mjs.map +0 -1
- package/dist/chunk-AJKZCWFG.js.map +0 -1
- package/dist/chunk-S7GUPAL4.js +0 -166
- package/dist/chunk-S7GUPAL4.js.map +0 -1
- package/dist/chunk-XXZ56SKG.mjs.map +0 -1
- package/src/connect/create-node-router.ts +0 -44
- package/src/connect/handler.ts +0 -46
- package/src/connect/middleware/cors-middleware.ts +0 -10
- package/src/connect/middleware/http-header-normalizer.ts +0 -93
- package/src/connect/middleware/rate-limiter-middleware.ts +0 -43
- package/src/connect/middleware/serializers-middleware.ts +0 -121
- package/src/connect/serializers/types.d.ts +0 -1
- package/src/connect/serializers/xml.ts +0 -13
- package/src/connect/serializers/yaml.ts +0 -7
- package/src/error-handler/jsonapi-error-handler.ts +0 -46
- package/src/error-handler/problem-error-handler.ts +0 -44
- package/src/error-handler/types.d.ts +0 -14
- package/src/error-handler/utils.ts +0 -39
- package/src/index-browser.tsx +0 -1
- package/src/index-server.ts +0 -75
- package/src/next/cli/index.ts +0 -2
- package/src/next/cli/list/api-route-file-parser.ts +0 -74
- package/src/next/cli/list/collect-api-route-files.ts +0 -42
- package/src/next/cli/list/list-command.ts +0 -105
- package/src/next/cli/list/routes-render.ts +0 -62
- package/src/next/cli/list/types.d.ts +0 -1
- package/src/next/index-browser.tsx +0 -3
- package/src/next/index-server.ts +0 -6
- package/src/next/routes/api/swagger.ts +0 -23
- package/src/next/routes/pages/swagger/get-static-properties-swagger.ts +0 -32
- package/src/next/routes/pages/swagger/redoc.tsx +0 -35
- package/src/next/routes/pages/swagger/swagger.tsx +0 -44
- package/src/next/webpack/with-open-api.ts +0 -63
- package/src/swagger/extend-swagger-spec.ts +0 -167
- package/src/swagger/swagger-handler.ts +0 -83
- package/src/utils.ts +0 -37
- package/src/zod/date-in-schema.ts +0 -57
- package/src/zod/date-out-schema.ts +0 -41
- package/src/zod/index.ts +0 -9
package/dist/chunk-S7GUPAL4.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }// src/swagger/swagger-handler.ts
|
|
2
|
-
var _crud = require('@visulima/crud');
|
|
3
|
-
var _debug = require('debug'); var _debug2 = _interopRequireDefault(_debug);
|
|
4
|
-
var _lodashmerge = require('lodash.merge'); var _lodashmerge2 = _interopRequireDefault(_lodashmerge);
|
|
5
|
-
var _fs = require('fs');
|
|
6
|
-
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
7
|
-
|
|
8
|
-
// src/connect/serializers/yaml.ts
|
|
9
|
-
var _yaml = require('yaml');
|
|
10
|
-
var yamlTransformer = (data) => _yaml.stringify.call(void 0, data, { indent: 2 });
|
|
11
|
-
var yaml_default = yamlTransformer;
|
|
12
|
-
|
|
13
|
-
// src/swagger/extend-swagger-spec.ts
|
|
14
|
-
var _case = require('case');
|
|
15
|
-
var extendComponentSchemas = (spec, schemaName, schema) => {
|
|
16
|
-
if (typeof spec.components !== "object") {
|
|
17
|
-
spec.components = {};
|
|
18
|
-
}
|
|
19
|
-
if (typeof spec.components.schemas !== "object") {
|
|
20
|
-
spec.components.schemas = {};
|
|
21
|
-
}
|
|
22
|
-
if (typeof spec.components.schemas[schemaName] === "undefined") {
|
|
23
|
-
spec.components.schemas[schemaName] = schema;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var extendComponentExamples = (spec, exampleName, example) => {
|
|
27
|
-
if (typeof spec.components !== "object") {
|
|
28
|
-
spec.components = {};
|
|
29
|
-
}
|
|
30
|
-
if (typeof spec.components.examples !== "object") {
|
|
31
|
-
spec.components.examples = {};
|
|
32
|
-
}
|
|
33
|
-
if (typeof spec.components.examples[exampleName] === "undefined") {
|
|
34
|
-
spec.components.examples[exampleName] = example;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
function extendSwaggerWithMediaTypeSchema(responseSpec, allowedMediaTypes, pathKey, spec, methodSpec, status) {
|
|
38
|
-
let examples;
|
|
39
|
-
Object.entries(responseSpec.content).forEach(([mediaName, contentSpec]) => {
|
|
40
|
-
if (typeof contentSpec.schema === "object") {
|
|
41
|
-
const { schema } = contentSpec;
|
|
42
|
-
if (mediaName === "application/json" && typeof contentSpec.examples !== "undefined") {
|
|
43
|
-
examples = contentSpec.examples;
|
|
44
|
-
}
|
|
45
|
-
if (typeof schema.$ref !== "undefined") {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const schemaIsArray = schema.type === "array";
|
|
49
|
-
Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {
|
|
50
|
-
var _a, _b, _c;
|
|
51
|
-
if (!allowed) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const schemaName = `${_case.header.call(void 0, pathKey.trim().replace("/", ""))}${mediaType === "application/ld+json" ? ".jsonld" : ""}`;
|
|
55
|
-
extendComponentSchemas(spec, schemaName, schema);
|
|
56
|
-
if (typeof ((_c = (_b = (_a = methodSpec == null ? void 0 : methodSpec.responses) == null ? void 0 : _a[status]) == null ? void 0 : _b.content[mediaType]) == null ? void 0 : _c.schema) === "undefined") {
|
|
57
|
-
methodSpec.responses[status].content[mediaType] = { schema: {} };
|
|
58
|
-
}
|
|
59
|
-
methodSpec.responses[status].content[mediaType].schema = schemaIsArray ? {
|
|
60
|
-
type: "array",
|
|
61
|
-
items: {
|
|
62
|
-
$ref: `#/components/schemas/${schemaName}`
|
|
63
|
-
}
|
|
64
|
-
} : {
|
|
65
|
-
$ref: `#/components/schemas/${schemaName}`
|
|
66
|
-
};
|
|
67
|
-
if (typeof methodSpec.produces === "undefined") {
|
|
68
|
-
methodSpec.produces = [];
|
|
69
|
-
}
|
|
70
|
-
methodSpec.produces.push(mediaType);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
return examples;
|
|
75
|
-
}
|
|
76
|
-
function extendSwaggerWithMediaTypeExamples(responseSpec, allowedMediaTypes, pathKey, spec, examples, methodSpec, status) {
|
|
77
|
-
Object.keys(responseSpec.content).forEach((mediaName) => {
|
|
78
|
-
if (mediaName === "application/json") {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {
|
|
82
|
-
var _a, _b, _c;
|
|
83
|
-
if (!allowed) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const examplesName = `${_case.header.call(void 0, pathKey.trim().replace("/", ""))}${mediaType === "application/ld+json" ? ".jsonld" : ""}`;
|
|
87
|
-
extendComponentExamples(spec, examplesName, examples);
|
|
88
|
-
if (typeof ((_c = (_b = (_a = methodSpec == null ? void 0 : methodSpec.responses) == null ? void 0 : _a[status]) == null ? void 0 : _b.content[mediaType]) == null ? void 0 : _c.examples) === "undefined") {
|
|
89
|
-
methodSpec.responses[status].content[mediaType] = { examples: {} };
|
|
90
|
-
}
|
|
91
|
-
methodSpec.responses[status].content[mediaType].examples = examples;
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
function extendSwaggerSpec(spec, allowedMediaTypes) {
|
|
96
|
-
if (typeof spec === "object" && typeof spec.paths === "object") {
|
|
97
|
-
Object.entries(spec.paths).forEach(([pathKey, pathSpec]) => {
|
|
98
|
-
Object.values(pathSpec).forEach((methodSpec) => {
|
|
99
|
-
if (typeof methodSpec.responses === "object") {
|
|
100
|
-
Object.entries(methodSpec.responses).forEach(([status, responseSpec]) => {
|
|
101
|
-
if (typeof responseSpec.content === "object") {
|
|
102
|
-
let examples = extendSwaggerWithMediaTypeSchema(responseSpec, allowedMediaTypes, pathKey, spec, methodSpec, status);
|
|
103
|
-
if (typeof examples !== "undefined") {
|
|
104
|
-
extendSwaggerWithMediaTypeExamples(responseSpec, allowedMediaTypes, pathKey, spec, examples, methodSpec, status);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
return spec;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/swagger/swagger-handler.ts
|
|
116
|
-
var swaggerCrudDebug = _debug2.default.call(void 0, "visulima:api-platform:swagger:crud:get-static-properties-swagger");
|
|
117
|
-
var swaggerHandler = (options = {}) => {
|
|
118
|
-
const {
|
|
119
|
-
allowedMediaTypes = {
|
|
120
|
-
"application/json": true
|
|
121
|
-
},
|
|
122
|
-
swaggerFilePath,
|
|
123
|
-
crud
|
|
124
|
-
} = options;
|
|
125
|
-
return async (request, response) => {
|
|
126
|
-
const swaggerPath = _path2.default.join(process.cwd(), swaggerFilePath || "swagger/swagger.json");
|
|
127
|
-
if (!_fs.existsSync.call(void 0, swaggerPath)) {
|
|
128
|
-
throw new Error(`Swagger file not found at ${swaggerPath}. Did you change the output path in "withOpenApi" inside the next.config.js file?`);
|
|
129
|
-
}
|
|
130
|
-
const fileContents = _fs.readFileSync.call(void 0, swaggerPath, "utf8");
|
|
131
|
-
let spec = extendSwaggerSpec(JSON.parse(fileContents), allowedMediaTypes);
|
|
132
|
-
let crudSwagger = {};
|
|
133
|
-
if (typeof crud !== "undefined") {
|
|
134
|
-
try {
|
|
135
|
-
const modelsOpenApi = await _crud.modelsToOpenApi.call(void 0, crud);
|
|
136
|
-
crudSwagger = {
|
|
137
|
-
components: { schemas: modelsOpenApi.schemas, examples: modelsOpenApi.examples },
|
|
138
|
-
tags: modelsOpenApi.tags,
|
|
139
|
-
paths: modelsOpenApi.paths
|
|
140
|
-
};
|
|
141
|
-
crudSwagger = extendSwaggerSpec(crudSwagger, allowedMediaTypes);
|
|
142
|
-
swaggerCrudDebug(JSON.stringify(crudSwagger, null, 2));
|
|
143
|
-
spec = _lodashmerge2.default.call(void 0, spec, crudSwagger);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.log(error);
|
|
146
|
-
throw new Error("Please install @visulima/crud to use the crud swagger generator.");
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (typeof request.headers.accept === "string" && /yaml|yml/.test(request.headers.accept)) {
|
|
150
|
-
response.statusCode = 200;
|
|
151
|
-
response.setHeader("Content-Type", request.headers.accept);
|
|
152
|
-
response.end(yaml_default(spec));
|
|
153
|
-
} else {
|
|
154
|
-
response.statusCode = 200;
|
|
155
|
-
response.setHeader("Content-Type", "application/json");
|
|
156
|
-
response.end(JSON.stringify(spec, null, 2));
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
var swagger_handler_default = swaggerHandler;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
exports.yaml_default = yaml_default; exports.swagger_handler_default = swagger_handler_default;
|
|
166
|
-
//# sourceMappingURL=chunk-S7GUPAL4.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/swagger/swagger-handler.ts","../src/connect/serializers/yaml.ts","../src/swagger/extend-swagger-spec.ts"],"names":[],"mappings":"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","sourcesContent":["// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies\nimport type { ModelsToOpenApiParameters, SwaggerModelsConfig } from \"@visulima/crud\";\n// eslint-disable-next-line unicorn/prevent-abbreviations,import/no-extraneous-dependencies\nimport { modelsToOpenApi } from \"@visulima/crud\";\nimport debug from \"debug\";\nimport merge from \"lodash.merge\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport path from \"node:path\";\nimport type { OAS3Definition, Tag } from \"swagger-jsdoc\";\n\nimport yamlTransformer from \"../connect/serializers/yaml\";\nimport extendSwaggerSpec from \"./extend-swagger-spec\";\n\n// eslint-disable-next-line testing-library/no-debugging-utils\nconst swaggerCrudDebug = debug(\"visulima:api-platform:swagger:crud:get-static-properties-swagger\");\n\nconst swaggerHandler = (\n options: Partial<{\n allowedMediaTypes: { [key: string]: boolean };\n swaggerFilePath: string;\n crud: Exclude<ModelsToOpenApiParameters, \"swagger\"> & {\n swagger?: {\n models?: SwaggerModelsConfig<string>;\n };\n };\n }> = {},\n) => {\n const {\n allowedMediaTypes = {\n \"application/json\": true,\n },\n swaggerFilePath,\n crud,\n } = options;\n\n return async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response) => {\n const swaggerPath = path.join(process.cwd(), swaggerFilePath || \"swagger/swagger.json\");\n\n if (!existsSync(swaggerPath)) {\n throw new Error(`Swagger file not found at ${swaggerPath}. Did you change the output path in \"withOpenApi\" inside the next.config.js file?`);\n }\n\n const fileContents = readFileSync(swaggerPath, \"utf8\");\n\n let spec = extendSwaggerSpec(JSON.parse(fileContents) as OAS3Definition, allowedMediaTypes) as OAS3Definition;\n let crudSwagger: Partial<OAS3Definition> = {};\n\n if (typeof crud !== \"undefined\") {\n try {\n const modelsOpenApi = await modelsToOpenApi(crud);\n\n crudSwagger = {\n components: { schemas: modelsOpenApi.schemas, examples: modelsOpenApi.examples },\n tags: modelsOpenApi.tags as Tag[],\n paths: modelsOpenApi.paths,\n };\n\n crudSwagger = extendSwaggerSpec(crudSwagger, allowedMediaTypes);\n\n swaggerCrudDebug(JSON.stringify(crudSwagger, null, 2));\n\n spec = merge(spec, crudSwagger);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.log(error);\n throw new Error(\"Please install @visulima/crud to use the crud swagger generator.\");\n }\n }\n\n if (typeof request.headers.accept === \"string\" && /yaml|yml/.test(request.headers.accept)) {\n response.statusCode = 200;\n response.setHeader(\"Content-Type\", request.headers.accept);\n response.end(yamlTransformer(spec));\n } else {\n response.statusCode = 200;\n response.setHeader(\"Content-Type\", \"application/json\");\n response.end(JSON.stringify(spec, null, 2));\n }\n };\n};\n\nexport default swaggerHandler;\n","import { stringify } from \"yaml\";\n\nimport type { Serializer } from \"./types\";\n\nconst yamlTransformer: Serializer = (data) => stringify(data, { indent: 2 });\n\nexport default yamlTransformer;\n","import { header as headerCase } from \"case\";\nimport type { OpenAPIV3 } from \"openapi-types\";\nimport type { OAS3Definition, Operation, Responses } from \"swagger-jsdoc\";\n\nconst extendComponentSchemas = (spec: Partial<OAS3Definition>, schemaName: string, schema: OpenAPIV3.SchemaObject) => {\n if (typeof spec.components !== \"object\") {\n // eslint-disable-next-line no-param-reassign\n spec.components = {};\n }\n\n if (typeof spec.components.schemas !== \"object\") {\n // eslint-disable-next-line no-param-reassign\n spec.components.schemas = {};\n }\n\n if (typeof spec.components.schemas[schemaName] === \"undefined\") {\n // eslint-disable-next-line no-param-reassign\n spec.components.schemas[schemaName] = schema;\n }\n};\n\nconst extendComponentExamples = (spec: Partial<OAS3Definition>, exampleName: string, example: OpenAPIV3.SchemaObject) => {\n if (typeof spec.components !== \"object\") {\n // eslint-disable-next-line no-param-reassign\n spec.components = {};\n }\n\n if (typeof spec.components.examples !== \"object\") {\n // eslint-disable-next-line no-param-reassign\n spec.components.examples = {};\n }\n\n if (typeof spec.components.examples[exampleName] === \"undefined\") {\n // eslint-disable-next-line no-param-reassign\n spec.components.examples[exampleName] = example;\n }\n};\n\nfunction extendSwaggerWithMediaTypeSchema(\n responseSpec: OpenAPIV3.ResponseObject,\n allowedMediaTypes: { [p: string]: boolean } | undefined,\n pathKey: string,\n spec: Partial<OAS3Definition>,\n methodSpec: Operation,\n status: string,\n) {\n let examples:\n | {\n [media: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject;\n }\n | undefined;\n\n Object.entries(responseSpec.content as object).forEach(([mediaName, contentSpec]) => {\n if (typeof contentSpec.schema === \"object\") {\n const { schema } = contentSpec;\n\n if (mediaName === \"application/json\" && typeof contentSpec.examples !== \"undefined\") {\n examples = contentSpec.examples;\n }\n\n if (typeof (schema as OpenAPIV3.ReferenceObject).$ref !== \"undefined\") {\n return;\n }\n\n const schemaIsArray = (schema as OpenAPIV3.SchemaObject).type === \"array\";\n\n Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {\n if (!allowed) {\n return;\n }\n\n // eslint-disable-next-line max-len\n const schemaName = `${headerCase(pathKey.trim().replace(\"/\", \"\"))}${mediaType === \"application/ld+json\" ? \".jsonld\" : \"\"}`;\n\n extendComponentSchemas(spec as OAS3Definition, schemaName, schema as OpenAPIV3.SchemaObject);\n\n if (typeof methodSpec?.responses?.[status]?.content[mediaType]?.schema === \"undefined\") {\n // eslint-disable-next-line no-param-reassign\n (methodSpec.responses as Responses)[status].content[mediaType] = { schema: {} };\n }\n\n // eslint-disable-next-line no-param-reassign\n (methodSpec.responses as Responses)[status].content[mediaType].schema = schemaIsArray\n ? {\n type: \"array\",\n items: {\n $ref: `#/components/schemas/${schemaName}`,\n },\n }\n : {\n $ref: `#/components/schemas/${schemaName}`,\n };\n\n if (typeof methodSpec.produces === \"undefined\") {\n // eslint-disable-next-line no-param-reassign\n methodSpec.produces = [];\n }\n\n methodSpec.produces.push(mediaType);\n });\n }\n });\n\n return examples;\n}\n\nfunction extendSwaggerWithMediaTypeExamples(\n responseSpec: OpenAPIV3.ResponseObject,\n allowedMediaTypes: { [p: string]: boolean } | undefined,\n pathKey: string,\n spec: Partial<OAS3Definition>,\n examples: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject } | undefined,\n methodSpec: Operation,\n status: string,\n) {\n Object.keys(responseSpec.content as object).forEach((mediaName) => {\n if (mediaName === \"application/json\") {\n return;\n }\n\n Object.entries(allowedMediaTypes || {}).forEach(([mediaType, allowed]) => {\n if (!allowed) {\n return;\n }\n\n // eslint-disable-next-line max-len\n const examplesName = `${headerCase(pathKey.trim().replace(\"/\", \"\"))}${mediaType === \"application/ld+json\" ? \".jsonld\" : \"\"}`;\n\n extendComponentExamples(spec as OAS3Definition, examplesName, examples as OpenAPIV3.SchemaObject);\n\n if (typeof methodSpec?.responses?.[status]?.content[mediaType]?.examples === \"undefined\") {\n // eslint-disable-next-line no-param-reassign\n (methodSpec.responses as Responses)[status].content[mediaType] = { examples: {} };\n }\n\n // eslint-disable-next-line no-param-reassign\n (methodSpec.responses as Responses)[status].content[mediaType].examples = examples;\n });\n });\n}\n\n// eslint-disable-next-line radar/cognitive-complexity\nexport default function extendSwaggerSpec(spec: Partial<OAS3Definition>, allowedMediaTypes?: { [key: string]: boolean }): Partial<OAS3Definition> {\n if (typeof spec === \"object\" && typeof spec.paths === \"object\") {\n Object.entries(spec.paths).forEach(([pathKey, pathSpec]) => {\n Object.values(pathSpec).forEach((methodSpec) => {\n if (typeof methodSpec.responses === \"object\") {\n Object.entries<OpenAPIV3.ResponseObject>(methodSpec.responses).forEach(([status, responseSpec]) => {\n if (typeof responseSpec.content === \"object\") {\n let examples:\n | {\n [media: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ExampleObject;\n }\n | undefined = extendSwaggerWithMediaTypeSchema(responseSpec, allowedMediaTypes, pathKey, spec, methodSpec, status);\n\n if (typeof examples !== \"undefined\") {\n extendSwaggerWithMediaTypeExamples(responseSpec, allowedMediaTypes, pathKey, spec, examples, methodSpec, status);\n }\n }\n });\n }\n });\n });\n }\n\n return spec;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/next/routes/pages/swagger/redoc.tsx","../src/next/routes/pages/swagger/swagger.tsx","../src/next/routes/pages/swagger/get-static-properties-swagger.ts"],"sourcesContent":["// eslint-disable-next-line import/no-extraneous-dependencies\nimport type { InferGetStaticPropsType, NextPage } from \"next\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport Head from \"next/head\";\nimport React from \"react\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport type { RedocStandaloneProps } from \"redoc\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport { RedocStandalone } from \"redoc\";\n\nimport getStaticProps from \"./get-static-properties-swagger\";\n\n// eslint-disable-next-line max-len\nconst RedocApiDocument: (\n name: string,\n swagger?: Exclude<RedocStandaloneProps, \"spec\">,\n // eslint-disable-next-line max-len,unicorn/no-useless-undefined\n) => NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (name, swagger = {}) => ({ swaggerData }: InferGetStaticPropsType<typeof getStaticProps>) => (\n <>\n <Head>\n <title>{name}</title>\n <style>\n {`\nbody {\n background: #fafafa !important;\n}\n`}\n </style>\n </Head>\n {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n <RedocStandalone {...swagger} spec={swaggerData} />\n </>\n);\n\nexport default RedocApiDocument;\n","import type { InferGetStaticPropsType, NextPage } from \"next\";\nimport dynamic from \"next/dynamic\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport Head from \"next/head\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport React from \"react\";\nimport type { SwaggerUIProps } from \"swagger-ui-react\";\n\nimport getStaticProps from \"./get-static-properties-swagger\";\n// eslint-disable-next-line import/no-extraneous-dependencies\nconst SwaggerUI = dynamic<{\n spec: any;\n // @ts-ignore\n // eslint-disable-next-line import/no-extraneous-dependencies\n}>(import(\"swagger-ui-react\"), { ssr: false });\ndynamic<{\n spec: any;\n // @ts-ignore\n // eslint-disable-next-line import/no-extraneous-dependencies\n}>(import(\"swagger-ui-react/swagger-ui.css\"), { ssr: false });\n\n// eslint-disable-next-line max-len\nconst SwaggerApiDocument: (\n name: string,\n swagger?: Exclude<SwaggerUIProps, \"spec\">,\n // eslint-disable-next-line max-len,unicorn/no-useless-undefined\n) => NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (name, swagger = {}) => ({ swaggerData }: InferGetStaticPropsType<typeof getStaticProps>) => (\n <>\n <Head>\n <title>{name}</title>\n <style>\n {`\nbody {\n background: #fafafa !important;\n}\n`}\n </style>\n </Head>\n {/* eslint-disable-next-line react/jsx-props-no-spreading */}\n <SwaggerUI {...swagger} spec={swaggerData} />\n </>\n);\n\nexport default SwaggerApiDocument;\n","// eslint-disable-next-line unicorn/prevent-abbreviations\nimport debug from \"debug\";\nimport type { GetStaticProps } from \"next\";\nimport type { OAS3Definition } from \"swagger-jsdoc\";\n\n// eslint-disable-next-line testing-library/no-debugging-utils\nconst swaggerDebug = debug(\"visulima:api-platform:swagger:get-static-properties-swagger\");\n\n// eslint-disable-next-line unicorn/consistent-function-scoping\nconst getStaticProps: (\n swaggerUrl: string,\n) => GetStaticProps = (swaggerUrl) => async (): Promise<{\n props: {\n swaggerUrl: string;\n swaggerData: OAS3Definition;\n };\n}> => {\n // eslint-disable-next-line compat/compat\n const response = await fetch(swaggerUrl);\n const swaggerData = await response.json();\n\n swaggerDebug(swaggerData);\n\n return {\n props: {\n swaggerUrl,\n swaggerData: JSON.parse(JSON.stringify(swaggerData)),\n },\n };\n};\n\nexport default getStaticProps;\n"],"mappings":";AAGA,OAAO,UAAU;AACjB,OAAO,WAAW;AAIlB,SAAS,uBAAuB;AAKhC,IAAM,mBAI0D,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,YAAY,MAC3F,0DACI,oCAAC,YACG,oCAAC,eAAO,IAAK,GACb,oCAAC,eACI;AAAA;AAAA;AAAA;AAAA,CAKL,CACJ,GAEA,oCAAC;AAAA,EAAiB,GAAG;AAAA,EAAS,MAAM;AAAA,CAAa,CACrD;AAGZ,IAAO,gBAAQ;;;ACjCf,OAAO,aAAa;AAEpB,OAAOA,WAAU;AAEjB,OAAOC,YAAW;AAKlB,IAAM,YAAY,QAIf,OAAO,qBAAqB,EAAE,KAAK,MAAM,CAAC;AAC7C,QAIG,OAAO,oCAAoC,EAAE,KAAK,MAAM,CAAC;AAG5D,IAAM,qBAI0D,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,YAAY,MAC3F,gBAAAA,OAAA,cAAAA,OAAA,gBACI,gBAAAA,OAAA,cAACD,OAAA,MACG,gBAAAC,OAAA,cAAC,eAAO,IAAK,GACb,gBAAAA,OAAA,cAAC,eACI;AAAA;AAAA;AAAA;AAAA,CAKL,CACJ,GAEA,gBAAAA,OAAA,cAAC;AAAA,EAAW,GAAG;AAAA,EAAS,MAAM;AAAA,CAAa,CAC/C;AAGZ,IAAO,kBAAQ;;;AC1Cf,OAAO,WAAW;AAKlB,IAAM,eAAe,MAAM,6DAA6D;AAGxF,IAAM,iBAEgB,CAAC,eAAe,YAKhC;AAEF,QAAM,WAAW,MAAM,MAAM,UAAU;AACvC,QAAM,cAAc,MAAM,SAAS,KAAK;AAExC,eAAa,WAAW;AAExB,SAAO;AAAA,IACH,OAAO;AAAA,MACH;AAAA,MACA,aAAa,KAAK,MAAM,KAAK,UAAU,WAAW,CAAC;AAAA,IACvD;AAAA,EACJ;AACJ;AAEA,IAAO,wCAAQ;","names":["Head","React"]}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { NodeRouter } from "@visulima/connect";
|
|
2
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
-
import type { AnyZodObject } from "zod";
|
|
4
|
-
import { ZodObject } from "zod";
|
|
5
|
-
|
|
6
|
-
import type { ErrorHandlers } from "../error-handler/types";
|
|
7
|
-
import { onError, onNoMatch } from "./handler";
|
|
8
|
-
import httpHeaderNormalizerMiddleware from "./middleware/http-header-normalizer";
|
|
9
|
-
import type { Serializers } from "./middleware/serializers-middleware";
|
|
10
|
-
import serializersMiddleware from "./middleware/serializers-middleware";
|
|
11
|
-
|
|
12
|
-
const createNodeRouter = <
|
|
13
|
-
Request extends IncomingMessage,
|
|
14
|
-
Response extends ServerResponse,
|
|
15
|
-
Schema extends AnyZodObject = ZodObject<{ body?: AnyZodObject; headers?: AnyZodObject; query?: AnyZodObject }>,
|
|
16
|
-
>(
|
|
17
|
-
options: {
|
|
18
|
-
middlewares?: {
|
|
19
|
-
"http-header-normalizer"?: { canonical?: boolean; normalizeHeaderKey?: (key: string, canonical: boolean) => string };
|
|
20
|
-
serializers?: {
|
|
21
|
-
serializers?: Serializers;
|
|
22
|
-
defaultContentType?: string;
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
errorHandlers?: ErrorHandlers;
|
|
26
|
-
showTrace?: boolean;
|
|
27
|
-
} = {},
|
|
28
|
-
) => {
|
|
29
|
-
const router = new NodeRouter<Request, Response, Schema>({
|
|
30
|
-
onNoMatch,
|
|
31
|
-
onError: onError(options.errorHandlers || [], options.showTrace || false),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return router
|
|
35
|
-
.use(httpHeaderNormalizerMiddleware(options?.middlewares?.["http-header-normalizer"] || {}))
|
|
36
|
-
.use(
|
|
37
|
-
serializersMiddleware(
|
|
38
|
-
options?.middlewares?.serializers?.serializers || [],
|
|
39
|
-
options?.middlewares?.serializers?.defaultContentType || "application/json; charset=utf-8",
|
|
40
|
-
),
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export default createNodeRouter;
|
package/src/connect/handler.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
FunctionLike, Nextable, Route, ValueOrPromise,
|
|
3
|
-
} from "@visulima/connect";
|
|
4
|
-
import createHttpError from "http-errors";
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
-
|
|
7
|
-
import JsonapiErrorHandler from "../error-handler/jsonapi-error-handler";
|
|
8
|
-
import ProblemErrorHandler from "../error-handler/problem-error-handler";
|
|
9
|
-
import type { ErrorHandler, ErrorHandlers } from "../error-handler/types";
|
|
10
|
-
|
|
11
|
-
// eslint-disable-next-line unicorn/consistent-function-scoping,max-len
|
|
12
|
-
export const onError = <Request extends IncomingMessage, Response extends ServerResponse>(errorHandlers: ErrorHandlers, showTrace: boolean) => async (error: unknown, request: Request, response: Response): Promise<void> => {
|
|
13
|
-
const apiFormat: string = request.headers.accept as string;
|
|
14
|
-
|
|
15
|
-
let errorHandler: ErrorHandler = ProblemErrorHandler;
|
|
16
|
-
|
|
17
|
-
if (apiFormat === "application/vnd.api+json") {
|
|
18
|
-
errorHandler = JsonapiErrorHandler;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
22
|
-
for (const { regex, handler } of errorHandlers) {
|
|
23
|
-
if (regex.test(apiFormat)) {
|
|
24
|
-
errorHandler = handler;
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// eslint-disable-next-line no-param-reassign
|
|
30
|
-
(error as { expose: boolean } & Error).expose = showTrace;
|
|
31
|
-
|
|
32
|
-
errorHandler(error, request, response);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const onNoMatch: <Request extends IncomingMessage, Response extends ServerResponse>(
|
|
36
|
-
request: Request,
|
|
37
|
-
response: Response,
|
|
38
|
-
routes: Route<Nextable<FunctionLike>>[],
|
|
39
|
-
) => ValueOrPromise<void> = async (request, response, routes) => {
|
|
40
|
-
const uniqueMethods = [...new Set(routes.map((route) => route.method))].join(", ");
|
|
41
|
-
|
|
42
|
-
response.setHeader("Allow", uniqueMethods);
|
|
43
|
-
response.statusCode = 405;
|
|
44
|
-
|
|
45
|
-
throw createHttpError(405, `No route with [${request.method}] method found.`);
|
|
46
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { expressWrapper } from "@visulima/connect";
|
|
2
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
3
|
-
import type { CorsOptions, CorsOptionsDelegate } from "cors";
|
|
4
|
-
import cors from "cors";
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
-
|
|
7
|
-
// eslint-disable-next-line max-len
|
|
8
|
-
const corsMiddleware = <Request extends IncomingMessage, Response extends ServerResponse>(options?: CorsOptions | CorsOptionsDelegate) => expressWrapper<Request, Response>(cors(options));
|
|
9
|
-
|
|
10
|
-
export default corsMiddleware;
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import type { NextHandler } from "@visulima/connect";
|
|
2
|
-
import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
|
|
3
|
-
|
|
4
|
-
const exceptions = {
|
|
5
|
-
alpn: "ALPN",
|
|
6
|
-
"c-pep": "C-PEP",
|
|
7
|
-
"c-pep-info": "C-PEP-Info",
|
|
8
|
-
"caldav-timezones": "CalDAV-Timezones",
|
|
9
|
-
"content-id": "Content-ID",
|
|
10
|
-
"content-md5": "Content-MD5",
|
|
11
|
-
dasl: "DASL",
|
|
12
|
-
dav: "DAV",
|
|
13
|
-
dnt: "DNT",
|
|
14
|
-
etag: "ETag",
|
|
15
|
-
getprofile: "GetProfile",
|
|
16
|
-
"http2-settings": "HTTP2-Settings",
|
|
17
|
-
"last-event-id": "Last-Event-ID",
|
|
18
|
-
"mime-version": "MIME-Version",
|
|
19
|
-
"optional-www-authenticate": "Optional-WWW-Authenticate",
|
|
20
|
-
"sec-websocket-accept": "Sec-WebSocket-Accept",
|
|
21
|
-
"sec-websocket-extensions": "Sec-WebSocket-Extensions",
|
|
22
|
-
"sec-webSocket-key": "Sec-WebSocket-Key",
|
|
23
|
-
"sec-webSocket-protocol": "Sec-WebSocket-Protocol",
|
|
24
|
-
"sec-webSocket-version": "Sec-WebSocket-Version",
|
|
25
|
-
slug: "SLUG",
|
|
26
|
-
tcn: "TCN",
|
|
27
|
-
te: "TE",
|
|
28
|
-
ttl: "TTL",
|
|
29
|
-
"www-authenticate": "WWW-Authenticate",
|
|
30
|
-
"x-att-deviceid": "X-ATT-DeviceId",
|
|
31
|
-
"x-dnsprefetch-control": "X-DNSPrefetch-Control",
|
|
32
|
-
"x-uidh": "X-UIDH",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const normalizeHeaderKey = (key: string, canonical: boolean) => {
|
|
36
|
-
const lowerCaseKey = key.toLowerCase();
|
|
37
|
-
|
|
38
|
-
if (!canonical) {
|
|
39
|
-
return lowerCaseKey;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (exceptions[lowerCaseKey as keyof typeof exceptions]) {
|
|
43
|
-
return exceptions[lowerCaseKey as keyof typeof exceptions];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
lowerCaseKey
|
|
48
|
-
.split("-")
|
|
49
|
-
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
50
|
-
.map((text) => text[0]?.toUpperCase() + text.slice(1))
|
|
51
|
-
.join("-")
|
|
52
|
-
);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const defaults = {
|
|
56
|
-
canonical: false,
|
|
57
|
-
normalizeHeaderKey,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* HTTP headers are case-insensitive.
|
|
62
|
-
* That's why NodeJS makes them lower case by default.
|
|
63
|
-
* While sensible, sometimes, for example for compatibility reasons, you might need them in their more common form.
|
|
64
|
-
*/
|
|
65
|
-
const httpHeaderNormalizerMiddleware = (options_?: { canonical?: boolean; normalizeHeaderKey?: (key: string, canonical: boolean) => string }) => {
|
|
66
|
-
const options = { ...defaults, ...options_ };
|
|
67
|
-
|
|
68
|
-
return async <Request extends IncomingMessage>(request: Request, _: any, next: NextHandler) => {
|
|
69
|
-
if (request.headers) {
|
|
70
|
-
const rawHeaders: IncomingHttpHeaders = {};
|
|
71
|
-
const headers: IncomingHttpHeaders = {};
|
|
72
|
-
|
|
73
|
-
Object.keys(request.headers).forEach((key) => {
|
|
74
|
-
rawHeaders[key] = request.headers[key];
|
|
75
|
-
|
|
76
|
-
const normalizedKey = options.normalizeHeaderKey(key, options.canonical);
|
|
77
|
-
|
|
78
|
-
if (typeof normalizedKey !== "undefined") {
|
|
79
|
-
headers[normalizedKey] = request.headers[key];
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
request.headers = headers;
|
|
84
|
-
// @TODO at type `request.rawHeaders` to global scope
|
|
85
|
-
// @ts-ignore
|
|
86
|
-
request.rawHeaders = rawHeaders;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return next();
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
export default httpHeaderNormalizerMiddleware;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { NextHandler } from "@visulima/connect";
|
|
2
|
-
import createHttpError from "http-errors";
|
|
3
|
-
import type { NextApiResponse } from "next";
|
|
4
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
5
|
-
import type { RateLimiterAbstract, RateLimiterRes } from "rate-limiter-flexible";
|
|
6
|
-
|
|
7
|
-
// eslint-disable-next-line max-len
|
|
8
|
-
const getIP: (request: IncomingMessage & { ip?: string }) => string | undefined = (request) => request?.ip
|
|
9
|
-
|| (request.headers["x-forwarded-for"] as string | undefined)
|
|
10
|
-
|| (request.headers["x-real-ip"] as string | undefined)
|
|
11
|
-
|| request.connection.remoteAddress;
|
|
12
|
-
|
|
13
|
-
type HeaderValue = string | number | ReadonlyArray<string>;
|
|
14
|
-
|
|
15
|
-
// eslint-disable-next-line max-len
|
|
16
|
-
const rateLimiterMiddleware = (rateLimiter: RateLimiterAbstract, headers?: (limiterResponse: RateLimiterRes) => { [key: string]: HeaderValue }) => async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response | NextApiResponse, next: NextHandler) => {
|
|
17
|
-
const ip = getIP(request);
|
|
18
|
-
|
|
19
|
-
if (typeof ip === "undefined") {
|
|
20
|
-
throw createHttpError(400, "Missing IP");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const limiter = await rateLimiter.consume(ip);
|
|
25
|
-
|
|
26
|
-
const mergedHeaders: { [key: string]: HeaderValue } = {
|
|
27
|
-
"Retry-After": Math.round(limiter.msBeforeNext / 1000) || 1,
|
|
28
|
-
"X-RateLimit-Remaining": limiter.remainingPoints,
|
|
29
|
-
"X-RateLimit-Reset": new Date(Date.now() + limiter.msBeforeNext).toISOString(),
|
|
30
|
-
...headers,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
Object.keys(mergedHeaders).forEach((key) => {
|
|
34
|
-
response.setHeader(key, mergedHeaders[key] as HeaderValue);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
await next();
|
|
38
|
-
} catch {
|
|
39
|
-
throw createHttpError(429, "Too Many Requests");
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export default rateLimiterMiddleware;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { NextHandler } from "@visulima/connect";
|
|
2
|
-
import accepts from "accepts";
|
|
3
|
-
import { header as headerCase } from "case";
|
|
4
|
-
import type { NextApiResponse } from "next";
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
-
|
|
7
|
-
import type { Serializer } from "../serializers/types";
|
|
8
|
-
import xmlTransformer from "../serializers/xml";
|
|
9
|
-
import yamlTransformer from "../serializers/yaml";
|
|
10
|
-
|
|
11
|
-
function hasJsonStructure(string_: any): boolean {
|
|
12
|
-
if (typeof string_ !== "string") {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const result = JSON.parse(string_);
|
|
18
|
-
const type = Object.prototype.toString.call(result);
|
|
19
|
-
|
|
20
|
-
return type === "[object Object]" || type === "[object Array]";
|
|
21
|
-
} catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const contentTypeKey = "Content-Type";
|
|
27
|
-
|
|
28
|
-
// eslint-disable-next-line max-len
|
|
29
|
-
const serialize = <Request extends IncomingMessage, Response extends ServerResponse>(
|
|
30
|
-
serializers: Serializers,
|
|
31
|
-
request: Request,
|
|
32
|
-
response: Response | NextApiResponse,
|
|
33
|
-
data: any,
|
|
34
|
-
options: {
|
|
35
|
-
defaultContentType: string;
|
|
36
|
-
},
|
|
37
|
-
// eslint-disable-next-line radar/cognitive-complexity
|
|
38
|
-
) => {
|
|
39
|
-
const contentType = response.getHeader(contentTypeKey) as string | undefined;
|
|
40
|
-
|
|
41
|
-
// skip serialization when Content-Type is already set
|
|
42
|
-
if (typeof contentType === "string") {
|
|
43
|
-
return data;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const accept = accepts(request);
|
|
47
|
-
const types: string[] = [...(accept.types() as string[]), options.defaultContentType];
|
|
48
|
-
|
|
49
|
-
let serializedData = data;
|
|
50
|
-
|
|
51
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
52
|
-
types.every((type) => {
|
|
53
|
-
let breakTypes = false;
|
|
54
|
-
|
|
55
|
-
serializers.forEach(({ regex, serializer }) => {
|
|
56
|
-
if (!regex.test(type)) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
response.setHeader(contentTypeKey, type);
|
|
61
|
-
serializedData = serializer(serializedData);
|
|
62
|
-
breakTypes = true;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (!breakTypes) {
|
|
66
|
-
if (/yaml|yml/.test(type)) {
|
|
67
|
-
response.setHeader(contentTypeKey, type);
|
|
68
|
-
|
|
69
|
-
serializedData = yamlTransformer(hasJsonStructure(data) ? JSON.parse(data) : data);
|
|
70
|
-
} else if (/xml/.test(type)) {
|
|
71
|
-
response.setHeader(contentTypeKey, type);
|
|
72
|
-
|
|
73
|
-
serializedData = xmlTransformer({
|
|
74
|
-
[headerCase(`${request.url?.replace("/api/", "")}`.trim())]: hasJsonStructure(data) ? JSON.parse(data) : data,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return breakTypes;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// eslint-disable-next-line no-param-reassign
|
|
83
|
-
return serializedData;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// eslint-disable-next-line max-len
|
|
87
|
-
const serializersMiddleware = (serializers: Serializers = [], defaultContentType: string = "application/json; charset=utf-8") => async <Request extends IncomingMessage, Response extends ServerResponse>(request: Request, response: Response | NextApiResponse, next: NextHandler) => {
|
|
88
|
-
if (typeof (response as NextApiResponse)?.send === "function") {
|
|
89
|
-
const oldSend = (response as NextApiResponse).send;
|
|
90
|
-
|
|
91
|
-
(response as NextApiResponse).send = (data) => {
|
|
92
|
-
(response as NextApiResponse).send = oldSend;
|
|
93
|
-
|
|
94
|
-
// eslint-disable-next-line no-param-reassign
|
|
95
|
-
data = serialize<Request, Response>(serializers, request, response, data, { defaultContentType });
|
|
96
|
-
|
|
97
|
-
return (response as NextApiResponse).send(data);
|
|
98
|
-
};
|
|
99
|
-
} else {
|
|
100
|
-
const oldEnd = response.end;
|
|
101
|
-
|
|
102
|
-
// @ts-ignore
|
|
103
|
-
response.end = (data, ...arguments_) => {
|
|
104
|
-
response.end = oldEnd;
|
|
105
|
-
|
|
106
|
-
// eslint-disable-next-line no-param-reassign
|
|
107
|
-
data = serialize<Request, Response>(serializers, request, response, data, { defaultContentType });
|
|
108
|
-
|
|
109
|
-
// @ts-ignore
|
|
110
|
-
return response.end(data, ...arguments_);
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return next();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export type Serializers = {
|
|
118
|
-
regex: RegExp;
|
|
119
|
-
serializer: Serializer;
|
|
120
|
-
}[];
|
|
121
|
-
export default serializersMiddleware;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type Serializer = (data: any) => string | Buffer | Uint8Array;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { XmlElement } from "jstoxml";
|
|
2
|
-
import { toXML } from "jstoxml";
|
|
3
|
-
|
|
4
|
-
import type { Serializer } from "./types";
|
|
5
|
-
|
|
6
|
-
const config = {
|
|
7
|
-
header: true,
|
|
8
|
-
indent: " ",
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const xmlTransformer: Serializer = (data?: XmlElement | XmlElement[]) => toXML(data, config);
|
|
12
|
-
|
|
13
|
-
export default xmlTransformer;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { HttpError } from "http-errors";
|
|
2
|
-
import { getReasonPhrase } from "http-status-codes";
|
|
3
|
-
import { ErrorSerializer, JapiError } from "ts-japi";
|
|
4
|
-
|
|
5
|
-
import type { ErrorHandler } from "./types";
|
|
6
|
-
import { addStatusCodeToResponse, sendJson, setErrorHeaders } from "./utils";
|
|
7
|
-
|
|
8
|
-
const defaultTitle = "An error occurred";
|
|
9
|
-
|
|
10
|
-
const jsonapiErrorHandler: ErrorHandler = (error: HttpError | JapiError | Error, _request, response) => {
|
|
11
|
-
addStatusCodeToResponse(response, error);
|
|
12
|
-
|
|
13
|
-
setErrorHeaders(response, error);
|
|
14
|
-
|
|
15
|
-
if (error instanceof JapiError || JapiError.isLikeJapiError(error)) {
|
|
16
|
-
const serializer = new ErrorSerializer();
|
|
17
|
-
|
|
18
|
-
sendJson(response, serializer.serialize(error));
|
|
19
|
-
} else if (error instanceof HttpError) {
|
|
20
|
-
const { statusCode, title, message } = error;
|
|
21
|
-
|
|
22
|
-
sendJson(response, {
|
|
23
|
-
errors: [
|
|
24
|
-
{
|
|
25
|
-
code: statusCode,
|
|
26
|
-
title: title || getReasonPhrase(statusCode) || defaultTitle,
|
|
27
|
-
detail: message,
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
} else {
|
|
32
|
-
const { message } = error;
|
|
33
|
-
|
|
34
|
-
sendJson(response, {
|
|
35
|
-
errors: [
|
|
36
|
-
{
|
|
37
|
-
code: "500",
|
|
38
|
-
title: getReasonPhrase(response.statusCode) || defaultTitle,
|
|
39
|
-
detail: message,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export default jsonapiErrorHandler;
|