snap-on-openapi 1.0.12 → 1.0.14
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/dist/OpenApi.d.ts +2 -2
- package/dist/OpenApi.js +65 -53
- package/dist/services/ConfigBuilder/types/DefaultConfig.d.ts +8 -1
- package/dist/services/ConfigBuilder/types/DefaultConfig.js +20 -6
- package/dist/services/TestUtils/TestUtils.d.ts +2 -0
- package/dist/services/TestUtils/TestUtils.js +4 -0
- package/dist/services/TestUtils/utils/TestLogger.d.ts +15 -0
- package/dist/services/TestUtils/utils/TestLogger.js +13 -0
- package/dist/types/config/Config.d.ts +10 -4
- package/dist/types/config/ContextParams.d.ts +2 -0
- package/dist/types/events/OnErrorEvent.d.ts +7 -0
- package/dist/types/events/OnErrorEvent.js +1 -0
- package/dist/types/events/OnHandlerEvent.d.ts +8 -0
- package/dist/types/events/OnHandlerEvent.js +1 -0
- package/dist/types/events/OnRequestEvent.d.ts +5 -0
- package/dist/types/events/OnRequestEvent.js +1 -0
- package/dist/types/events/OnResponseEvent.d.ts +8 -0
- package/dist/types/events/OnResponseEvent.js +1 -0
- package/dist/types/events/OnRouteEvent.d.ts +10 -0
- package/dist/types/events/OnRouteEvent.js +1 -0
- package/package.json +1 -1
package/dist/OpenApi.d.ts
CHANGED
|
@@ -51,10 +51,10 @@ export declare class OpenApi<TRouteTypes extends string, TErrorCodes extends str
|
|
|
51
51
|
body: unknown;
|
|
52
52
|
headers: Record<string, string>;
|
|
53
53
|
}>;
|
|
54
|
-
protected handleError(e: unknown, req: Request): {
|
|
54
|
+
protected handleError(e: unknown, req: Request): Promise<{
|
|
55
55
|
status: number;
|
|
56
56
|
body: z.TypeOf<import("./index.js").OpenApiErrorConfigMap<TErrorCodes>[TErrorCodes]["responseValidator"]>;
|
|
57
57
|
headers: {};
|
|
58
|
-
}
|
|
58
|
+
}>;
|
|
59
59
|
protected static getBuilder(): ConfigBuilder<SampleRouteType, ErrorCode, DefaultErrorMap, DefaultRouteParamsMap, DefaultRouteContextMap, DefaultRouteMap, DefaultConfig>;
|
|
60
60
|
}
|
package/dist/OpenApi.js
CHANGED
|
@@ -129,6 +129,9 @@ export class OpenApi {
|
|
|
129
129
|
}
|
|
130
130
|
async processRootRoute(originalReq) {
|
|
131
131
|
try {
|
|
132
|
+
if (this.config.onRequest) {
|
|
133
|
+
await this.config.onRequest({ request: originalReq, logger: this.logger });
|
|
134
|
+
}
|
|
132
135
|
const url = new URL(originalReq.url);
|
|
133
136
|
const basePath = this.getBasePath() === '/' ? '' : this.getBasePath();
|
|
134
137
|
const urlPath = url.pathname.replace(basePath, '');
|
|
@@ -176,12 +179,19 @@ export class OpenApi {
|
|
|
176
179
|
query: reqQuery,
|
|
177
180
|
body: body,
|
|
178
181
|
};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
const onRoute = {
|
|
183
|
+
request: originalReq,
|
|
184
|
+
logger: this.logger,
|
|
185
|
+
path: urlPath,
|
|
186
|
+
method: originalReq.method,
|
|
187
|
+
params: pathParams,
|
|
188
|
+
query: reqQuery,
|
|
189
|
+
body: body,
|
|
190
|
+
route: route,
|
|
191
|
+
};
|
|
192
|
+
if (this.config.onRoute) {
|
|
193
|
+
await this.config.onRoute(onRoute);
|
|
194
|
+
}
|
|
185
195
|
const queryValidator = route.validators.query?.strict() ?? z.object({});
|
|
186
196
|
const query = queryValidator.safeParse(req.query);
|
|
187
197
|
if (!query.success) {
|
|
@@ -192,80 +202,82 @@ export class OpenApi {
|
|
|
192
202
|
if (!path.success) {
|
|
193
203
|
throw new ValidationError(path.error, ValidationLocation.Path, req.params);
|
|
194
204
|
}
|
|
195
|
-
let response;
|
|
196
205
|
const containsBody = route.method !== Method.GET;
|
|
206
|
+
let bodyData = {};
|
|
197
207
|
if (containsBody && route.validators.body) {
|
|
198
|
-
const
|
|
199
|
-
if (!
|
|
200
|
-
throw new ValidationError(
|
|
208
|
+
const bodyResult = route.validators.body.safeParse(req.body);
|
|
209
|
+
if (!bodyResult.success) {
|
|
210
|
+
throw new ValidationError(bodyResult.error, ValidationLocation.Body, req.body);
|
|
201
211
|
}
|
|
202
|
-
|
|
203
|
-
route: route,
|
|
204
|
-
request: originalReq,
|
|
205
|
-
params: {
|
|
206
|
-
query: query.data,
|
|
207
|
-
path: path.data,
|
|
208
|
-
body: body.data,
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
response = await route.handler({
|
|
212
|
-
...context,
|
|
213
|
-
params: {
|
|
214
|
-
query: query.data,
|
|
215
|
-
path: path.data,
|
|
216
|
-
body: body.data,
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
const context = await this.config.routes[route.type].contextFactory({
|
|
222
|
-
route: route,
|
|
223
|
-
request: originalReq,
|
|
224
|
-
params: {
|
|
225
|
-
query: query.data,
|
|
226
|
-
path: path.data,
|
|
227
|
-
body: {},
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
response = await route.handler({
|
|
231
|
-
...context,
|
|
232
|
-
params: {
|
|
233
|
-
query: query.data,
|
|
234
|
-
path: path.data,
|
|
235
|
-
body: {},
|
|
236
|
-
}
|
|
237
|
-
});
|
|
212
|
+
bodyData = bodyResult.data;
|
|
238
213
|
}
|
|
214
|
+
const context = await this.config.routes[route.type].contextFactory({
|
|
215
|
+
route: route,
|
|
216
|
+
request: originalReq,
|
|
217
|
+
logger: this.logger,
|
|
218
|
+
params: {
|
|
219
|
+
query: query.data,
|
|
220
|
+
path: path.data,
|
|
221
|
+
body: bodyData,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
const onHandler = {
|
|
225
|
+
...onRoute,
|
|
226
|
+
validated: {
|
|
227
|
+
query: query.data,
|
|
228
|
+
path: path.data,
|
|
229
|
+
body: bodyData,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
const response = await route.handler({
|
|
233
|
+
...context,
|
|
234
|
+
params: {
|
|
235
|
+
query: query.data,
|
|
236
|
+
path: path.data,
|
|
237
|
+
body: bodyData,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
239
240
|
const finalResponse = route.validators.responseHeaders ? response : { body: response, headers: {} };
|
|
240
241
|
const finalResponseValidator = z.object({
|
|
241
242
|
body: route.validators.response ?? z.undefined(),
|
|
242
243
|
headers: route.validators.responseHeaders?.strict() ?? z.object({}),
|
|
243
244
|
});
|
|
245
|
+
const onResponse = {
|
|
246
|
+
...onHandler,
|
|
247
|
+
response: { status: 200, body: finalResponse.body, headers: finalResponse.headers },
|
|
248
|
+
};
|
|
244
249
|
if (this.config.disableResponseValidation) {
|
|
245
|
-
this.
|
|
250
|
+
if (this.config.onResponse) {
|
|
251
|
+
await this.config.onResponse(onResponse);
|
|
252
|
+
}
|
|
246
253
|
return { status: 200, body: finalResponse.body, headers: finalResponse.headers };
|
|
247
254
|
}
|
|
248
255
|
const validated = finalResponseValidator.safeParse(finalResponse);
|
|
249
256
|
if (!validated.success) {
|
|
250
257
|
throw new ValidationError(validated.error, ValidationLocation.Response, finalResponse);
|
|
251
258
|
}
|
|
252
|
-
this.
|
|
259
|
+
if (this.config.onResponse) {
|
|
260
|
+
await this.config.onResponse(onResponse);
|
|
261
|
+
}
|
|
253
262
|
return { status: 200, body: validated.data.body, headers: validated.data.headers };
|
|
254
263
|
}
|
|
255
264
|
catch (e) {
|
|
256
|
-
return this.handleError(e, originalReq);
|
|
265
|
+
return await this.handleError(e, originalReq);
|
|
257
266
|
}
|
|
258
267
|
}
|
|
259
|
-
handleError(e, req) {
|
|
260
|
-
this.logger.error('Error during request openAPI route handling', e);
|
|
268
|
+
async handleError(e, req) {
|
|
261
269
|
try {
|
|
262
|
-
const
|
|
270
|
+
const event = {
|
|
271
|
+
request: req,
|
|
272
|
+
logger: this.logger,
|
|
273
|
+
error: e,
|
|
274
|
+
};
|
|
275
|
+
const response = this.config.onError ? await this.config.onError(event) : this.config.defaultError;
|
|
263
276
|
const status = this.config.errors[response.code].status;
|
|
264
277
|
const valid = this.config.errors[response.code].responseValidator.safeParse(response.body);
|
|
265
278
|
if (!valid.success) {
|
|
266
279
|
throw new Error("Error response haven't passed validation");
|
|
267
280
|
}
|
|
268
|
-
this.logger.info(`Response: '${status}'`, response.body);
|
|
269
281
|
return { status: Number(status), body: response.body, headers: {} };
|
|
270
282
|
}
|
|
271
283
|
catch (e) {
|
|
@@ -2,6 +2,9 @@ import { ErrorCode } from '../../../enums/ErrorCode.js';
|
|
|
2
2
|
import { SampleRouteType } from '../../../enums/SampleRouteType.js';
|
|
3
3
|
import { Config } from '../../../types/config/Config.js';
|
|
4
4
|
import { ErrorResponse } from '../../../types/config/ErrorResponse.js';
|
|
5
|
+
import { OnErrorEvent } from '../../../types/events/OnErrorEvent.js';
|
|
6
|
+
import { OnResponseEvent } from '../../../types/events/OnResponseEvent.js';
|
|
7
|
+
import { OnRouteEvent } from '../../../types/events/OnRouteEvent.js';
|
|
5
8
|
import { RoutePath } from '../../../types/RoutePath.js';
|
|
6
9
|
import { DefaultErrorMap } from './DefaultErrorMap.js';
|
|
7
10
|
import { DefaultRouteContextMap } from './DefaultRouteContextMap.js';
|
|
@@ -17,6 +20,10 @@ export declare class DefaultConfig implements Config<SampleRouteType, ErrorCode,
|
|
|
17
20
|
readonly error: ErrorCode.UnknownError;
|
|
18
21
|
};
|
|
19
22
|
};
|
|
20
|
-
|
|
23
|
+
onRequest?: () => Promise<void>;
|
|
24
|
+
onRoute?: (e: OnRouteEvent) => Promise<void>;
|
|
25
|
+
onHandler?: () => Promise<void>;
|
|
26
|
+
onResponse?: (e: OnResponseEvent) => Promise<void>;
|
|
27
|
+
onError?: (e: OnErrorEvent) => Promise<ErrorResponse<ErrorCode, DefaultErrorMap>>;
|
|
21
28
|
skipDescriptionsCheck?: boolean;
|
|
22
29
|
}
|
|
@@ -14,9 +14,23 @@ export class DefaultConfig {
|
|
|
14
14
|
error: ErrorCode.UnknownError,
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
onRequest = () => Promise.resolve();
|
|
18
|
+
onRoute = async (e) => {
|
|
19
|
+
e.logger.info(`Calling route ${e.route.path}`);
|
|
20
|
+
e.logger.info(`${e.method}: ${e.request.url}`, {
|
|
21
|
+
path: e.path,
|
|
22
|
+
query: e.query,
|
|
23
|
+
body: e.body,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
onHandler = () => Promise.resolve();
|
|
27
|
+
onResponse = async (e) => {
|
|
28
|
+
e.logger.info(`Response: ${e.response.status}`, { body: e.response.body, headers: e.response.headers });
|
|
29
|
+
};
|
|
30
|
+
onError = async (e) => {
|
|
31
|
+
e.logger.error('Error during request openAPI route handling', { url: e.request.url, error: e.error });
|
|
32
|
+
if (e.error instanceof ValidationError) {
|
|
33
|
+
const zodError = e.error.getZodError();
|
|
20
34
|
const map = [];
|
|
21
35
|
for (const issue of zodError.issues) {
|
|
22
36
|
map.push({
|
|
@@ -24,18 +38,18 @@ export class DefaultConfig {
|
|
|
24
38
|
message: issue.message,
|
|
25
39
|
});
|
|
26
40
|
}
|
|
27
|
-
if (e.getLocation() !== ValidationLocation.Response) {
|
|
41
|
+
if (e.error.getLocation() !== ValidationLocation.Response) {
|
|
28
42
|
const response = {
|
|
29
43
|
error: {
|
|
30
44
|
code: ErrorCode.ValidationFailed,
|
|
31
|
-
location: e.getLocation(),
|
|
45
|
+
location: e.error.getLocation(),
|
|
32
46
|
fieldErrors: map,
|
|
33
47
|
},
|
|
34
48
|
};
|
|
35
49
|
return { code: ErrorCode.ValidationFailed, body: response };
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
|
-
if (e instanceof BuiltInError && e.getCode() === ErrorCode.NotFound) {
|
|
52
|
+
if (e.error instanceof BuiltInError && e.error.getCode() === ErrorCode.NotFound) {
|
|
39
53
|
return { code: ErrorCode.NotFound, body: { error: ErrorCode.NotFound } };
|
|
40
54
|
}
|
|
41
55
|
const unknownError = {
|
|
@@ -2,6 +2,7 @@ import { OpenApi } from '../../OpenApi.js';
|
|
|
2
2
|
import { Method } from '../../enums/Methods.js';
|
|
3
3
|
import { SampleRouteType } from '../../enums/SampleRouteType.js';
|
|
4
4
|
import { RoutePath } from '../../types/RoutePath.js';
|
|
5
|
+
import { TestLogger } from './utils/TestLogger.js';
|
|
5
6
|
export declare class TestUtils {
|
|
6
7
|
static createRequest(route: RoutePath, method?: Method, body?: object): Request;
|
|
7
8
|
static sendRequest(api: OpenApi<any, any, any>, route: RoutePath, method?: Method, body?: object): Promise<{
|
|
@@ -10,4 +11,5 @@ export declare class TestUtils {
|
|
|
10
11
|
}>;
|
|
11
12
|
static createOpenApi(): OpenApi<SampleRouteType, import("../../index.js").OpenApiErrorCode, import("../../index.js").OpenApiDefaultConfig>;
|
|
12
13
|
static awaitGeneric<T>(timeoutMs: number, intervalMs: number, callback: () => Promise<T | null>): Promise<T | null>;
|
|
14
|
+
static getTestLogger(): TestLogger;
|
|
13
15
|
}
|
|
@@ -2,6 +2,7 @@ import z from 'zod';
|
|
|
2
2
|
import { OpenApi } from '../../OpenApi.js';
|
|
3
3
|
import { Method } from '../../enums/Methods.js';
|
|
4
4
|
import { SampleRouteType } from '../../enums/SampleRouteType.js';
|
|
5
|
+
import { TestLogger } from './utils/TestLogger.js';
|
|
5
6
|
export class TestUtils {
|
|
6
7
|
static createRequest(route, method = Method.GET, body) {
|
|
7
8
|
const request = new Request(`http://localhost${route}`, {
|
|
@@ -45,4 +46,7 @@ export class TestUtils {
|
|
|
45
46
|
} while (now < deadline);
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
49
|
+
static getTestLogger() {
|
|
50
|
+
return new TestLogger('TestUtils');
|
|
51
|
+
}
|
|
48
52
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Logger } from '../../Logger/Logger.js';
|
|
2
|
+
export declare class TestLogger extends Logger {
|
|
3
|
+
private messages;
|
|
4
|
+
popMessage(): {
|
|
5
|
+
message: string;
|
|
6
|
+
level: string;
|
|
7
|
+
data?: object;
|
|
8
|
+
} | undefined;
|
|
9
|
+
shiftMessage(): {
|
|
10
|
+
message: string;
|
|
11
|
+
level: string;
|
|
12
|
+
data?: object;
|
|
13
|
+
} | undefined;
|
|
14
|
+
protected log(message: string, level: string, data?: object): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Logger } from '../../Logger/Logger.js';
|
|
2
|
+
export class TestLogger extends Logger {
|
|
3
|
+
messages = [];
|
|
4
|
+
popMessage() {
|
|
5
|
+
return this.messages.pop();
|
|
6
|
+
}
|
|
7
|
+
shiftMessage() {
|
|
8
|
+
return this.messages.shift();
|
|
9
|
+
}
|
|
10
|
+
log(message, level, data) {
|
|
11
|
+
this.messages.push({ message, level, data });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -9,6 +9,11 @@ import { RouteConfigMap } from './RouteConfigMap.js';
|
|
|
9
9
|
import { RouteContextMap } from './RouteContextMap.js';
|
|
10
10
|
import { RouteExtraPropsMap } from './RouteExtraPropsMap.js';
|
|
11
11
|
import { Server } from './Server.js';
|
|
12
|
+
import { OnErrorEvent } from '../events/OnErrorEvent.js';
|
|
13
|
+
import { OnHandlerEvent } from '../events/OnHandlerEvent.js';
|
|
14
|
+
import { OnRequestEvent } from '../events/OnRequestEvent.js';
|
|
15
|
+
import { OnResponseEvent } from '../events/OnResponseEvent.js';
|
|
16
|
+
import { OnRouteEvent } from '../events/OnRouteEvent.js';
|
|
12
17
|
export type Config<TRouteTypes extends string, TErrorCodes extends string, TErrorConfigMap extends ErrorConfigMap<TErrorCodes>, TRouteParamMap extends RouteExtraPropsMap<TRouteTypes>, TRouteContextMap extends RouteContextMap<TRouteTypes, TRouteParamMap>, TRouteConfigMap extends RouteConfigMap<TRouteTypes, TErrorCodes, TRouteParamMap, TRouteContextMap>> = {
|
|
13
18
|
disableResponseValidation?: boolean;
|
|
14
19
|
logger?: Logger;
|
|
@@ -28,10 +33,11 @@ export type Config<TRouteTypes extends string, TErrorCodes extends string, TErro
|
|
|
28
33
|
apiVersion?: string;
|
|
29
34
|
servers?: Server[];
|
|
30
35
|
logLevel?: LogLevel;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
onRequest?: (e: OnRequestEvent) => Promise<void>;
|
|
37
|
+
onRoute?: (e: OnRouteEvent) => Promise<void>;
|
|
38
|
+
onHandler?: (e: OnHandlerEvent) => Promise<void>;
|
|
39
|
+
onResponse?: (e: OnResponseEvent) => Promise<void>;
|
|
40
|
+
onError?: (e: OnErrorEvent) => Promise<ErrorResponse<TErrorCodes, TErrorConfigMap>>;
|
|
35
41
|
middleware?: <T extends TRouteTypes>(route: AnyRoute<T>, ctx: Awaited<ReturnType<TRouteContextMap[T]>>) => Promise<{
|
|
36
42
|
body?: unknown;
|
|
37
43
|
status?: number;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ZodObject, ZodRawShape } from 'zod';
|
|
2
2
|
import { AnyRoute } from '../AnyRoute.js';
|
|
3
3
|
import { RouteExtraProps } from './RouteExtraProps.js';
|
|
4
|
+
import { Logger } from '../../services/Logger/Logger.js';
|
|
4
5
|
export type ContextParams<TRouteType extends string, TExtraProps extends ZodObject<ZodRawShape> | undefined> = {
|
|
5
6
|
route: AnyRoute<TRouteType> & RouteExtraProps<TExtraProps>;
|
|
6
7
|
request: Request;
|
|
8
|
+
logger: Logger;
|
|
7
9
|
params: {
|
|
8
10
|
body: unknown;
|
|
9
11
|
query: unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnyRoute } from '../AnyRoute.js';
|
|
2
|
+
import { OnRequestEvent } from './OnRequestEvent.js';
|
|
3
|
+
export interface OnRouteEvent extends OnRequestEvent {
|
|
4
|
+
path: string;
|
|
5
|
+
method: string;
|
|
6
|
+
params: Record<string, string>;
|
|
7
|
+
query: Record<string, string | string[]>;
|
|
8
|
+
body: unknown;
|
|
9
|
+
route: AnyRoute<string>;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED