Skip to content

Commit bb53f78

Browse files
committed
feat(openapi_3.1): adds open api 3.1 type
1 parent 1894d84 commit bb53f78

28 files changed

+102
-87
lines changed

src/framework/ajv/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@ interface SerDesSchema extends Partial<SerDes> {
1111
}
1212

1313
export function createRequestAjv(
14-
openApiSpec: OpenAPIV3.Document,
14+
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
1515
options: Options = {},
1616
): AjvDraft4 {
1717
return createAjv(openApiSpec, options);
1818
}
1919

2020
export function createResponseAjv(
21-
openApiSpec: OpenAPIV3.Document,
21+
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
2222
options: Options = {},
2323
): AjvDraft4 {
2424
return createAjv(openApiSpec, options, false);
2525
}
2626

2727
function createAjv(
28-
openApiSpec: OpenAPIV3.Document,
28+
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
2929
options: Options = {},
3030
request = true,
3131
): AjvDraft4 {

src/framework/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class OpenAPIFramework {
7575
private loadSpec(
7676
filePath: string | object,
7777
$refParser: { mode: 'bundle' | 'dereference' } = { mode: 'bundle' },
78-
): Promise<OpenAPIV3.Document> {
78+
): Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1> {
7979
// Because of this issue ( https://github.com/APIDevTools/json-schema-ref-parser/issues/101#issuecomment-421755168 )
8080
// We need this workaround ( use '$RefParser.dereference' instead of '$RefParser.bundle' ) if asked by user
8181
if (typeof filePath === 'string') {
@@ -87,7 +87,7 @@ export class OpenAPIFramework {
8787
$refParser.mode === 'dereference'
8888
? $RefParser.dereference(absolutePath)
8989
: $RefParser.bundle(absolutePath);
90-
return doc as Promise<OpenAPIV3.Document>;
90+
return doc as Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1>;
9191
} else {
9292
throw new Error(
9393
`${this.loggingPrefix}spec could not be read at ${filePath}`,
@@ -98,10 +98,10 @@ export class OpenAPIFramework {
9898
$refParser.mode === 'dereference'
9999
? $RefParser.dereference(filePath)
100100
: $RefParser.bundle(filePath);
101-
return doc as Promise<OpenAPIV3.Document>;
101+
return doc as Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1>;
102102
}
103103

104-
private sortApiDocTags(apiDoc: OpenAPIV3.Document): void {
104+
private sortApiDocTags(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1): void {
105105
if (apiDoc && Array.isArray(apiDoc.tags)) {
106106
apiDoc.tags.sort((a, b): number => {
107107
return a.name < b.name ? -1 : 1;

src/framework/openapi.context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface RoutePair {
66
openApiRoute: string;
77
}
88
export class OpenApiContext {
9-
public readonly apiDoc: OpenAPIV3.Document;
9+
public readonly apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
1010
public readonly expressRouteMap = {};
1111
public readonly openApiRouteMap = {};
1212
public readonly routes: RouteMetadata[] = [];

src/framework/openapi.schema.validator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class OpenAPISchemaValidator {
5656
this.validator = ajvInstance.compile(schema);
5757
}
5858

59-
public validate(openapiDoc: OpenAPIV3.Document): {
59+
public validate(openapiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1): {
6060
errors: Array<ErrorObject> | null;
6161
} {
6262
const valid = this.validator(openapiDoc);

src/framework/openapi.spec.loader.ts

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from './types';
77

88
export interface Spec {
9-
apiDoc: OpenAPIV3.Document;
9+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
1010
basePaths: string[];
1111
routes: RouteMetadata[];
1212
}
@@ -20,7 +20,7 @@ export interface RouteMetadata {
2020
}
2121

2222
interface DiscoveredRoutes {
23-
apiDoc: OpenAPIV3.Document;
23+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
2424
basePaths: string[];
2525
routes: RouteMetadata[];
2626
}
@@ -47,42 +47,44 @@ export class OpenApiSpecLoader {
4747
const routes: RouteMetadata[] = [];
4848
const toExpressParams = this.toExpressParams;
4949
// const basePaths = this.framework.basePaths;
50-
// let apiDoc: OpenAPIV3.Document = null;
50+
// let apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1 = null;
5151
// let basePaths: string[] = null;
5252
const { apiDoc, basePaths } = await this.framework.initialize({
5353
visitApi(ctx: OpenAPIFrameworkAPIContext): void {
5454
const apiDoc = ctx.getApiDoc();
5555
const basePaths = ctx.basePaths;
5656
for (const bpa of basePaths) {
5757
const bp = bpa.replace(/\/$/, '');
58-
for (const [path, methods] of Object.entries(apiDoc.paths)) {
59-
for (const [method, schema] of Object.entries(methods)) {
60-
if (
61-
method.startsWith('x-') ||
62-
['parameters', 'summary', 'description'].includes(method)
63-
) {
64-
continue;
65-
}
66-
const pathParams = new Set<string>();
67-
const parameters = [...schema.parameters ?? [], ...methods.parameters ?? []]
68-
for (const param of parameters) {
69-
if (param.in === 'path') {
70-
pathParams.add(param.name);
58+
if (apiDoc.paths) {
59+
for (const [path, methods] of Object.entries(apiDoc.paths)) {
60+
for (const [method, schema] of Object.entries(methods)) {
61+
if (
62+
method.startsWith('x-') ||
63+
['parameters', 'summary', 'description'].includes(method)
64+
) {
65+
continue;
66+
}
67+
const pathParams = new Set<string>();
68+
const parameters = [...schema.parameters ?? [], ...methods.parameters ?? []]
69+
for (const param of parameters) {
70+
if (param.in === 'path') {
71+
pathParams.add(param.name);
72+
}
7173
}
74+
const openApiRoute = `${bp}${path}`;
75+
const expressRoute = `${openApiRoute}`
76+
.split(':')
77+
.map(toExpressParams)
78+
.join('\\:');
79+
80+
routes.push({
81+
basePath: bp,
82+
expressRoute,
83+
openApiRoute,
84+
method: method.toUpperCase(),
85+
pathParams: Array.from(pathParams),
86+
});
7287
}
73-
const openApiRoute = `${bp}${path}`;
74-
const expressRoute = `${openApiRoute}`
75-
.split(':')
76-
.map(toExpressParams)
77-
.join('\\:');
78-
79-
routes.push({
80-
basePath: bp,
81-
expressRoute,
82-
openApiRoute,
83-
method: method.toUpperCase(),
84-
pathParams: Array.from(pathParams),
85-
});
8688
}
8789
}
8890
}

src/framework/types.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface ValidationSchema extends ParametersSchema {
2121
}
2222

2323
export interface OpenAPIFrameworkInit {
24-
apiDoc: OpenAPIV3.Document;
24+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
2525
basePaths: string[];
2626
}
2727
export type SecurityHandlers = {
@@ -109,7 +109,7 @@ export type SerDesMap = {
109109
};
110110

111111
export interface OpenApiValidatorOpts {
112-
apiSpec: OpenAPIV3.Document | string;
112+
apiSpec: OpenAPIV3.DocumentV3 | string;
113113
validateApiSpec?: boolean;
114114
validateResponses?: boolean | ValidateResponseOpts;
115115
validateRequests?: boolean | ValidateRequestOpts;
@@ -152,7 +152,7 @@ export interface NormalizedOpenApiValidatorOpts extends OpenApiValidatorOpts {
152152
}
153153

154154
export namespace OpenAPIV3 {
155-
export interface Document {
155+
export interface DocumentV3 {
156156
openapi: string;
157157
info: InfoObject;
158158
servers?: ServerObject[];
@@ -163,6 +163,13 @@ export namespace OpenAPIV3 {
163163
externalDocs?: ExternalDocumentationObject;
164164
}
165165

166+
export interface DocumentV3_1 extends Omit<DocumentV3, 'paths'> {
167+
paths?: DocumentV3['paths']
168+
webhooks: {
169+
[name: string]: PathItemObject | ReferenceObject
170+
}
171+
}
172+
166173
export interface InfoObject {
167174
title: string;
168175
description?: string;
@@ -474,7 +481,7 @@ export interface OpenAPIFrameworkPathObject {
474481
}
475482

476483
interface OpenAPIFrameworkArgs {
477-
apiDoc: OpenAPIV3.Document | string;
484+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1 | string;
478485
validateApiSpec?: boolean;
479486
$refParser?: {
480487
mode: 'bundle' | 'dereference';
@@ -484,7 +491,7 @@ interface OpenAPIFrameworkArgs {
484491
export interface OpenAPIFrameworkAPIContext {
485492
// basePaths: BasePath[];
486493
basePaths: string[];
487-
getApiDoc(): OpenAPIV3.Document;
494+
getApiDoc(): OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
488495
}
489496

490497
export interface OpenAPIFrameworkVisitor {

src/middlewares/openapi.metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { httpMethods } from './parsers/schema.preprocessor';
1515

1616
export function applyOpenApiMetadata(
1717
openApiContext: OpenApiContext,
18-
responseApiDoc: OpenAPIV3.Document,
18+
responseApiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
1919
): OpenApiRequestHandler {
2020
return (req: OpenApiRequest, res: Response, next: NextFunction): void => {
2121
// note base path is empty when path is fully qualified i.e. req.path.startsWith('')

src/middlewares/openapi.multipart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { MulterError } from 'multer';
1515
const multer = require('multer');
1616

1717
export function multipart(
18-
apiDoc: OpenAPIV3.Document,
18+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
1919
options: MultipartOpts,
2020
): OpenApiRequestHandler {
2121
const mult = multer(options.multerOpts);

src/middlewares/openapi.request.validator.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { BodySchemaParser } from './parsers/body.parse';
2222
import { ParametersSchemaParser } from './parsers/schema.parse';
2323
import { RequestParameterMutator } from './parsers/req.parameter.mutator';
24+
import { debug } from 'console';
2425

2526
type OperationObject = OpenAPIV3.OperationObject;
2627
type SchemaObject = OpenAPIV3.SchemaObject;
@@ -31,13 +32,13 @@ type ApiKeySecurityScheme = OpenAPIV3.ApiKeySecurityScheme;
3132

3233
export class RequestValidator {
3334
private middlewareCache: { [key: string]: RequestHandler } = {};
34-
private apiDoc: OpenAPIV3.Document;
35+
private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
3536
private ajv: Ajv;
3637
private ajvBody: Ajv;
3738
private requestOpts: ValidateRequestOpts = {};
3839

3940
constructor(
40-
apiDoc: OpenAPIV3.Document,
41+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
4142
options: RequestValidatorOptions = {},
4243
) {
4344
this.middlewareCache = {};
@@ -267,15 +268,15 @@ export class RequestValidator {
267268
}
268269

269270
class Validator {
270-
private readonly apiDoc: OpenAPIV3.Document;
271+
private readonly apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
271272
readonly schemaGeneral: object;
272273
readonly schemaBody: object;
273274
readonly validatorGeneral: ValidateFunction;
274275
readonly validatorBody: ValidateFunction;
275276
readonly allSchemaProperties: ValidationSchema;
276277

277278
constructor(
278-
apiDoc: OpenAPIV3.Document,
279+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
279280
parametersSchema: ParametersSchema,
280281
bodySchema: BodySchema,
281282
ajv: {
@@ -329,7 +330,7 @@ class Validator {
329330

330331
class Security {
331332
public static queryParam(
332-
apiDocs: OpenAPIV3.Document,
333+
apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
333334
schema: OperationObject,
334335
): string[] {
335336
const hasPathSecurity = schema.security?.length > 0 ?? false;

src/middlewares/openapi.response.validator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ interface ValidateResult {
2727
}
2828
export class ResponseValidator {
2929
private ajvBody: Ajv;
30-
private spec: OpenAPIV3.Document;
30+
private spec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
3131
private validatorsCache: {
3232
[key: string]: { [key: string]: ValidateFunction };
3333
} = {};
3434
private eovOptions: ValidateResponseOpts;
3535

3636
constructor(
37-
openApiSpec: OpenAPIV3.Document,
37+
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
3838
options: Options = {},
3939
eovOptions: ValidateResponseOpts = {},
4040
) {

src/middlewares/openapi.security.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface SecurityHandlerResult {
2323
error?: string;
2424
}
2525
export function security(
26-
apiDoc: OpenAPIV3.Document,
26+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
2727
securityHandlers: SecurityHandlers,
2828
): OpenApiRequestHandler {
2929
return async (req, res, next) => {

src/middlewares/parsers/req.parameter.mutator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ type Schema = ReferenceObject | SchemaObject;
3939
* the request is mutated to accomodate various styles and types e.g. form, explode, deepObject, etc
4040
*/
4141
export class RequestParameterMutator {
42-
private _apiDocs: OpenAPIV3.Document;
42+
private _apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
4343
private path: string;
4444
private ajv: Ajv;
4545
private parsedSchema: ValidationSchema;
4646

4747
constructor(
4848
ajv: Ajv,
49-
apiDocs: OpenAPIV3.Document,
49+
apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
5050
path: string,
5151
parsedSchema: ValidationSchema,
5252
) {

src/middlewares/parsers/schema.parse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ type Parameter = OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject;
1717
*/
1818
export class ParametersSchemaParser {
1919
private _ajv: Ajv;
20-
private _apiDocs: OpenAPIV3.Document;
20+
private _apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
2121

22-
constructor(ajv: Ajv, apiDocs: OpenAPIV3.Document) {
22+
constructor(ajv: Ajv, apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1) {
2323
this._ajv = ajv;
2424
this._apiDocs = apiDocs;
2525
}

src/middlewares/parsers/schema.preprocessor.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ export const httpMethods = new Set([
9191
]);
9292
export class SchemaPreprocessor {
9393
private ajv: Ajv;
94-
private apiDoc: OpenAPIV3.Document;
95-
private apiDocRes: OpenAPIV3.Document;
94+
private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
95+
private apiDocRes: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
9696
private serDesMap: SerDesMap;
9797
private responseOpts: ValidateResponseOpts;
9898
constructor(
99-
apiDoc: OpenAPIV3.Document,
99+
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
100100
ajvOptions: Options,
101101
validateResponsesOpts: ValidateResponseOpts,
102102
) {
@@ -108,22 +108,28 @@ export class SchemaPreprocessor {
108108

109109
public preProcess() {
110110
const componentSchemas = this.gatherComponentSchemaNodes();
111-
const r = this.gatherSchemaNodesFromPaths();
111+
let r;
112+
113+
if (this.apiDoc.paths) {
114+
r = this.gatherSchemaNodesFromPaths();
115+
}
112116

113117
// Now that we've processed paths, clone a response spec if we are validating responses
114118
this.apiDocRes = !!this.responseOpts ? cloneDeep(this.apiDoc) : null;
115119

116120
const schemaNodes = {
117121
schemas: componentSchemas,
118-
requestBodies: r.requestBodies,
119-
responses: r.responses,
120-
requestParameters: r.requestParameters,
122+
requestBodies: r?.requestBodies,
123+
responses: r?.responses,
124+
requestParameters: r?.requestParameters,
121125
};
122126

123127
// Traverse the schemas
124-
this.traverseSchemas(schemaNodes, (parent, schema, opts) =>
128+
if (r) {
129+
this.traverseSchemas(schemaNodes, (parent, schema, opts) =>
125130
this.schemaVisitor(parent, schema, opts),
126131
);
132+
}
127133

128134
return {
129135
apiDoc: this.apiDoc,

0 commit comments

Comments
 (0)