snap-on-openapi 1.0.1
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/README.md +643 -0
- package/dist/OpenApi.d.ts +58 -0
- package/dist/OpenApi.js +272 -0
- package/dist/README.md +643 -0
- package/dist/assets/stoplight.html +27 -0
- package/dist/assets/swagger.html +892 -0
- package/dist/enums/ErrorCode.d.ts +5 -0
- package/dist/enums/ErrorCode.js +6 -0
- package/dist/enums/Methods.d.ts +7 -0
- package/dist/enums/Methods.js +8 -0
- package/dist/enums/SampleRouteType.d.ts +3 -0
- package/dist/enums/SampleRouteType.js +4 -0
- package/dist/enums/ValidationLocations.d.ts +6 -0
- package/dist/enums/ValidationLocations.js +7 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +23 -0
- package/dist/services/ClientGenerator/ClientGenerator.d.ts +9 -0
- package/dist/services/ClientGenerator/ClientGenerator.js +48 -0
- package/dist/services/ConfigBuilder/ConfigBuilder.d.ts +31 -0
- package/dist/services/ConfigBuilder/ConfigBuilder.js +65 -0
- package/dist/services/ConfigBuilder/types/DefaultConfig.d.ts +22 -0
- package/dist/services/ConfigBuilder/types/DefaultConfig.js +47 -0
- package/dist/services/ConfigBuilder/types/DefaultErrorMap.d.ts +113 -0
- package/dist/services/ConfigBuilder/types/DefaultErrorMap.js +21 -0
- package/dist/services/ConfigBuilder/types/DefaultRouteContextMap.d.ts +6 -0
- package/dist/services/ConfigBuilder/types/DefaultRouteContextMap.js +3 -0
- package/dist/services/ConfigBuilder/types/DefaultRouteMap.d.ts +12 -0
- package/dist/services/ConfigBuilder/types/DefaultRouteMap.js +9 -0
- package/dist/services/ConfigBuilder/types/DefaultRouteParamsMap.d.ts +5 -0
- package/dist/services/ConfigBuilder/types/DefaultRouteParamsMap.js +3 -0
- package/dist/services/ConfigBuilder/types/OpenApiConstructor.d.ts +2 -0
- package/dist/services/ConfigBuilder/types/OpenApiConstructor.js +1 -0
- package/dist/services/DescriptionChecker/DescriptionChecker.d.ts +16 -0
- package/dist/services/DescriptionChecker/DescriptionChecker.js +47 -0
- package/dist/services/DevelopmentUtils/DevelopmentUtils.d.ts +4 -0
- package/dist/services/DevelopmentUtils/DevelopmentUtils.js +15 -0
- package/dist/services/ExpressWrapper/ExpressWrapper.d.ts +15 -0
- package/dist/services/ExpressWrapper/ExpressWrapper.js +68 -0
- package/dist/services/ExpressWrapper/types/ExpressApp.d.ts +8 -0
- package/dist/services/ExpressWrapper/types/ExpressApp.js +1 -0
- package/dist/services/ExpressWrapper/types/ExpressHandler.d.ts +3 -0
- package/dist/services/ExpressWrapper/types/ExpressHandler.js +1 -0
- package/dist/services/ExpressWrapper/types/ExpressRequest.d.ts +8 -0
- package/dist/services/ExpressWrapper/types/ExpressRequest.js +1 -0
- package/dist/services/ExpressWrapper/types/ExpressResponse.d.ts +6 -0
- package/dist/services/ExpressWrapper/types/ExpressResponse.js +1 -0
- package/dist/services/Logger/Logger.d.ts +15 -0
- package/dist/services/Logger/Logger.js +100 -0
- package/dist/services/Logger/types/LogLevel.d.ts +6 -0
- package/dist/services/Logger/types/LogLevel.js +7 -0
- package/dist/services/RoutingFactory/RoutingFactory.d.ts +10 -0
- package/dist/services/RoutingFactory/RoutingFactory.js +23 -0
- package/dist/services/SchemaGenerator/SchemaGenerator.d.ts +19 -0
- package/dist/services/SchemaGenerator/SchemaGenerator.js +131 -0
- package/dist/services/TanstackStartWrapper/TanstackStartWrapper.d.ts +35 -0
- package/dist/services/TanstackStartWrapper/TanstackStartWrapper.js +67 -0
- package/dist/services/TestUtils/TestUtils.d.ts +13 -0
- package/dist/services/TestUtils/TestUtils.js +48 -0
- package/dist/services/ValidationUtils/ValidationUtils.d.ts +56 -0
- package/dist/services/ValidationUtils/ValidationUtils.js +38 -0
- package/dist/services/ValidationUtils/transformers/stringBooleanTransformer.d.ts +3 -0
- package/dist/services/ValidationUtils/transformers/stringBooleanTransformer.js +5 -0
- package/dist/services/ValidationUtils/transformers/stringDateTransformer.d.ts +3 -0
- package/dist/services/ValidationUtils/transformers/stringDateTransformer.js +16 -0
- package/dist/services/ValidationUtils/transformers/stringNumberTransfromer.d.ts +3 -0
- package/dist/services/ValidationUtils/transformers/stringNumberTransfromer.js +24 -0
- package/dist/services/ValidationUtils/types/PaginatedResponse.d.ts +8 -0
- package/dist/services/ValidationUtils/types/PaginatedResponse.js +1 -0
- package/dist/types/AnyRoute.d.ts +3 -0
- package/dist/types/AnyRoute.js +1 -0
- package/dist/types/InitialBuilder.d.ts +9 -0
- package/dist/types/InitialBuilder.js +1 -0
- package/dist/types/NonEmptyArray.d.ts +1 -0
- package/dist/types/NonEmptyArray.js +1 -0
- package/dist/types/Route.d.ts +22 -0
- package/dist/types/Route.js +1 -0
- package/dist/types/RouteMap.d.ts +3 -0
- package/dist/types/RouteMap.js +1 -0
- package/dist/types/RoutePath.d.ts +1 -0
- package/dist/types/RoutePath.js +1 -0
- package/dist/types/Wrappers.d.ts +7 -0
- package/dist/types/Wrappers.js +1 -0
- package/dist/types/config/AnyConfig.d.ts +4 -0
- package/dist/types/config/AnyConfig.js +1 -0
- package/dist/types/config/AnyRouteConfigMap.d.ts +4 -0
- package/dist/types/config/AnyRouteConfigMap.js +1 -0
- package/dist/types/config/Config.d.ts +19 -0
- package/dist/types/config/Config.js +1 -0
- package/dist/types/config/ContextParams.d.ts +12 -0
- package/dist/types/config/ContextParams.js +1 -0
- package/dist/types/config/ErrorConfig.d.ts +6 -0
- package/dist/types/config/ErrorConfig.js +1 -0
- package/dist/types/config/ErrorConfigMap.d.ts +5 -0
- package/dist/types/config/ErrorConfigMap.js +1 -0
- package/dist/types/config/ErrorResponse.d.ts +8 -0
- package/dist/types/config/ErrorResponse.js +1 -0
- package/dist/types/config/ISpecificationExtension.d.ts +6 -0
- package/dist/types/config/ISpecificationExtension.js +1 -0
- package/dist/types/config/Info.d.ts +7 -0
- package/dist/types/config/Info.js +1 -0
- package/dist/types/config/OmitMappedField.d.ts +3 -0
- package/dist/types/config/OmitMappedField.js +1 -0
- package/dist/types/config/RouteConfig.d.ts +10 -0
- package/dist/types/config/RouteConfig.js +1 -0
- package/dist/types/config/RouteConfigMap.d.ts +7 -0
- package/dist/types/config/RouteConfigMap.js +1 -0
- package/dist/types/config/RouteContextMap.d.ts +6 -0
- package/dist/types/config/RouteContextMap.js +1 -0
- package/dist/types/config/RouteExtraProps.d.ts +2 -0
- package/dist/types/config/RouteExtraProps.js +1 -0
- package/dist/types/config/RouteExtraPropsMap.d.ts +4 -0
- package/dist/types/config/RouteExtraPropsMap.js +1 -0
- package/dist/types/config/Server.d.ts +5 -0
- package/dist/types/config/Server.js +1 -0
- package/dist/types/errors/ApiError.d.ts +5 -0
- package/dist/types/errors/ApiError.js +10 -0
- package/dist/types/errors/BuiltInError.d.ts +4 -0
- package/dist/types/errors/BuiltInError.js +3 -0
- package/dist/types/errors/FieldError.d.ts +33 -0
- package/dist/types/errors/FieldError.js +9 -0
- package/dist/types/errors/ValidationError.d.ts +10 -0
- package/dist/types/errors/ValidationError.js +17 -0
- package/dist/types/errors/responses/NotFoundErrorResponse.d.ts +12 -0
- package/dist/types/errors/responses/NotFoundErrorResponse.js +6 -0
- package/dist/types/errors/responses/UnknownErrorResponse.d.ts +12 -0
- package/dist/types/errors/responses/UnknownErrorResponse.js +6 -0
- package/dist/types/errors/responses/ValidationErrorResponse.d.ts +89 -0
- package/dist/types/errors/responses/ValidationErrorResponse.js +12 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +25 -0
- package/package.json +53 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ExpressApp } from './types/ExpressApp.js';
|
|
2
|
+
import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
|
|
3
|
+
import { OpenApi } from '../../OpenApi.js';
|
|
4
|
+
import { AnyConfig } from '../../types/config/AnyConfig.js';
|
|
5
|
+
import { RoutePath } from '../../types/RoutePath.js';
|
|
6
|
+
export declare class ExpressWrapper<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
|
|
7
|
+
protected service: OpenApi<TRouteTypes, TErrorCodes, TConfig>;
|
|
8
|
+
protected developmentUtils: DevelopmentUtils;
|
|
9
|
+
protected schemaRoute: RoutePath;
|
|
10
|
+
constructor(openApi: OpenApi<TRouteTypes, TErrorCodes, TConfig>);
|
|
11
|
+
createStoplightRoute(route: RoutePath, expressApp: ExpressApp): void;
|
|
12
|
+
createSwaggerRoute(route: RoutePath, expressApp: ExpressApp): void;
|
|
13
|
+
createSchemaRoute(route: RoutePath, expressApp: ExpressApp): void;
|
|
14
|
+
createOpenApiRootRoute(expressApp: ExpressApp): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { format } from 'url';
|
|
2
|
+
import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
|
|
3
|
+
export class ExpressWrapper {
|
|
4
|
+
service;
|
|
5
|
+
developmentUtils;
|
|
6
|
+
schemaRoute = '/openapi-schema';
|
|
7
|
+
constructor(openApi) {
|
|
8
|
+
this.service = openApi;
|
|
9
|
+
this.developmentUtils = new DevelopmentUtils();
|
|
10
|
+
}
|
|
11
|
+
createStoplightRoute(route, expressApp) {
|
|
12
|
+
const handler = async (req, res) => {
|
|
13
|
+
const body = this.developmentUtils.getStoplightHtml(this.schemaRoute);
|
|
14
|
+
res.status(200).header('Content-Type', 'text/html').send(body);
|
|
15
|
+
};
|
|
16
|
+
this.createSchemaRoute(this.schemaRoute, expressApp);
|
|
17
|
+
expressApp.get(route, handler);
|
|
18
|
+
}
|
|
19
|
+
createSwaggerRoute(route, expressApp) {
|
|
20
|
+
const handler = async (req, res) => {
|
|
21
|
+
const body = this.developmentUtils.getSwaggerHTML(this.schemaRoute);
|
|
22
|
+
res.status(200).header('Content-Type', 'text/html').send(body);
|
|
23
|
+
};
|
|
24
|
+
this.createSchemaRoute(this.schemaRoute, expressApp);
|
|
25
|
+
expressApp.get(route, handler);
|
|
26
|
+
}
|
|
27
|
+
createSchemaRoute(route, expressApp) {
|
|
28
|
+
const handler = async (req, res) => {
|
|
29
|
+
const body = this.service.schemaGenerator.getYaml();
|
|
30
|
+
res.status(200).header('Content-Type', 'text/html').send(body);
|
|
31
|
+
};
|
|
32
|
+
expressApp.get(route, handler);
|
|
33
|
+
}
|
|
34
|
+
createOpenApiRootRoute(expressApp) {
|
|
35
|
+
const route = this.service.getBasePath();
|
|
36
|
+
const headerToStr = (header) => {
|
|
37
|
+
if (Array.isArray(header)) {
|
|
38
|
+
return header.join(',');
|
|
39
|
+
}
|
|
40
|
+
return header;
|
|
41
|
+
};
|
|
42
|
+
const handler = async (req, res) => {
|
|
43
|
+
const emptyHeaders = {};
|
|
44
|
+
const headers = Object.entries(req.headers).reduce((acc, val) => ({
|
|
45
|
+
...acc,
|
|
46
|
+
...(typeof val[1] !== 'undefined' ? { [val[0]]: headerToStr(val[1]) } : {}),
|
|
47
|
+
}), emptyHeaders);
|
|
48
|
+
const url = format({
|
|
49
|
+
protocol: req.protocol,
|
|
50
|
+
host: req.host,
|
|
51
|
+
pathname: req.originalUrl,
|
|
52
|
+
});
|
|
53
|
+
const openApiRequest = new Request(url, {
|
|
54
|
+
body: req.body,
|
|
55
|
+
headers: headers,
|
|
56
|
+
method: req.method,
|
|
57
|
+
});
|
|
58
|
+
const result = await this.service.processRootRoute(openApiRequest);
|
|
59
|
+
res.status(result.status).header('Content-Type', 'application/json').json(result.body);
|
|
60
|
+
};
|
|
61
|
+
const regex = new RegExp(`${route}.*`);
|
|
62
|
+
expressApp.get(regex, handler);
|
|
63
|
+
expressApp.post(regex, handler);
|
|
64
|
+
expressApp.patch(regex, handler);
|
|
65
|
+
expressApp.delete(regex, handler);
|
|
66
|
+
expressApp.put(regex, handler);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ExpressHandler } from './ExpressHandler.js';
|
|
2
|
+
export interface ExpressApp {
|
|
3
|
+
get: (route: string | RegExp, handler: ExpressHandler) => void;
|
|
4
|
+
post: (route: string | RegExp, handler: ExpressHandler) => void;
|
|
5
|
+
delete: (route: string | RegExp, handler: ExpressHandler) => void;
|
|
6
|
+
put: (route: string | RegExp, handler: ExpressHandler) => void;
|
|
7
|
+
patch: (route: string | RegExp, handler: ExpressHandler) => void;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LogLevel } from './types/LogLevel.js';
|
|
2
|
+
export declare class Logger {
|
|
3
|
+
protected invoker: string;
|
|
4
|
+
static logLevel: LogLevel;
|
|
5
|
+
static showTime: boolean;
|
|
6
|
+
constructor(invoker: string, originalInvoker?: string);
|
|
7
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
8
|
+
debug(message: string, data?: object): void;
|
|
9
|
+
error(message: string | null, error: unknown, data?: object): void;
|
|
10
|
+
getInvoker(): string;
|
|
11
|
+
static setLogLevel(level: LogLevel): void;
|
|
12
|
+
protected log(message: string, level: string, data?: object): void;
|
|
13
|
+
protected transformData(data: object): unknown;
|
|
14
|
+
protected removeCircularity(data: object): unknown;
|
|
15
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { LogLevel } from './types/LogLevel.js';
|
|
2
|
+
export class Logger {
|
|
3
|
+
invoker;
|
|
4
|
+
static logLevel = LogLevel.all;
|
|
5
|
+
static showTime = true;
|
|
6
|
+
constructor(invoker, originalInvoker) {
|
|
7
|
+
this.invoker = (originalInvoker ? `${originalInvoker}:` : '') + invoker;
|
|
8
|
+
}
|
|
9
|
+
info(message, data) {
|
|
10
|
+
if (Logger.logLevel === LogLevel.error) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
this.log(message, 'info', data);
|
|
14
|
+
}
|
|
15
|
+
debug(message, data) {
|
|
16
|
+
if (Logger.logLevel !== LogLevel.all) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.log(message, 'debug', data);
|
|
20
|
+
}
|
|
21
|
+
error(message, error, data) {
|
|
22
|
+
let newMessage = 'UnkownError: ';
|
|
23
|
+
if (!message) {
|
|
24
|
+
if (error instanceof Error) {
|
|
25
|
+
newMessage = error.message;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
newMessage = message;
|
|
30
|
+
}
|
|
31
|
+
this.log(newMessage, 'error', data);
|
|
32
|
+
console.log(error);
|
|
33
|
+
}
|
|
34
|
+
getInvoker() {
|
|
35
|
+
return this.invoker;
|
|
36
|
+
}
|
|
37
|
+
static setLogLevel(level) {
|
|
38
|
+
this.logLevel = level;
|
|
39
|
+
}
|
|
40
|
+
log(message, level, data) {
|
|
41
|
+
const now = new Date();
|
|
42
|
+
const timePart = Logger.showTime ? now.toISOString() : '';
|
|
43
|
+
const msg = `${timePart}[${level}][${this.invoker}]: ${message}`;
|
|
44
|
+
console.log(msg);
|
|
45
|
+
if (data) {
|
|
46
|
+
console.dir(this.transformData(data), { depth: null });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
transformData(data) {
|
|
50
|
+
const plain = this.removeCircularity(data);
|
|
51
|
+
return plain;
|
|
52
|
+
}
|
|
53
|
+
removeCircularity(data) {
|
|
54
|
+
if (typeof data !== 'object') {
|
|
55
|
+
return data;
|
|
56
|
+
}
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const recurse = (obj, path = []) => {
|
|
59
|
+
if (Array.isArray(obj)) {
|
|
60
|
+
const result = [];
|
|
61
|
+
let index = -1;
|
|
62
|
+
for (const val of obj) {
|
|
63
|
+
index++;
|
|
64
|
+
if (typeof val === 'object' && val) {
|
|
65
|
+
if (seen.has(val)) {
|
|
66
|
+
result.push('circular->' + path.join('.'));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
seen.add(val);
|
|
70
|
+
const newPath = [...path, index.toString()];
|
|
71
|
+
result.push(recurse(val, newPath));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
result.push(val);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
if (obj instanceof Date) {
|
|
79
|
+
return obj;
|
|
80
|
+
}
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const key of Object.keys(obj)) {
|
|
83
|
+
if (typeof obj[key] === 'object' && obj[key]) {
|
|
84
|
+
if (seen.has(obj[key])) {
|
|
85
|
+
result[key] = 'circular->' + path.join('.');
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
seen.add(obj[key]);
|
|
89
|
+
const newPath = [...path, key];
|
|
90
|
+
result[key] = recurse(obj[key], newPath);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
result[key] = obj[key];
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
const result = recurse(data);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ZodFirstPartySchemaTypes, ZodObject, ZodRawShape } from 'zod';
|
|
2
|
+
import { Method } from '../../enums/Methods.js';
|
|
3
|
+
import { Route } from '../../types/Route.js';
|
|
4
|
+
import { AnyConfig } from '../../types/config/AnyConfig.js';
|
|
5
|
+
import { RouteExtraProps } from '../../types/config/RouteExtraProps.js';
|
|
6
|
+
export declare class RoutingFactory<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
|
|
7
|
+
protected map: TConfig;
|
|
8
|
+
constructor(map: TConfig);
|
|
9
|
+
createRoute<TType extends TRouteTypes, TMethod extends Method, TResponseValidator extends ZodFirstPartySchemaTypes, TQueryValidator extends ZodObject<ZodRawShape> | undefined = undefined, TPathValidator extends ZodObject<ZodRawShape> | undefined = undefined, TBodyValidator extends ZodObject<ZodRawShape> | undefined = undefined>(params: Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TMethod> & (RouteExtraProps<TConfig['routes'][TType]['extraProps']>)): Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class RoutingFactory {
|
|
2
|
+
map;
|
|
3
|
+
constructor(map) {
|
|
4
|
+
this.map = map;
|
|
5
|
+
}
|
|
6
|
+
createRoute(params) {
|
|
7
|
+
const result = {
|
|
8
|
+
...params,
|
|
9
|
+
method: params.method,
|
|
10
|
+
type: params.type,
|
|
11
|
+
path: params.path,
|
|
12
|
+
description: params.description,
|
|
13
|
+
validators: {
|
|
14
|
+
query: params.validators.query,
|
|
15
|
+
path: params.validators.path,
|
|
16
|
+
response: params.validators.response,
|
|
17
|
+
body: params.validators.body,
|
|
18
|
+
},
|
|
19
|
+
handler: params.handler,
|
|
20
|
+
};
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import 'zod-openapi/extend';
|
|
2
|
+
import { createDocument, ZodOpenApiOperationObject } from 'zod-openapi';
|
|
3
|
+
import { AnyRoute } from '../../types/AnyRoute.js';
|
|
4
|
+
import { AnyConfig } from '../../types/config/AnyConfig.js';
|
|
5
|
+
import { Logger } from '../Logger/Logger.js';
|
|
6
|
+
import { Server } from '../../types/config/Server.js';
|
|
7
|
+
import { Info } from '../../types/config/Info.js';
|
|
8
|
+
export declare class SchemaGenerator<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
|
|
9
|
+
protected logger: Logger;
|
|
10
|
+
protected info: Info;
|
|
11
|
+
protected routes: AnyRoute<TRouteTypes>[];
|
|
12
|
+
protected servers: Server[];
|
|
13
|
+
protected routeSpec: TConfig;
|
|
14
|
+
constructor(invoker: string, info: Info, spec: TConfig, routes: AnyRoute<TRouteTypes>[], servers: Server[]);
|
|
15
|
+
getYaml(): string;
|
|
16
|
+
saveYaml(path: string): void;
|
|
17
|
+
protected createDocument(): ReturnType<typeof createDocument>;
|
|
18
|
+
protected createOperation(route: AnyRoute<TRouteTypes>): ZodOpenApiOperationObject;
|
|
19
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import 'zod-openapi/extend';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import { stringify } from 'yaml';
|
|
4
|
+
import { createDocument } from 'zod-openapi';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { Method } from '../../enums/Methods.js';
|
|
7
|
+
import { Logger } from '../Logger/Logger.js';
|
|
8
|
+
export class SchemaGenerator {
|
|
9
|
+
logger;
|
|
10
|
+
info;
|
|
11
|
+
routes;
|
|
12
|
+
servers;
|
|
13
|
+
routeSpec;
|
|
14
|
+
constructor(invoker, info, spec, routes, servers) {
|
|
15
|
+
this.logger = new Logger(SchemaGenerator.name, invoker);
|
|
16
|
+
this.routes = routes;
|
|
17
|
+
this.routeSpec = spec;
|
|
18
|
+
this.servers = servers;
|
|
19
|
+
this.info = info;
|
|
20
|
+
}
|
|
21
|
+
getYaml() {
|
|
22
|
+
const document = this.createDocument();
|
|
23
|
+
const yaml = stringify(document, { aliasDuplicateObjects: false });
|
|
24
|
+
return yaml;
|
|
25
|
+
}
|
|
26
|
+
saveYaml(path) {
|
|
27
|
+
this.logger.info('Generating YAML for Open API');
|
|
28
|
+
const yaml = this.getYaml();
|
|
29
|
+
writeFileSync(path, yaml);
|
|
30
|
+
}
|
|
31
|
+
createDocument() {
|
|
32
|
+
const openApi = {
|
|
33
|
+
openapi: '3.1.0',
|
|
34
|
+
info: this.info,
|
|
35
|
+
components: {
|
|
36
|
+
securitySchemes: {
|
|
37
|
+
bearerHttpAuthentication: {
|
|
38
|
+
type: 'apiKey',
|
|
39
|
+
scheme: 'bearer',
|
|
40
|
+
bearerFormat: 'jwt',
|
|
41
|
+
name: 'authorization',
|
|
42
|
+
in: 'header',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
paths: {},
|
|
47
|
+
servers: this.servers,
|
|
48
|
+
};
|
|
49
|
+
const paths = {};
|
|
50
|
+
for (const route of this.routes) {
|
|
51
|
+
const operation = this.createOperation(route);
|
|
52
|
+
const existingPath = paths[route.path] ?? {};
|
|
53
|
+
paths[route.path] = {
|
|
54
|
+
...existingPath,
|
|
55
|
+
[route.method.toLowerCase()]: operation,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
openApi.paths = paths;
|
|
59
|
+
const document = createDocument(openApi, {
|
|
60
|
+
unionOneOf: true,
|
|
61
|
+
defaultDateSchema: {
|
|
62
|
+
type: 'string', format: 'date-time',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return document;
|
|
66
|
+
}
|
|
67
|
+
createOperation(route) {
|
|
68
|
+
const requestParams = {
|
|
69
|
+
query: route.validators.query,
|
|
70
|
+
path: route.validators.path,
|
|
71
|
+
};
|
|
72
|
+
const operation = {
|
|
73
|
+
requestParams: requestParams,
|
|
74
|
+
description: route.description,
|
|
75
|
+
responses: {
|
|
76
|
+
200: {
|
|
77
|
+
description: 'Good Response',
|
|
78
|
+
content: {
|
|
79
|
+
'application/json': { schema: route.validators.response },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const enabledErrors = Object.keys(this.routeSpec.routes[route.type].errors ?? {});
|
|
85
|
+
if (!enabledErrors.find((x) => x === this.routeSpec.defaultError.code)) {
|
|
86
|
+
enabledErrors.push(this.routeSpec.defaultError.code);
|
|
87
|
+
}
|
|
88
|
+
const httpStatusMap = new Map();
|
|
89
|
+
for (const [key, error] of Object.entries(this.routeSpec.errors)) {
|
|
90
|
+
const errorConfig = error;
|
|
91
|
+
if (!enabledErrors.includes(key)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const errors = httpStatusMap.get(errorConfig.status) ?? [];
|
|
95
|
+
errors.push(errorConfig);
|
|
96
|
+
httpStatusMap.set(errorConfig.status, errors);
|
|
97
|
+
}
|
|
98
|
+
for (const [code, errors] of httpStatusMap.entries()) {
|
|
99
|
+
const error = errors[0];
|
|
100
|
+
if (!error) {
|
|
101
|
+
throw new Error(`No errors found for code '${code}'`);
|
|
102
|
+
}
|
|
103
|
+
const description = errors.map((x) => x.description).join(' or ');
|
|
104
|
+
const validators = errors.map((x) => x.responseValidator);
|
|
105
|
+
const schema = errors.length === 1 ? error.responseValidator : z.union(validators).openapi({ unionOneOf: true });
|
|
106
|
+
operation.responses[error.status] = {
|
|
107
|
+
description: description,
|
|
108
|
+
content: {
|
|
109
|
+
'application/json': {
|
|
110
|
+
schema: schema,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (this.routeSpec.routes[route.type].authorization) {
|
|
116
|
+
operation.security = [
|
|
117
|
+
{
|
|
118
|
+
bearerHttpAuthentication: [],
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
if (route.method !== Method.GET) {
|
|
123
|
+
operation.requestBody = {
|
|
124
|
+
content: {
|
|
125
|
+
'application/json': { schema: route.validators.body },
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return operation;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { OpenApi } from '../../OpenApi.js';
|
|
2
|
+
import { AnyConfig } from '../../types/config/AnyConfig.js';
|
|
3
|
+
import { RoutePath } from '../../types/RoutePath.js';
|
|
4
|
+
import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
|
|
5
|
+
export declare class TanstackStartWrapper<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
|
|
6
|
+
protected service: OpenApi<TRouteTypes, TErrorCodes, TConfig>;
|
|
7
|
+
protected developmentUtils: DevelopmentUtils;
|
|
8
|
+
constructor(openApi: OpenApi<TRouteTypes, TErrorCodes, TConfig>);
|
|
9
|
+
createShemaMethods(): {
|
|
10
|
+
GET: () => Promise<Response>;
|
|
11
|
+
};
|
|
12
|
+
createStoplightMethods(schemaRoutePath: RoutePath): {
|
|
13
|
+
GET: () => Promise<Response>;
|
|
14
|
+
};
|
|
15
|
+
createSwaggerMethods(schemaRoutePath: RoutePath): {
|
|
16
|
+
GET: () => Promise<Response>;
|
|
17
|
+
};
|
|
18
|
+
getOpenApiRootMethods(): {
|
|
19
|
+
GET: (ctx: {
|
|
20
|
+
request: Request;
|
|
21
|
+
}) => Promise<Response>;
|
|
22
|
+
POST: (ctx: {
|
|
23
|
+
request: Request;
|
|
24
|
+
}) => Promise<Response>;
|
|
25
|
+
PATCH: (ctx: {
|
|
26
|
+
request: Request;
|
|
27
|
+
}) => Promise<Response>;
|
|
28
|
+
PUT: (ctx: {
|
|
29
|
+
request: Request;
|
|
30
|
+
}) => Promise<Response>;
|
|
31
|
+
DELETE: (ctx: {
|
|
32
|
+
request: Request;
|
|
33
|
+
}) => Promise<Response>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
|
|
2
|
+
export class TanstackStartWrapper {
|
|
3
|
+
service;
|
|
4
|
+
developmentUtils;
|
|
5
|
+
constructor(openApi) {
|
|
6
|
+
this.service = openApi;
|
|
7
|
+
this.developmentUtils = new DevelopmentUtils();
|
|
8
|
+
}
|
|
9
|
+
createShemaMethods() {
|
|
10
|
+
const processor = async () => {
|
|
11
|
+
const body = this.service.schemaGenerator.getYaml();
|
|
12
|
+
const res = new Response(body, {
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'text/html',
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
return res;
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
GET: processor,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
createStoplightMethods(schemaRoutePath) {
|
|
24
|
+
const processor = async () => {
|
|
25
|
+
const body = this.developmentUtils.getStoplightHtml(schemaRoutePath);
|
|
26
|
+
const res = new Response(body, {
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'text/html',
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
return res;
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
GET: processor,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
createSwaggerMethods(schemaRoutePath) {
|
|
38
|
+
const processor = async () => {
|
|
39
|
+
const body = this.developmentUtils.getSwaggerHTML(schemaRoutePath);
|
|
40
|
+
const res = new Response(body, {
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'text/html',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
return res;
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
GET: processor,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
getOpenApiRootMethods() {
|
|
52
|
+
const processor = async (ctx) => {
|
|
53
|
+
const response = await this.service.processRootRoute(ctx.request);
|
|
54
|
+
const res = new Response(JSON.stringify(response.body), {
|
|
55
|
+
status: response.status,
|
|
56
|
+
});
|
|
57
|
+
return res;
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
GET: processor,
|
|
61
|
+
POST: processor,
|
|
62
|
+
PATCH: processor,
|
|
63
|
+
PUT: processor,
|
|
64
|
+
DELETE: processor,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OpenApi } from '../../OpenApi.js';
|
|
2
|
+
import { Method } from '../../enums/Methods.js';
|
|
3
|
+
import { SampleRouteType } from '../../enums/SampleRouteType.js';
|
|
4
|
+
import { RoutePath } from '../../types/RoutePath.js';
|
|
5
|
+
export declare class TestUtils {
|
|
6
|
+
static createRequest(route: RoutePath, method?: Method, body?: object): Request;
|
|
7
|
+
static sendRequest(api: OpenApi<any, any, any>, route: RoutePath, method?: Method, body?: object): Promise<{
|
|
8
|
+
status: number;
|
|
9
|
+
body: any;
|
|
10
|
+
}>;
|
|
11
|
+
static createOpenApi(): OpenApi<SampleRouteType, import("../../index.js").OpenApiErrorCode, import("../../index.js").OpenApiDefaultConfig>;
|
|
12
|
+
static awaitGeneric<T>(timeoutMs: number, intervalMs: number, callback: () => Promise<T | null>): Promise<T | null>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { OpenApi } from '../../OpenApi.js';
|
|
3
|
+
import { Method } from '../../enums/Methods.js';
|
|
4
|
+
import { SampleRouteType } from '../../enums/SampleRouteType.js';
|
|
5
|
+
export class TestUtils {
|
|
6
|
+
static createRequest(route, method = Method.GET, body) {
|
|
7
|
+
const request = new Request(`http://localhost${route}`, {
|
|
8
|
+
method: method,
|
|
9
|
+
body: JSON.stringify(body),
|
|
10
|
+
});
|
|
11
|
+
return request;
|
|
12
|
+
}
|
|
13
|
+
static sendRequest(api, route, method = Method.GET, body) {
|
|
14
|
+
const req = this.createRequest(route, method, body);
|
|
15
|
+
return api.processRootRoute(req);
|
|
16
|
+
}
|
|
17
|
+
static createOpenApi() {
|
|
18
|
+
const api = OpenApi.builder.create();
|
|
19
|
+
const sampleRoute = api.factory.createRoute({
|
|
20
|
+
type: SampleRouteType.Public,
|
|
21
|
+
method: Method.GET,
|
|
22
|
+
path: '/sample',
|
|
23
|
+
description: 'Sample route',
|
|
24
|
+
validators: {
|
|
25
|
+
response: z.string().openapi({ description: 'Sample response' }),
|
|
26
|
+
},
|
|
27
|
+
handler: () => Promise.resolve('success'),
|
|
28
|
+
});
|
|
29
|
+
api.addRoute(sampleRoute);
|
|
30
|
+
return api;
|
|
31
|
+
}
|
|
32
|
+
static async awaitGeneric(timeoutMs, intervalMs, callback) {
|
|
33
|
+
let now = new Date().getTime();
|
|
34
|
+
const deadline = now + timeoutMs;
|
|
35
|
+
let counter = 1;
|
|
36
|
+
do {
|
|
37
|
+
const left = (deadline - now) / 1000;
|
|
38
|
+
console.log(`Iteration ${counter++}: ${left} sec. left`);
|
|
39
|
+
const result = await callback();
|
|
40
|
+
if (result !== null) {
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
44
|
+
now = new Date().getTime();
|
|
45
|
+
} while (now < deadline);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|