snap-on-openapi 1.0.7 → 1.0.9

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.js CHANGED
@@ -43,7 +43,7 @@ export class OpenApi {
43
43
  this.basePath = config.basePath;
44
44
  const info = {
45
45
  title: config.apiName ?? 'My API',
46
- version: '3.1.0',
46
+ version: config.apiVersion ?? '1.0.0',
47
47
  };
48
48
  this.schemaGenerator = new SchemaGenerator(this.logger.derrive('SchemaGenerator'), info, this.config, this.routes, this.servers);
49
49
  this.wrappers = {
@@ -106,11 +106,11 @@ export class OpenApi {
106
106
  }
107
107
  getRouteForPath(path, method) {
108
108
  const fittingRoutes = [];
109
+ const pathParts = path.split('/').filter((x) => x !== '');
109
110
  outer: for (const route of this.routes) {
110
111
  if (route.method === method) {
111
112
  fittingRoutes.push(route);
112
113
  const routeParts = route.path.split('/').filter((x) => x !== '');
113
- const pathParts = path.split('/').filter((x) => x !== '');
114
114
  if (routeParts.length !== pathParts.length) {
115
115
  continue;
116
116
  }
@@ -130,7 +130,8 @@ export class OpenApi {
130
130
  async processRootRoute(originalReq) {
131
131
  try {
132
132
  const url = new URL(originalReq.url);
133
- const urlPath = url.pathname.replace(this.getBasePath(), '');
133
+ const basePath = this.getBasePath() === '/' ? '' : this.getBasePath();
134
+ const urlPath = url.pathname.replace(basePath, '');
134
135
  const route = this.getRouteForPath(urlPath, originalReq.method);
135
136
  if (!route) {
136
137
  this.logger.info(`Route for ${originalReq.method}:${urlPath} not found`);
@@ -177,7 +178,7 @@ export class OpenApi {
177
178
  };
178
179
  this.logger.info(`Calling route ${route.path}`);
179
180
  this.logger.info(`${req.method}: ${req.path}`, {
180
- params: req.params,
181
+ path: req.params,
181
182
  query: req.query,
182
183
  body: req.body,
183
184
  });
@@ -194,7 +195,7 @@ export class OpenApi {
194
195
  let response;
195
196
  const containsBody = route.method !== Method.GET;
196
197
  if (containsBody && route.validators.body) {
197
- const body = route.validators.body.strict().safeParse(req.body);
198
+ const body = route.validators.body.safeParse(req.body);
198
199
  if (!body.success) {
199
200
  throw new ValidationError(body.error, ValidationLocation.Body);
200
201
  }
@@ -237,7 +238,7 @@ export class OpenApi {
237
238
  }
238
239
  const finalResponse = route.validators.responseHeaders ? response : { body: response, headers: {} };
239
240
  const finalResponseValidator = z.object({
240
- body: route.validators.response,
241
+ body: route.validators.response ?? z.undefined(),
241
242
  headers: route.validators.responseHeaders?.strict() ?? z.object({}),
242
243
  });
243
244
  const validated = finalResponseValidator.safeParse(finalResponse);
@@ -16,7 +16,7 @@ export class DescriptionChecker {
16
16
  if (!route.description || route.description.length < minimalLength) {
17
17
  throw new Error(`Description for ${route.path} is missing or too small`);
18
18
  }
19
- this.checkValidatorDescriptions(route, 'responseValidator', 'responseValidator', route.validators.response);
19
+ this.checkValidatorDescriptions(route, 'responseValidator', 'responseValidator', route.validators.response ?? z.undefined());
20
20
  this.checkValidatorDescriptions(route, 'pathValidator', 'pathValidator', route.validators.path ?? z.object({}), false);
21
21
  this.checkValidatorDescriptions(route, 'queryValidator', 'queryValidator', route.validators.query ?? z.object({}), false);
22
22
  this.checkValidatorDescriptions(route, 'bodyValidator', 'bodyValidator', route.validators.body ?? z.object({}), false);
@@ -9,9 +9,10 @@ export declare class ExpressWrapper<TRouteTypes extends string, TErrorCodes exte
9
9
  protected developmentUtils: DevelopmentUtils;
10
10
  protected schemaRoute: RoutePath;
11
11
  constructor(openApi: OpenApi<TRouteTypes, TErrorCodes, TConfig>);
12
- requestBodyToString(req: ExpressRequest): Promise<string | undefined>;
13
12
  createStoplightRoute(route: RoutePath, expressApp: ExpressApp): void;
14
13
  createSwaggerRoute(route: RoutePath, expressApp: ExpressApp): void;
15
14
  createSchemaRoute(route: RoutePath, expressApp: ExpressApp): void;
16
15
  createOpenApiRootRoute(expressApp: ExpressApp): void;
16
+ protected covertExpressRequestToRequest(req: ExpressRequest): Promise<Request>;
17
+ protected requestBodyToString(req: ExpressRequest): Promise<string | undefined>;
17
18
  }
@@ -1,5 +1,5 @@
1
- import { format } from 'url';
2
1
  import { DevelopmentUtils } from '../DevelopmentUtils/DevelopmentUtils.js';
2
+ import { format, parse } from 'url';
3
3
  export class ExpressWrapper {
4
4
  service;
5
5
  developmentUtils;
@@ -8,25 +8,6 @@ export class ExpressWrapper {
8
8
  this.service = openApi;
9
9
  this.developmentUtils = new DevelopmentUtils();
10
10
  }
11
- async requestBodyToString(req) {
12
- if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
13
- return undefined;
14
- }
15
- return new Promise((resolve, reject) => {
16
- const chunks = [];
17
- req.on('data', (chunk) => {
18
- chunks.push(chunk);
19
- });
20
- req.on('end', () => {
21
- const bodyBuffer = Buffer.concat(chunks);
22
- const bodyString = bodyBuffer.toString();
23
- resolve(bodyString);
24
- });
25
- req.on('error', (error) => {
26
- reject(error);
27
- });
28
- });
29
- }
30
11
  createStoplightRoute(route, expressApp) {
31
12
  const handler = async (req, res) => {
32
13
  const body = this.developmentUtils.getStoplightHtml(this.schemaRoute);
@@ -52,30 +33,9 @@ export class ExpressWrapper {
52
33
  }
53
34
  createOpenApiRootRoute(expressApp) {
54
35
  const route = this.service.getBasePath();
55
- const headerToStr = (header) => {
56
- if (Array.isArray(header)) {
57
- return header.join(',');
58
- }
59
- return header;
60
- };
61
36
  const handler = async (req, res) => {
62
- const emptyHeaders = {};
63
- const headers = Object.entries(req.headers).reduce((acc, val) => ({
64
- ...acc,
65
- ...(typeof val[1] !== 'undefined' ? { [val[0]]: headerToStr(val[1]) } : {}),
66
- }), emptyHeaders);
67
- const url = format({
68
- protocol: req.protocol,
69
- host: req.host,
70
- pathname: req.originalUrl,
71
- });
72
- const body = await this.requestBodyToString(req);
73
- const openApiRequest = new Request(url, {
74
- headers: headers,
75
- method: req.method,
76
- body: body,
77
- });
78
- const result = await this.service.processRootRoute(openApiRequest);
37
+ const request = await this.covertExpressRequestToRequest(req);
38
+ const result = await this.service.processRootRoute(request);
79
39
  res.status(result.status);
80
40
  res.header('Content-Type', 'application/json');
81
41
  for (const header of Object.entries(result.headers)) {
@@ -90,4 +50,49 @@ export class ExpressWrapper {
90
50
  expressApp.delete(regex, handler);
91
51
  expressApp.put(regex, handler);
92
52
  }
53
+ async covertExpressRequestToRequest(req) {
54
+ const headerToStr = (header) => {
55
+ if (Array.isArray(header)) {
56
+ return header.join(',');
57
+ }
58
+ return header;
59
+ };
60
+ const emptyHeaders = {};
61
+ const headers = Object.entries(req.headers).reduce((acc, val) => ({
62
+ ...acc,
63
+ ...(typeof val[1] !== 'undefined' ? { [val[0]]: headerToStr(val[1]) } : {}),
64
+ }), emptyHeaders);
65
+ const body = await this.requestBodyToString(req);
66
+ const parsedUrl = parse(req.originalUrl, true);
67
+ const url = format({
68
+ ...parsedUrl,
69
+ host: req.host,
70
+ protocol: req.protocol,
71
+ });
72
+ const openApiRequest = new Request(url, {
73
+ headers: headers,
74
+ method: req.method,
75
+ body: body,
76
+ });
77
+ return openApiRequest;
78
+ }
79
+ async requestBodyToString(req) {
80
+ if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
81
+ return undefined;
82
+ }
83
+ return new Promise((resolve, reject) => {
84
+ const chunks = [];
85
+ req.on('data', (chunk) => {
86
+ chunks.push(chunk);
87
+ });
88
+ req.on('end', () => {
89
+ const bodyBuffer = Buffer.concat(chunks);
90
+ const bodyString = bodyBuffer.toString();
91
+ resolve(bodyString);
92
+ });
93
+ req.on('error', (error) => {
94
+ reject(error);
95
+ });
96
+ });
97
+ }
93
98
  }
@@ -6,5 +6,5 @@ import { RouteExtraProps } from '../../types/config/RouteExtraProps.js';
6
6
  export declare class RoutingFactory<TRouteTypes extends string, TErrorCodes extends string, TConfig extends AnyConfig<TRouteTypes, TErrorCodes>> {
7
7
  protected map: TConfig;
8
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, TResponseHeadersValidator extends ZodObject<ZodRawShape> | undefined = undefined>(params: Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TResponseHeadersValidator, TMethod> & (RouteExtraProps<TConfig['routes'][TType]['extraProps']>)): Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TResponseHeadersValidator>;
9
+ createRoute<TType extends TRouteTypes, TMethod extends Method, TResponseValidator extends ZodFirstPartySchemaTypes | undefined = undefined, TQueryValidator extends ZodObject<ZodRawShape> | undefined = undefined, TPathValidator extends ZodObject<ZodRawShape> | undefined = undefined, TBodyValidator extends ZodFirstPartySchemaTypes | undefined = undefined, TResponseHeadersValidator extends ZodObject<ZodRawShape> | undefined = undefined>(params: Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TResponseHeadersValidator, TMethod> & (RouteExtraProps<TConfig['routes'][TType]['extraProps']>)): Route<TType, Awaited<ReturnType<TConfig['routes'][TType]['contextFactory']>>, TResponseValidator, TPathValidator, TQueryValidator, TBodyValidator, TResponseHeadersValidator>;
10
10
  }
@@ -13,6 +13,8 @@ export declare class SchemaGenerator<TRouteTypes extends string, TErrorCodes ext
13
13
  protected routeSpec: TConfig;
14
14
  constructor(logger: Logger, info: Info, spec: TConfig, routes: AnyRoute<TRouteTypes>[], servers: Server[]);
15
15
  getYaml(): string;
16
+ getJson(): string;
17
+ saveJson(path: string): void;
16
18
  saveYaml(path: string): void;
17
19
  protected createDocument(): ReturnType<typeof createDocument>;
18
20
  protected createOperation(route: AnyRoute<TRouteTypes>): ZodOpenApiOperationObject;
@@ -19,17 +19,29 @@ export class SchemaGenerator {
19
19
  }
20
20
  getYaml() {
21
21
  const document = this.createDocument();
22
+ this.logger.info('Generating YAML for Open API');
22
23
  const yaml = stringify(document, { aliasDuplicateObjects: false });
23
24
  return yaml;
24
25
  }
26
+ getJson() {
27
+ const document = this.createDocument();
28
+ this.logger.info('Generating JSON for Open API');
29
+ const json = JSON.stringify(document, null, 2);
30
+ return json;
31
+ }
32
+ saveJson(path) {
33
+ const json = this.getJson();
34
+ this.logger.info(`Saving JSON to ${path}`);
35
+ writeFileSync(path, json);
36
+ }
25
37
  saveYaml(path) {
26
- this.logger.info('Generating YAML for Open API');
27
38
  const yaml = this.getYaml();
39
+ this.logger.info(`Saving YAML to ${path}`);
28
40
  writeFileSync(path, yaml);
29
41
  }
30
42
  createDocument() {
31
43
  const openApi = {
32
- openapi: '3.1.0',
44
+ openapi: this.routeSpec.generator?.openApiVersion ?? '3.1.0',
33
45
  info: this.info,
34
46
  components: {
35
47
  securitySchemes: {
@@ -68,15 +80,20 @@ export class SchemaGenerator {
68
80
  query: route.validators.query,
69
81
  path: route.validators.path,
70
82
  };
83
+ const mediaTypes = this.routeSpec.generator?.responseMediaTypes ?? ['application/json'];
84
+ const content = mediaTypes.reduce((acc, mediaType) => ({
85
+ ...acc,
86
+ [mediaType]: { schema: route.validators.response },
87
+ }), {});
71
88
  const operation = {
72
89
  requestParams: requestParams,
73
90
  description: route.description,
91
+ tags: route.tags,
92
+ operationId: route.operationId,
74
93
  responses: {
75
94
  200: {
76
- description: 'Good Response',
77
- content: {
78
- 'application/json': { schema: route.validators.response },
79
- },
95
+ description: this.routeSpec.generator?.goodResponseDescription ?? 'Good Response',
96
+ content: route.validators.response ? content : undefined,
80
97
  },
81
98
  },
82
99
  };
@@ -94,22 +111,25 @@ export class SchemaGenerator {
94
111
  errors.push(errorConfig);
95
112
  httpStatusMap.set(errorConfig.status, errors);
96
113
  }
97
- for (const [code, errors] of httpStatusMap.entries()) {
98
- const error = errors[0];
99
- if (!error) {
100
- throw new Error(`No errors found for code '${code}'`);
101
- }
102
- const description = errors.map((x) => x.description).join(' or ');
103
- const validators = errors.map((x) => x.responseValidator);
104
- const schema = errors.length === 1 ? error.responseValidator : z.union(validators).openapi({ unionOneOf: true });
105
- operation.responses[error.status] = {
106
- description: description,
107
- content: {
108
- 'application/json': {
109
- schema: schema,
114
+ const shouldGenerateErrors = this.routeSpec.generator?.generateErrors ?? true;
115
+ if (shouldGenerateErrors) {
116
+ for (const [code, errors] of httpStatusMap.entries()) {
117
+ const error = errors[0];
118
+ if (!error) {
119
+ throw new Error(`No errors found for code '${code}'`);
120
+ }
121
+ const description = errors.map((x) => x.description).join(' or ');
122
+ const validators = errors.map((x) => x.responseValidator);
123
+ const schema = errors.length === 1 ? error.responseValidator : z.union(validators).openapi({ unionOneOf: true });
124
+ operation.responses[error.status] = {
125
+ description: description,
126
+ content: {
127
+ 'application/json': {
128
+ schema: schema,
129
+ },
110
130
  },
111
- },
112
- };
131
+ };
132
+ }
113
133
  }
114
134
  if (this.routeSpec.routes[route.type].authorization) {
115
135
  operation.security = [
@@ -118,11 +138,14 @@ export class SchemaGenerator {
118
138
  },
119
139
  ];
120
140
  }
121
- if (route.method !== Method.GET) {
141
+ if (route.method !== Method.GET && route.validators.body) {
142
+ const mediaTypes = this.routeSpec.generator?.requestMediaTypes ?? ['application/json'];
143
+ const content = mediaTypes.reduce((acc, mediaType) => ({
144
+ ...acc,
145
+ [mediaType]: { schema: route.validators.body },
146
+ }), {});
122
147
  operation.requestBody = {
123
- content: {
124
- 'application/json': { schema: route.validators.body },
125
- },
148
+ content,
126
149
  };
127
150
  }
128
151
  return operation;
@@ -2,7 +2,8 @@ import 'zod-openapi/extend';
2
2
  import { ZodObject, ZodRawShape, ZodType } from 'zod';
3
3
  export declare class ValidationUtils {
4
4
  readonly strings: {
5
- datetime: import("zod").ZodEffects<import("zod").ZodEffects<import("zod").ZodString, string, string>, Date, string>;
5
+ datetime: import("zod").ZodUnion<[import("zod").ZodDate, import("zod").ZodEffects<import("zod").ZodEffects<import("zod").ZodString, string, string>, Date, string>]>;
6
+ date: import("zod").ZodUnion<[import("zod").ZodDate, import("zod").ZodEffects<import("zod").ZodEffects<import("zod").ZodString, string, string>, Date, string>]>;
6
7
  number: import("zod").ZodEffects<import("zod").ZodEffects<import("zod").ZodString, string, string>, number, string>;
7
8
  boolean: import("zod").ZodEffects<import("zod").ZodEnum<["true", "false"]>, boolean, "true" | "false">;
8
9
  };
@@ -39,18 +40,18 @@ export declare class ValidationUtils {
39
40
  count: number;
40
41
  }>;
41
42
  }, "strip", import("zod").ZodTypeAny, {
43
+ items: T["_output"][];
42
44
  info: {
43
45
  page: number;
44
46
  pageSize: number;
45
47
  count: number;
46
48
  };
47
- items: T["_output"][];
48
49
  }, {
50
+ items: T["_input"][];
49
51
  info: {
50
52
  page: number;
51
53
  pageSize: number;
52
54
  count: number;
53
55
  };
54
- items: T["_input"][];
55
56
  }>;
56
57
  }
@@ -1,11 +1,13 @@
1
1
  import 'zod-openapi/extend';
2
2
  import { array, number, object } from 'zod';
3
- import { stringDateTransformer } from './transformers/stringDateTransformer.js';
4
3
  import { stringNumberTransformer } from './transformers/stringNumberTransfromer.js';
5
4
  import { stringBooleanTransformer } from './transformers/stringBooleanTransformer.js';
5
+ import { stringDateTimeTransformer } from './transformers/stringDateTimeTransformer.js';
6
+ import { stringDateTransformer } from './transformers/stringDateTransformer.js';
6
7
  export class ValidationUtils {
7
8
  strings = {
8
- datetime: stringDateTransformer,
9
+ datetime: stringDateTimeTransformer,
10
+ date: stringDateTransformer,
9
11
  number: stringNumberTransformer,
10
12
  boolean: stringBooleanTransformer,
11
13
  };
@@ -0,0 +1,3 @@
1
+ import 'zod-openapi/extend';
2
+ import z from 'zod';
3
+ export declare const stringDateTimeTransformer: z.ZodUnion<[z.ZodDate, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, Date, string>]>;
@@ -0,0 +1,20 @@
1
+ import 'zod-openapi/extend';
2
+ import z from 'zod';
3
+ export const stringDateTimeTransformer = z.union([
4
+ z.date(),
5
+ z.string().refine((input) => {
6
+ try {
7
+ const output = new Date(Date.parse(input));
8
+ const outputStr = output.toISOString().replace('T', ' ');
9
+ const inputStr = input.replace('T', ' ');
10
+ console.log(inputStr, outputStr);
11
+ return inputStr === outputStr;
12
+ }
13
+ catch (e) {
14
+ return false;
15
+ }
16
+ }, 'Not a valid date string')
17
+ .transform((x) => {
18
+ return new Date(Date.parse(x));
19
+ }),
20
+ ]).openapi({ type: 'string', format: 'date-time' });
@@ -0,0 +1,13 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { stringDateTimeTransformer } from './stringDateTimeTransformer.js';
3
+ describe('stringDateTimeTransformer', () => {
4
+ test('Can parse ISO datetime', async () => {
5
+ const result = stringDateTimeTransformer.safeParse('2025-07-03T08:33:32.442Z');
6
+ expect(result.success).toBe(true);
7
+ expect(result.data?.toISOString()).toBe('2025-07-03T08:33:32.442Z');
8
+ });
9
+ test("Doesn't throw on bad input", async () => {
10
+ const result = stringDateTimeTransformer.safeParse('jibberish');
11
+ expect(result.success).toBe(false);
12
+ });
13
+ });
@@ -1,3 +1,3 @@
1
1
  import 'zod-openapi/extend';
2
2
  import z from 'zod';
3
- export declare const stringDateTransformer: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, Date, string>;
3
+ export declare const stringDateTransformer: z.ZodUnion<[z.ZodDate, z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, Date, string>]>;
@@ -1,16 +1,19 @@
1
1
  import 'zod-openapi/extend';
2
2
  import z from 'zod';
3
- export const stringDateTransformer = z.string().refine((input) => {
4
- try {
5
- const output = new Date(Date.parse(input));
6
- const outputStr = output.toISOString().replace('T', ' ');
7
- const inputStr = input.replace('T', ' ');
8
- return inputStr === outputStr;
9
- }
10
- catch (e) {
11
- return false;
12
- }
13
- }, 'Not a valid date string')
14
- .transform((x) => {
15
- return new Date(Date.parse(x));
16
- }).openapi({ type: 'string', format: 'date-time' });
3
+ export const stringDateTransformer = z.union([
4
+ z.date(),
5
+ z.string().refine((input) => {
6
+ try {
7
+ const output = new Date(Date.parse(input));
8
+ const outputStr = output.toISOString().split('T')[0];
9
+ const inputStr = input.replace('T', ' ');
10
+ return inputStr === outputStr;
11
+ }
12
+ catch (e) {
13
+ return false;
14
+ }
15
+ }, 'Not a valid date string')
16
+ .transform((x) => {
17
+ return new Date(Date.parse(x));
18
+ }),
19
+ ]).openapi({ type: 'string', format: 'date-time' });
@@ -1,3 +1,3 @@
1
1
  import { ZodObject, ZodRawShape, ZodFirstPartySchemaTypes } from 'zod';
2
2
  import { Route } from './Route.js';
3
- export type AnyRoute<TRouteType extends string> = Route<TRouteType, any, ZodFirstPartySchemaTypes, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined>;
3
+ export type AnyRoute<TRouteType extends string> = Route<TRouteType, any, ZodFirstPartySchemaTypes | undefined, ZodObject<ZodRawShape> | undefined, ZodObject<ZodRawShape> | undefined, ZodFirstPartySchemaTypes | undefined, ZodObject<ZodRawShape> | undefined>;
@@ -1,13 +1,15 @@
1
1
  import { z, ZodFirstPartySchemaTypes, ZodObject, ZodRawShape } from 'zod';
2
2
  import { Method } from '../enums/Methods.js';
3
3
  import { RoutePath } from './RoutePath.js';
4
- type BodyHandlerResponse<T extends ZodFirstPartySchemaTypes> = Promise<z.infer<T>>;
5
- type FullHandlerRespnse<T extends ZodFirstPartySchemaTypes, THeaders extends ZodObject<ZodRawShape>> = Promise<{
6
- body: z.infer<T>;
7
- headers: z.infer<THeaders>;
4
+ type BodyHandlerResponse<T extends ZodFirstPartySchemaTypes | undefined = undefined> = Promise<T extends undefined ? undefined : z.infer<Exclude<T, undefined>>>;
5
+ type FullHandlerRespnse<T extends ZodFirstPartySchemaTypes | undefined, THeaders extends ZodObject<ZodRawShape> | undefined> = Promise<{
6
+ body: Awaited<BodyHandlerResponse<T>>;
7
+ headers: z.infer<Exclude<THeaders, undefined>>;
8
8
  }>;
9
- type HandlerResponse<T extends ZodFirstPartySchemaTypes, TH extends ZodObject<ZodRawShape> | undefined> = TH extends undefined ? BodyHandlerResponse<T> : FullHandlerRespnse<T, Exclude<TH, undefined>>;
10
- export interface Route<TType extends string, TContext extends object, TResponseValidator extends ZodFirstPartySchemaTypes, TPathValidator extends ZodObject<ZodRawShape> | undefined, TQueryValidator extends ZodObject<ZodRawShape> | undefined, TBodyValidator extends ZodObject<ZodRawShape> | undefined, TResponseHeadersValidator extends ZodObject<ZodRawShape> | undefined, TMethod extends Method = Method> {
9
+ type HandlerResponse<T extends ZodFirstPartySchemaTypes | undefined, TH extends ZodObject<ZodRawShape> | undefined> = TH extends undefined ? BodyHandlerResponse<T> : FullHandlerRespnse<T, Exclude<TH, undefined>>;
10
+ export interface Route<TType extends string, TContext extends object, TResponseValidator extends ZodFirstPartySchemaTypes | undefined, TPathValidator extends ZodObject<ZodRawShape> | undefined, TQueryValidator extends ZodObject<ZodRawShape> | undefined, TBodyValidator extends ZodFirstPartySchemaTypes | undefined, TResponseHeadersValidator extends ZodObject<ZodRawShape> | undefined, TMethod extends Method = Method> {
11
+ tags?: string[];
12
+ operationId?: string;
11
13
  type: TType;
12
14
  method: TMethod;
13
15
  path: RoutePath;
@@ -16,12 +18,12 @@ export interface Route<TType extends string, TContext extends object, TResponseV
16
18
  query?: TQueryValidator;
17
19
  path?: TPathValidator;
18
20
  body?: TMethod extends 'GET' ? never : TBodyValidator;
19
- response: TResponseValidator;
21
+ response?: TResponseValidator;
20
22
  responseHeaders?: TResponseHeadersValidator;
21
23
  };
22
24
  handler: (context: {
23
25
  params: {
24
- body: TBodyValidator extends ZodObject<ZodRawShape> ? z.infer<TBodyValidator> : object;
26
+ body: TBodyValidator extends ZodFirstPartySchemaTypes ? z.infer<TBodyValidator> : object;
25
27
  query: TQueryValidator extends ZodObject<ZodRawShape> ? z.infer<TQueryValidator> : object;
26
28
  path: TPathValidator extends ZodObject<ZodRawShape> ? z.infer<TPathValidator> : object;
27
29
  };
@@ -1,3 +1,4 @@
1
+ import { ZodOpenApiVersion } from 'zod-openapi';
1
2
  import { Logger } from '../../services/Logger/Logger.js';
2
3
  import { LogLevel } from '../../services/Logger/types/LogLevel.js';
3
4
  import { AnyRoute } from '../AnyRoute.js';
@@ -12,10 +13,18 @@ export type Config<TRouteTypes extends string, TErrorCodes extends string, TErro
12
13
  logger?: Logger;
13
14
  basePath: RoutePath;
14
15
  routes: TRouteConfigMap;
16
+ generator?: {
17
+ openApiVersion?: ZodOpenApiVersion;
18
+ goodResponseDescription?: string;
19
+ generateErrors?: boolean;
20
+ requestMediaTypes?: string[];
21
+ responseMediaTypes?: string[];
22
+ };
15
23
  errors: TErrorConfigMap;
16
24
  defaultError: ErrorResponse<TErrorCodes, TErrorConfigMap>;
17
25
  skipDescriptionsCheck?: boolean;
18
26
  apiName?: string;
27
+ apiVersion?: string;
19
28
  servers?: Server[];
20
29
  logLevel?: LogLevel;
21
30
  handleError?: (e: unknown, req: Request) => ErrorResponse<TErrorCodes, TErrorConfigMap>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "snap-on-openapi",
3
3
  "author": "Alex Sarychev",
4
- "version": "1.0.7",
4
+ "version": "1.0.9",
5
5
  "description": "Swiftly build type-checked OpenAPI applications with Zod and TypeScript",
6
6
  "type": "module",
7
7
  "license": "ISC",