Skip to content

fix: Remove all OpenAPI examples from spec before validating #1040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/framework/openapi.spec.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
OpenAPIV3,
OpenAPIFrameworkArgs,
} from './types';
import { stripExamples } from './openapi/strip.examples';

export interface Spec {
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
Expand Down Expand Up @@ -103,6 +104,8 @@ export class OpenApiSpecLoader {

routes.sort(sortRoutes);

stripExamples(apiDoc);

serial = serial + 1;
return {
apiDoc,
Expand Down
208 changes: 208 additions & 0 deletions src/framework/openapi/strip.examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { OpenAPIV3 } from '../types';

export function stripExamples(
document: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
): void {
stripExamplesFromPaths(document.paths);
stripExamplesFromComponents(document.components);

if (isDocumentV3_1(document)) {
stripExamplesFromPaths(document.components?.pathItems);
stripExamplesFromPaths(document.webhooks);
}
}

function stripExamplesFromPaths(path?: OpenAPIV3.PathsObject): void {
if (hasNoExamples(path)) return;
forEachValue(path, (pathItem) => stripExamplesFromPathItem(pathItem));
}

function stripExamplesFromComponents(
components?: OpenAPIV3.ComponentsObject,
): void {
if (hasNoExamples(components)) return;

delete components.examples;

stripExamplesFromSchema(components.schemas);
stripExamplesFromResponses(components.responses);
stripExamplesFromHeaders(components.headers);
stripExamplesFromCallbacks(components.callbacks);

forEachValue(components.requestBodies, (requestBody) =>
stripExamplesFromRequestBody(requestBody),
);

if (components.parameters !== undefined) {
stripExamplesFromParameters(
Object.entries(components.parameters).map(
([_key, parameter]) => parameter,
),
);
}
}

function stripExamplesFromPathItem(
pathItem?: OpenAPIV3.ReferenceObject | OpenAPIV3.PathItemObject,
): void {
// Explicitly not checking whether pathItem is a ReferenceObject, as
// there is no way to differentiate them. Attempt to remove all example
// properties either way.
if (pathItem === undefined) return;

['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].forEach(
(method) => {
stripExamplesFromOperation(pathItem[method]);
},
);

if ('parameters' in pathItem) {
stripExamplesFromParameters(pathItem.parameters);
}
}

function stripExamplesFromSchema(
schema?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
): void {
if (hasNoExamples(schema)) return;

if (schema.type !== 'array') {
stripExamplesFromBaseSchema(schema);
return;
}

if ('items' in schema) {
stripExamplesFromSchema(schema.items);
} else {
stripExamplesFromSchema(schema.not);
(['allOf', 'oneOf', 'anyOf'] as const).forEach((property) => {
schema[property].forEach((childObject) =>
stripExamplesFromSchema(childObject),
);
});
}
}

function stripExamplesFromBaseSchema<T>(
baseSchema?: OpenAPIV3.BaseSchemaObject<T>,
): void {
if (hasNoExamples(baseSchema)) return;

if (typeof baseSchema.additionalProperties !== 'boolean') {
stripExamplesFromSchema(baseSchema.additionalProperties);
}

forEachValue(baseSchema.properties, (schema) =>
stripExamplesFromSchema(schema),
);
}

function stripExamplesFromOperation(
operation?: OpenAPIV3.OperationObject,
): void {
if (hasNoExamples(operation)) return;
stripExamplesFromParameters(operation.parameters);
stripExamplesFromRequestBody(operation.requestBody);
stripExamplesFromResponses(operation.responses);
stripExamplesFromCallbacks(operation.callbacks);
}

function stripExamplesFromRequestBody(
requestBody?: OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject,
): void {
if (hasNoExamples(requestBody)) return;
stripExamplesFromContent(requestBody.content);
}

function stripExamplesFromResponses(
responses?: OpenAPIV3.ReferenceObject | OpenAPIV3.ResponsesObject,
): void {
if (hasNoExamples(responses)) return;
forEachValue(responses, (response) => {
if ('$ref' in response) {
return;
}
stripExamplesFromHeaders(response.headers);
stripExamplesFromContent(response.content);
});
}

function stripExamplesFromEncoding(encoding?: OpenAPIV3.EncodingObject): void {
if (hasNoExamples(encoding)) return;
stripExamplesFromHeaders(encoding.headers);
}

function stripExamplesFromHeaders(headers?: {
[header: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.HeaderObject;
}): void {
if (hasNoExamples(headers)) return;
forEachValue(headers, (header) => stripExamplesFromParameterBase(header));
}

function stripExamplesFromContent(content?: {
[media: string]: OpenAPIV3.MediaTypeObject;
}): void {
forEachValue(content, (mediaTypeObject) => {
if (hasNoExamples(mediaTypeObject)) return;

delete mediaTypeObject.example;
delete mediaTypeObject.examples;

stripExamplesFromSchema(mediaTypeObject.schema);
forEachValue(mediaTypeObject.encoding, (encoding) =>
stripExamplesFromEncoding(encoding),
);
});
}

function stripExamplesFromParameters(
parameters?: Array<OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject>,
): void {
if (hasNoExamples(parameters)) return;
parameters.forEach((parameter) => stripExamplesFromParameterBase(parameter));
}

function stripExamplesFromParameterBase(
parameterBase?: OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterBaseObject,
): void {
if (hasNoExamples(parameterBase)) return;

delete parameterBase.example;
delete parameterBase.examples;

stripExamplesFromSchema(parameterBase.schema);
stripExamplesFromContent(parameterBase.content);
}

function stripExamplesFromCallbacks(callbacks?: {
[callback: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.CallbackObject;
}): void {
if (hasNoExamples(callbacks)) return;

forEachValue(callbacks, (callback) => {
if ('$ref' in callback) {
return;
}
stripExamplesFromPaths(callback);
});
}

function isDocumentV3_1(
document: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
): document is OpenAPIV3.DocumentV3_1 {
return document.openapi.startsWith('3.1.');
}

function hasNoExamples<T>(
object: T | OpenAPIV3.ReferenceObject | undefined,
): object is OpenAPIV3.ReferenceObject | undefined {
return object === undefined || '$ref' in object;
}

function forEachValue<Value>(
dictionary: { [key: string]: Value } | undefined,
perform: (value: Value) => void,
): void {
if (dictionary === undefined) return;
Object.entries(dictionary).forEach(([_key, value]) => perform(value));
}
6 changes: 3 additions & 3 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as ajv from 'ajv';
import * as multer from 'multer';
import { FormatsPluginOptions } from 'ajv-formats';
import { Request, Response, NextFunction, RequestHandler } from 'express';
import { Request, Response, NextFunction } from 'express';
import { RouteMetadata } from './openapi.spec.loader';
import AjvDraft4 from 'ajv-draft-04';
import Ajv2020 from 'ajv/dist/2020';
Expand Down Expand Up @@ -301,7 +301,7 @@ export namespace OpenAPIV3 {

export interface HeaderObject extends ParameterBaseObject { }

interface ParameterBaseObject {
export interface ParameterBaseObject {
description?: string;
required?: boolean;
deprecated?: boolean;
Expand Down Expand Up @@ -342,7 +342,7 @@ export namespace OpenAPIV3 {
discriminator?: DiscriminatorObject;
}

interface BaseSchemaObject<T> {
export interface BaseSchemaObject<T> {
// JSON schema allowed properties, adjusted for OpenAPI
type?: T;
title?: string;
Expand Down
2 changes: 0 additions & 2 deletions src/middlewares/openapi.request.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export class RequestValidator {
) {
this.middlewareCache = {};
this.apiDoc = apiDoc;
// Examples not needed for validation
delete this.apiDoc.components?.examples;
this.requestOpts.allowUnknownQueryParameters =
options.allowUnknownQueryParameters;

Expand Down
Loading