Skip to content

Commit

Permalink
fix/strip-all-examples-from-openapi-spec
Browse files Browse the repository at this point in the history
  • Loading branch information
max-at-silverflow committed Jan 13, 2025
1 parent 49ffda6 commit 189d624
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 5 deletions.
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 @@ -312,7 +312,7 @@ export namespace OpenAPIV3 {

export interface HeaderObject extends ParameterBaseObject {}

interface ParameterBaseObject {
export interface ParameterBaseObject {
description?: string;
required?: boolean;
deprecated?: boolean;
Expand Down Expand Up @@ -357,7 +357,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

0 comments on commit 189d624

Please sign in to comment.