Skip to content

Commit 189d624

Browse files
fix/strip-all-examples-from-openapi-spec
1 parent 49ffda6 commit 189d624

File tree

5 files changed

+357
-5
lines changed

5 files changed

+357
-5
lines changed

src/framework/openapi.spec.loader.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
OpenAPIV3,
66
OpenAPIFrameworkArgs,
77
} from './types';
8+
import { stripExamples } from './openapi/strip.examples';
89

910
export interface Spec {
1011
apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
@@ -103,6 +104,8 @@ export class OpenApiSpecLoader {
103104

104105
routes.sort(sortRoutes);
105106

107+
stripExamples(apiDoc);
108+
106109
serial = serial + 1;
107110
return {
108111
apiDoc,
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { OpenAPIV3 } from '../types';
2+
3+
export function stripExamples(
4+
document: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
5+
): void {
6+
stripExamplesFromPaths(document.paths);
7+
stripExamplesFromComponents(document.components);
8+
9+
if (isDocumentV3_1(document)) {
10+
stripExamplesFromPaths(document.components?.pathItems);
11+
stripExamplesFromPaths(document.webhooks);
12+
}
13+
}
14+
15+
function stripExamplesFromPaths(path?: OpenAPIV3.PathsObject): void {
16+
if (hasNoExamples(path)) return;
17+
forEachValue(path, (pathItem) => stripExamplesFromPathItem(pathItem));
18+
}
19+
20+
function stripExamplesFromComponents(
21+
components?: OpenAPIV3.ComponentsObject,
22+
): void {
23+
if (hasNoExamples(components)) return;
24+
25+
delete components.examples;
26+
27+
stripExamplesFromSchema(components.schemas);
28+
stripExamplesFromResponses(components.responses);
29+
stripExamplesFromHeaders(components.headers);
30+
stripExamplesFromCallbacks(components.callbacks);
31+
32+
forEachValue(components.requestBodies, (requestBody) =>
33+
stripExamplesFromRequestBody(requestBody),
34+
);
35+
36+
if (components.parameters !== undefined) {
37+
stripExamplesFromParameters(
38+
Object.entries(components.parameters).map(
39+
([_key, parameter]) => parameter,
40+
),
41+
);
42+
}
43+
}
44+
45+
function stripExamplesFromPathItem(
46+
pathItem?: OpenAPIV3.ReferenceObject | OpenAPIV3.PathItemObject,
47+
): void {
48+
// Explicitly not checking whether pathItem is a ReferenceObject, as
49+
// there is no way to differentiate them. Attempt to remove all example
50+
// properties either way.
51+
if (pathItem === undefined) return;
52+
53+
['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].forEach(
54+
(method) => {
55+
stripExamplesFromOperation(pathItem[method]);
56+
},
57+
);
58+
59+
if ('parameters' in pathItem) {
60+
stripExamplesFromParameters(pathItem.parameters);
61+
}
62+
}
63+
64+
function stripExamplesFromSchema(
65+
schema?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,
66+
): void {
67+
if (hasNoExamples(schema)) return;
68+
69+
if (schema.type !== 'array') {
70+
stripExamplesFromBaseSchema(schema);
71+
return;
72+
}
73+
74+
if ('items' in schema) {
75+
stripExamplesFromSchema(schema.items);
76+
} else {
77+
stripExamplesFromSchema(schema.not);
78+
(['allOf', 'oneOf', 'anyOf'] as const).forEach((property) => {
79+
schema[property].forEach((childObject) =>
80+
stripExamplesFromSchema(childObject),
81+
);
82+
});
83+
}
84+
}
85+
86+
function stripExamplesFromBaseSchema<T>(
87+
baseSchema?: OpenAPIV3.BaseSchemaObject<T>,
88+
): void {
89+
if (hasNoExamples(baseSchema)) return;
90+
91+
if (typeof baseSchema.additionalProperties !== 'boolean') {
92+
stripExamplesFromSchema(baseSchema.additionalProperties);
93+
}
94+
95+
forEachValue(baseSchema.properties, (schema) =>
96+
stripExamplesFromSchema(schema),
97+
);
98+
}
99+
100+
function stripExamplesFromOperation(
101+
operation?: OpenAPIV3.OperationObject,
102+
): void {
103+
if (hasNoExamples(operation)) return;
104+
stripExamplesFromParameters(operation.parameters);
105+
stripExamplesFromRequestBody(operation.requestBody);
106+
stripExamplesFromResponses(operation.responses);
107+
stripExamplesFromCallbacks(operation.callbacks);
108+
}
109+
110+
function stripExamplesFromRequestBody(
111+
requestBody?: OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject,
112+
): void {
113+
if (hasNoExamples(requestBody)) return;
114+
stripExamplesFromContent(requestBody.content);
115+
}
116+
117+
function stripExamplesFromResponses(
118+
responses?: OpenAPIV3.ReferenceObject | OpenAPIV3.ResponsesObject,
119+
): void {
120+
if (hasNoExamples(responses)) return;
121+
forEachValue(responses, (response) => {
122+
if ('$ref' in response) {
123+
return;
124+
}
125+
stripExamplesFromHeaders(response.headers);
126+
stripExamplesFromContent(response.content);
127+
});
128+
}
129+
130+
function stripExamplesFromEncoding(encoding?: OpenAPIV3.EncodingObject): void {
131+
if (hasNoExamples(encoding)) return;
132+
stripExamplesFromHeaders(encoding.headers);
133+
}
134+
135+
function stripExamplesFromHeaders(headers?: {
136+
[header: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.HeaderObject;
137+
}): void {
138+
if (hasNoExamples(headers)) return;
139+
forEachValue(headers, (header) => stripExamplesFromParameterBase(header));
140+
}
141+
142+
function stripExamplesFromContent(content?: {
143+
[media: string]: OpenAPIV3.MediaTypeObject;
144+
}): void {
145+
forEachValue(content, (mediaTypeObject) => {
146+
if (hasNoExamples(mediaTypeObject)) return;
147+
148+
delete mediaTypeObject.example;
149+
delete mediaTypeObject.examples;
150+
151+
stripExamplesFromSchema(mediaTypeObject.schema);
152+
forEachValue(mediaTypeObject.encoding, (encoding) =>
153+
stripExamplesFromEncoding(encoding),
154+
);
155+
});
156+
}
157+
158+
function stripExamplesFromParameters(
159+
parameters?: Array<OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject>,
160+
): void {
161+
if (hasNoExamples(parameters)) return;
162+
parameters.forEach((parameter) => stripExamplesFromParameterBase(parameter));
163+
}
164+
165+
function stripExamplesFromParameterBase(
166+
parameterBase?: OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterBaseObject,
167+
): void {
168+
if (hasNoExamples(parameterBase)) return;
169+
170+
delete parameterBase.example;
171+
delete parameterBase.examples;
172+
173+
stripExamplesFromSchema(parameterBase.schema);
174+
stripExamplesFromContent(parameterBase.content);
175+
}
176+
177+
function stripExamplesFromCallbacks(callbacks?: {
178+
[callback: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.CallbackObject;
179+
}): void {
180+
if (hasNoExamples(callbacks)) return;
181+
182+
forEachValue(callbacks, (callback) => {
183+
if ('$ref' in callback) {
184+
return;
185+
}
186+
stripExamplesFromPaths(callback);
187+
});
188+
}
189+
190+
function isDocumentV3_1(
191+
document: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1,
192+
): document is OpenAPIV3.DocumentV3_1 {
193+
return document.openapi.startsWith('3.1.');
194+
}
195+
196+
function hasNoExamples<T>(
197+
object: T | OpenAPIV3.ReferenceObject | undefined,
198+
): object is OpenAPIV3.ReferenceObject | undefined {
199+
return object === undefined || '$ref' in object;
200+
}
201+
202+
function forEachValue<Value>(
203+
dictionary: { [key: string]: Value } | undefined,
204+
perform: (value: Value) => void,
205+
): void {
206+
if (dictionary === undefined) return;
207+
Object.entries(dictionary).forEach(([_key, value]) => perform(value));
208+
}

src/framework/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ajv from 'ajv';
22
import * as multer from 'multer';
33
import { FormatsPluginOptions } from 'ajv-formats';
4-
import { Request, Response, NextFunction, RequestHandler } from 'express';
4+
import { Request, Response, NextFunction } from 'express';
55
import { RouteMetadata } from './openapi.spec.loader';
66
import AjvDraft4 from 'ajv-draft-04';
77
import Ajv2020 from 'ajv/dist/2020';
@@ -312,7 +312,7 @@ export namespace OpenAPIV3 {
312312

313313
export interface HeaderObject extends ParameterBaseObject {}
314314

315-
interface ParameterBaseObject {
315+
export interface ParameterBaseObject {
316316
description?: string;
317317
required?: boolean;
318318
deprecated?: boolean;
@@ -357,7 +357,7 @@ export namespace OpenAPIV3 {
357357
discriminator?: DiscriminatorObject;
358358
}
359359

360-
interface BaseSchemaObject<T> {
360+
export interface BaseSchemaObject<T> {
361361
// JSON schema allowed properties, adjusted for OpenAPI
362362
type?: T;
363363
title?: string;

src/middlewares/openapi.request.validator.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ export class RequestValidator {
4242
) {
4343
this.middlewareCache = {};
4444
this.apiDoc = apiDoc;
45-
// Examples not needed for validation
46-
delete this.apiDoc.components?.examples;
4745
this.requestOpts.allowUnknownQueryParameters =
4846
options.allowUnknownQueryParameters;
4947

0 commit comments

Comments
 (0)