diff --git a/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.test.ts b/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.test.ts new file mode 100644 index 0000000..9144ce7 --- /dev/null +++ b/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.test.ts @@ -0,0 +1,17 @@ +import { visitOpenAPIObjects } from './visitOpenAPIObjects' + +describe('visitOpenAPIObjects()', () => { + describe('with invalid OpenAPI document', () => { + test('should not throw', () => { + const openAPI = { + openapi: '3.1.0', + paths: { + '/endpoint': null // not an object + } + } + expect(() => { + visitOpenAPIObjects(openAPI, 'openapi', {}, () => {}) + }).not.toThrow() + }) + }) +}) diff --git a/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.ts b/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.ts index b6a0faa..13bf457 100644 --- a/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.ts +++ b/packages/criteria-openapi/src/specification/v3.1/visitOpenAPIObjects.ts @@ -48,6 +48,14 @@ export type OpenAPIObject = | PathItem | ReferenceType +function isObject(value: any): value is object { + return typeof value === 'object' && value !== null +} + +function isArray(value: any): value is object { + return typeof value === 'object' && value !== null && Array.isArray(value) +} + function appendJSONPointer(path: JSONPointer[], jsonPointer: JSONPointer): JSONPointer[] { return [...path.slice(0, -1), `${path[path.length - 1]}${jsonPointer}`] } @@ -80,6 +88,9 @@ export function visitOpenAPIObjects void ) => { + if (!isObject(map)) { + return false + } for (const [key, value] of Object.entries(map)) { const stop = Boolean(visitor(value, appendJSONPointer(path, `/${escapeReferenceToken(key)}`), states)) if (stop) { @@ -95,6 +106,9 @@ export function visitOpenAPIObjects void ) => { + if (!isArray(list)) { + return false + } for (let index = 0; index < list.length; index++) { const stop = Boolean(visitor(list[index], appendJSONPointer(path, `/${index}`), states)) if (stop) { @@ -105,6 +119,10 @@ export function visitOpenAPIObjects, path: JSONPointer[], states: State[]) => { + if (!isObject(openapi)) { + return false + } + let stop = false if (!stop && openapi.paths) { @@ -121,6 +139,10 @@ export function visitOpenAPIObjects, states: State[]) => { + if (!isObject(paths)) { + return false + } + let stop = false for (const [key, pathItem] of Object.entries(paths)) { @@ -136,6 +158,10 @@ export function visitOpenAPIObjects | ReferenceType, path: JSONPointer[], states: State[]) => { + if (!isObject(pathItem)) { + return false + } + if (seen.has(pathItem)) { return false } @@ -194,6 +220,10 @@ export function visitOpenAPIObjects { + if (!isObject(operation)) { + return false + } + if (seen.has(operation)) { return false } @@ -228,6 +258,10 @@ export function visitOpenAPIObjects | ReferenceType, path: JSONPointer[], states: State[]) => { + if (!isObject(schema) && typeof schema !== 'boolean') { + return false + } + const newState = { ...states[states.length - 1] } states = [...states, newState] @@ -255,6 +289,10 @@ export function visitOpenAPIObjects | ReferenceType, path: JSONPointer[], states: State[]) => { + if (!isObject(response)) { + return false + } + if (seen.has(response)) { return false } @@ -290,6 +328,10 @@ export function visitOpenAPIObjects { + if (!isObject(parameter)) { + return false + } + if (seen.has(parameter)) { return false } @@ -307,7 +349,7 @@ export function visitOpenAPIObjects { + if (!isObject(example)) { + return false + } + if (seen.has(example)) { return false } @@ -342,6 +388,10 @@ export function visitOpenAPIObjects { + if (!isObject(requestBody)) { + return false + } + if (seen.has(requestBody)) { return false } @@ -367,6 +417,10 @@ export function visitOpenAPIObjects | ReferenceType, path: JSONPointer[], states: State[]) => { + if (!isObject(header)) { + return false + } + if (seen.has(header)) { return false } @@ -384,7 +438,7 @@ export function visitOpenAPIObjects { + if (!isObject(securityScheme)) { + return false + } + if (seen.has(securityScheme)) { return false } @@ -419,6 +477,10 @@ export function visitOpenAPIObjects { + if (!isObject(link)) { + return false + } + if (seen.has(link)) { return false } @@ -436,6 +498,10 @@ export function visitOpenAPIObjects | ReferenceType, path: JSONPointer[], states: State[]) => { + if (!isObject(callback)) { + return false + } + if (seen.has(callback)) { return false } @@ -463,6 +529,10 @@ export function visitOpenAPIObjects, path: JSONPointer[], states: State[]) => { + if (!isObject(components)) { + return false + } + let stop = false if (!stop && components.schemas) { stop = visitMap(components.schemas, [...path, '/schemas'], states, visitSchema) @@ -498,6 +568,10 @@ export function visitOpenAPIObjects, path: JSONPointer[], states: State[]) => { + if (!isObject(mediaType)) { + return false + } + let stop = false if (!stop && mediaType.encoding) { stop = visitMap(mediaType.encoding, [...path, '/encoding'], states, visitEncoding) @@ -505,13 +579,17 @@ export function visitOpenAPIObjects, path: JSONPointer[], states: State[]) => { + if (!isObject(encoding)) { + return false + } + let stop = false if (!stop && encoding.headers) { stop = visitMap(encoding.headers, [...path, '/headers'], states, visitHeader)