Skip to content

Commit

Permalink
feat(openapi_3.1): adds open api 3.1 type
Browse files Browse the repository at this point in the history
  • Loading branch information
SF97 committed Feb 5, 2024
1 parent 1894d84 commit 432dd0e
Show file tree
Hide file tree
Showing 18 changed files with 89 additions and 74 deletions.
6 changes: 3 additions & 3 deletions src/framework/ajv/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ interface SerDesSchema extends Partial<SerDes> {
}

export function createRequestAjv(
openApiSpec: OpenAPIV3.Document,
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
options: Options = {},
): AjvDraft4 {
return createAjv(openApiSpec, options);
}

export function createResponseAjv(
openApiSpec: OpenAPIV3.Document,
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
options: Options = {},
): AjvDraft4 {
return createAjv(openApiSpec, options, false);
}

function createAjv(
openApiSpec: OpenAPIV3.Document,
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
options: Options = {},
request = true,
): AjvDraft4 {
Expand Down
8 changes: 4 additions & 4 deletions src/framework/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class OpenAPIFramework {
private loadSpec(
filePath: string | object,
$refParser: { mode: 'bundle' | 'dereference' } = { mode: 'bundle' },
): Promise<OpenAPIV3.Document> {
): Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1> {
// Because of this issue ( https://github.com/APIDevTools/json-schema-ref-parser/issues/101#issuecomment-421755168 )
// We need this workaround ( use '$RefParser.dereference' instead of '$RefParser.bundle' ) if asked by user
if (typeof filePath === 'string') {
Expand All @@ -87,7 +87,7 @@ export class OpenAPIFramework {
$refParser.mode === 'dereference'
? $RefParser.dereference(absolutePath)
: $RefParser.bundle(absolutePath);
return doc as Promise<OpenAPIV3.Document>;
return doc as Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1>;
} else {
throw new Error(
`${this.loggingPrefix}spec could not be read at ${filePath}`,
Expand All @@ -98,10 +98,10 @@ export class OpenAPIFramework {
$refParser.mode === 'dereference'
? $RefParser.dereference(filePath)
: $RefParser.bundle(filePath);
return doc as Promise<OpenAPIV3.Document>;
return doc as Promise<OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1>;
}

private sortApiDocTags(apiDoc: OpenAPIV3.Document): void {
private sortApiDocTags(apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1): void {
if (apiDoc && Array.isArray(apiDoc.tags)) {
apiDoc.tags.sort((a, b): number => {
return a.name < b.name ? -1 : 1;
Expand Down
2 changes: 1 addition & 1 deletion src/framework/openapi.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface RoutePair {
openApiRoute: string;
}
export class OpenApiContext {
public readonly apiDoc: OpenAPIV3.Document;
public readonly apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
public readonly expressRouteMap = {};
public readonly openApiRouteMap = {};
public readonly routes: RouteMetadata[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/framework/openapi.schema.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class OpenAPISchemaValidator {
this.validator = ajvInstance.compile(schema);
}

public validate(openapiDoc: OpenAPIV3.Document): {
public validate(openapiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1): {
errors: Array<ErrorObject> | null;
} {
const valid = this.validator(openapiDoc);
Expand Down
60 changes: 31 additions & 29 deletions src/framework/openapi.spec.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from './types';

export interface Spec {
apiDoc: OpenAPIV3.Document;
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
basePaths: string[];
routes: RouteMetadata[];
}
Expand All @@ -20,7 +20,7 @@ export interface RouteMetadata {
}

interface DiscoveredRoutes {
apiDoc: OpenAPIV3.Document;
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
basePaths: string[];
routes: RouteMetadata[];
}
Expand All @@ -47,42 +47,44 @@ export class OpenApiSpecLoader {
const routes: RouteMetadata[] = [];
const toExpressParams = this.toExpressParams;
// const basePaths = this.framework.basePaths;
// let apiDoc: OpenAPIV3.Document = null;
// let apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1 = null;
// let basePaths: string[] = null;
const { apiDoc, basePaths } = await this.framework.initialize({
visitApi(ctx: OpenAPIFrameworkAPIContext): void {
const apiDoc = ctx.getApiDoc();
const basePaths = ctx.basePaths;
for (const bpa of basePaths) {
const bp = bpa.replace(/\/$/, '');
for (const [path, methods] of Object.entries(apiDoc.paths)) {
for (const [method, schema] of Object.entries(methods)) {
if (
method.startsWith('x-') ||
['parameters', 'summary', 'description'].includes(method)
) {
continue;
}
const pathParams = new Set<string>();
const parameters = [...schema.parameters ?? [], ...methods.parameters ?? []]
for (const param of parameters) {
if (param.in === 'path') {
pathParams.add(param.name);
if (apiDoc.paths) {
for (const [path, methods] of Object.entries(apiDoc.paths)) {
for (const [method, schema] of Object.entries(methods)) {
if (
method.startsWith('x-') ||
['parameters', 'summary', 'description'].includes(method)
) {
continue;
}
const pathParams = new Set<string>();
const parameters = [...schema.parameters ?? [], ...methods.parameters ?? []]
for (const param of parameters) {
if (param.in === 'path') {
pathParams.add(param.name);
}
}
const openApiRoute = `${bp}${path}`;
const expressRoute = `${openApiRoute}`
.split(':')
.map(toExpressParams)
.join('\\:');

routes.push({
basePath: bp,
expressRoute,
openApiRoute,
method: method.toUpperCase(),
pathParams: Array.from(pathParams),
});
}
const openApiRoute = `${bp}${path}`;
const expressRoute = `${openApiRoute}`
.split(':')
.map(toExpressParams)
.join('\\:');

routes.push({
basePath: bp,
expressRoute,
openApiRoute,
method: method.toUpperCase(),
pathParams: Array.from(pathParams),
});
}
}
}
Expand Down
17 changes: 12 additions & 5 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface ValidationSchema extends ParametersSchema {
}

export interface OpenAPIFrameworkInit {
apiDoc: OpenAPIV3.Document;
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
basePaths: string[];
}
export type SecurityHandlers = {
Expand Down Expand Up @@ -109,7 +109,7 @@ export type SerDesMap = {
};

export interface OpenApiValidatorOpts {
apiSpec: OpenAPIV3.Document | string;
apiSpec: OpenAPIV3.DocumentV3 | string;
validateApiSpec?: boolean;
validateResponses?: boolean | ValidateResponseOpts;
validateRequests?: boolean | ValidateRequestOpts;
Expand Down Expand Up @@ -152,7 +152,7 @@ export interface NormalizedOpenApiValidatorOpts extends OpenApiValidatorOpts {
}

export namespace OpenAPIV3 {
export interface Document {
export interface DocumentV3 {
openapi: string;
info: InfoObject;
servers?: ServerObject[];
Expand All @@ -163,6 +163,13 @@ export namespace OpenAPIV3 {
externalDocs?: ExternalDocumentationObject;
}

export interface DocumentV3_1 extends Omit<DocumentV3, 'paths'> {
paths?: DocumentV3['paths']
webhooks: {
[name: string]: PathItemObject | ReferenceObject
}
}

export interface InfoObject {
title: string;
description?: string;
Expand Down Expand Up @@ -474,7 +481,7 @@ export interface OpenAPIFrameworkPathObject {
}

interface OpenAPIFrameworkArgs {
apiDoc: OpenAPIV3.Document | string;
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1 | string;
validateApiSpec?: boolean;
$refParser?: {
mode: 'bundle' | 'dereference';
Expand All @@ -484,7 +491,7 @@ interface OpenAPIFrameworkArgs {
export interface OpenAPIFrameworkAPIContext {
// basePaths: BasePath[];
basePaths: string[];
getApiDoc(): OpenAPIV3.Document;
getApiDoc(): OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
}

export interface OpenAPIFrameworkVisitor {
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/openapi.metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { httpMethods } from './parsers/schema.preprocessor';

export function applyOpenApiMetadata(
openApiContext: OpenApiContext,
responseApiDoc: OpenAPIV3.Document,
responseApiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
): OpenApiRequestHandler {
return (req: OpenApiRequest, res: Response, next: NextFunction): void => {
// note base path is empty when path is fully qualified i.e. req.path.startsWith('')
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/openapi.multipart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { MulterError } from 'multer';
const multer = require('multer');

export function multipart(
apiDoc: OpenAPIV3.Document,
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
options: MultipartOpts,
): OpenApiRequestHandler {
const mult = multer(options.multerOpts);
Expand Down
11 changes: 6 additions & 5 deletions src/middlewares/openapi.request.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { BodySchemaParser } from './parsers/body.parse';
import { ParametersSchemaParser } from './parsers/schema.parse';
import { RequestParameterMutator } from './parsers/req.parameter.mutator';
import { debug } from 'console';

type OperationObject = OpenAPIV3.OperationObject;
type SchemaObject = OpenAPIV3.SchemaObject;
Expand All @@ -31,13 +32,13 @@ type ApiKeySecurityScheme = OpenAPIV3.ApiKeySecurityScheme;

export class RequestValidator {
private middlewareCache: { [key: string]: RequestHandler } = {};
private apiDoc: OpenAPIV3.Document;
private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
private ajv: Ajv;
private ajvBody: Ajv;
private requestOpts: ValidateRequestOpts = {};

constructor(
apiDoc: OpenAPIV3.Document,
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
options: RequestValidatorOptions = {},
) {
this.middlewareCache = {};
Expand Down Expand Up @@ -267,15 +268,15 @@ export class RequestValidator {
}

class Validator {
private readonly apiDoc: OpenAPIV3.Document;
private readonly apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
readonly schemaGeneral: object;
readonly schemaBody: object;
readonly validatorGeneral: ValidateFunction;
readonly validatorBody: ValidateFunction;
readonly allSchemaProperties: ValidationSchema;

constructor(
apiDoc: OpenAPIV3.Document,
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
parametersSchema: ParametersSchema,
bodySchema: BodySchema,
ajv: {
Expand Down Expand Up @@ -329,7 +330,7 @@ class Validator {

class Security {
public static queryParam(
apiDocs: OpenAPIV3.Document,
apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
schema: OperationObject,
): string[] {
const hasPathSecurity = schema.security?.length > 0 ?? false;
Expand Down
4 changes: 2 additions & 2 deletions src/middlewares/openapi.response.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ interface ValidateResult {
}
export class ResponseValidator {
private ajvBody: Ajv;
private spec: OpenAPIV3.Document;
private spec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
private validatorsCache: {
[key: string]: { [key: string]: ValidateFunction };
} = {};
private eovOptions: ValidateResponseOpts;

constructor(
openApiSpec: OpenAPIV3.Document,
openApiSpec: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
options: Options = {},
eovOptions: ValidateResponseOpts = {},
) {
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/openapi.security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface SecurityHandlerResult {
error?: string;
}
export function security(
apiDoc: OpenAPIV3.Document,
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
securityHandlers: SecurityHandlers,
): OpenApiRequestHandler {
return async (req, res, next) => {
Expand Down
4 changes: 2 additions & 2 deletions src/middlewares/parsers/req.parameter.mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ type Schema = ReferenceObject | SchemaObject;
* the request is mutated to accomodate various styles and types e.g. form, explode, deepObject, etc
*/
export class RequestParameterMutator {
private _apiDocs: OpenAPIV3.Document;
private _apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
private path: string;
private ajv: Ajv;
private parsedSchema: ValidationSchema;

constructor(
ajv: Ajv,
apiDocs: OpenAPIV3.Document,
apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
path: string,
parsedSchema: ValidationSchema,
) {
Expand Down
4 changes: 2 additions & 2 deletions src/middlewares/parsers/schema.parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ type Parameter = OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject;
*/
export class ParametersSchemaParser {
private _ajv: Ajv;
private _apiDocs: OpenAPIV3.Document;
private _apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;

constructor(ajv: Ajv, apiDocs: OpenAPIV3.Document) {
constructor(ajv: Ajv, apiDocs: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1) {
this._ajv = ajv;
this._apiDocs = apiDocs;
}
Expand Down
22 changes: 14 additions & 8 deletions src/middlewares/parsers/schema.preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ export const httpMethods = new Set([
]);
export class SchemaPreprocessor {
private ajv: Ajv;
private apiDoc: OpenAPIV3.Document;
private apiDocRes: OpenAPIV3.Document;
private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
private apiDocRes: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
private serDesMap: SerDesMap;
private responseOpts: ValidateResponseOpts;
constructor(
apiDoc: OpenAPIV3.Document,
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
ajvOptions: Options,
validateResponsesOpts: ValidateResponseOpts,
) {
Expand All @@ -108,22 +108,28 @@ export class SchemaPreprocessor {

public preProcess() {
const componentSchemas = this.gatherComponentSchemaNodes();
const r = this.gatherSchemaNodesFromPaths();
let r;

if (this.apiDoc.paths) {
r = this.gatherSchemaNodesFromPaths();
}

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

const schemaNodes = {
schemas: componentSchemas,
requestBodies: r.requestBodies,
responses: r.responses,
requestParameters: r.requestParameters,
requestBodies: r?.requestBodies,
responses: r?.responses,
requestParameters: r?.requestParameters,
};

// Traverse the schemas
this.traverseSchemas(schemaNodes, (parent, schema, opts) =>
if (r) {
this.traverseSchemas(schemaNodes, (parent, schema, opts) =>
this.schemaVisitor(parent, schema, opts),
);
}

return {
apiDoc: this.apiDoc,
Expand Down
Loading

0 comments on commit 432dd0e

Please sign in to comment.