From 2cf4c3cd7b172cd7c02224d56786351cedb48aae Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Mon, 14 May 2018 11:37:19 +0300 Subject: [PATCH] fix: path parameters are not correctly override, fixes #481 --- src/services/OpenAPIParser.ts | 11 ++++++-- src/services/models/Operation.ts | 13 +++++++--- src/utils/__tests__/openapi.test.ts | 39 +++++++++++++++++++++++++++++ src/utils/openapi.ts | 23 ++++++++++++++++- 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 758d3f1d2e..cabf56f52f 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -46,8 +46,8 @@ export class OpenAPIParser { constructor( spec: OpenAPISpec, - specUrl: string | undefined, - private options: RedocNormalizedOptions, + specUrl?: string, + private options: RedocNormalizedOptions = new RedocNormalizedOptions({}), ) { this.validate(spec); this.preprocess(spec); @@ -166,6 +166,13 @@ export class OpenAPIParser { return obj; } + shalowDeref(obj: OpenAPIRef | T): T { + if (this.isRef(obj)) { + return this.byRef(obj.$ref)!; + } + return obj; + } + /** * Merge allOf contsraints. * @param schema schema with allOF diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 9808f62b8a..f6fa04ee34 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -12,6 +12,7 @@ import { getOperationSummary, isAbsolutePath, JsonPointer, + mergeParams, sortByRequired, stripTrailingSlash, } from '../../utils'; @@ -65,7 +66,9 @@ export class OperationModel implements IMenuItem { this.id = operationSpec.operationId !== undefined ? 'operation/' + operationSpec.operationId - : this.parent !== undefined ? this.parent.id + operationSpec._$ref : operationSpec._$ref; + : this.parent !== undefined + ? this.parent.id + operationSpec._$ref + : operationSpec._$ref; this.name = getOperationSummary(operationSpec); this.description = operationSpec.description; @@ -83,9 +86,11 @@ export class OperationModel implements IMenuItem { this.codeSamples = operationSpec['x-code-samples'] || []; this.path = JsonPointer.baseName(this._$ref, 2); - this.parameters = operationSpec.pathParameters - .concat(operationSpec.parameters || []) - .map(paramOrRef => new FieldModel(parser, paramOrRef, this._$ref, options)); + this.parameters = mergeParams( + parser, + operationSpec.pathParameters, + operationSpec.parameters, + ).map(paramOrRef => new FieldModel(parser, paramOrRef, this._$ref, options)); if (options.requiredPropsFirst) { sortByRequired(this.parameters); diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index acdad223c5..36c85e64d9 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -4,8 +4,12 @@ import { getStatusCodeType, isOperationName, isPrimitiveType, + mergeParams, } from '../'; +import { OpenAPIParser } from '../../services'; +import { OpenAPIParameter } from '../../types'; + describe('Utils', () => { describe('openapi getStatusCode', () => { it('Should return info for status codes within 100 and 200', () => { @@ -183,4 +187,39 @@ describe('Utils', () => { expect(isPrimitiveType(schema)).toEqual(false); }); }); + + describe('openapi mergeParams', () => { + it('Should deduplicate params with same "name" and "in"', () => { + const pathParams: OpenAPIParameter[] = [ + { + name: 'param1', + in: 'path', + description: 'path', + }, + { + name: 'param2', + in: 'path', + }, + ]; + const operationParams: OpenAPIParameter[] = [ + { + name: 'param1', + in: 'path', + description: 'oper', + }, + { + name: 'param2', + in: 'query', + }, + ]; + + const parser = new OpenAPIParser({ openapi: '3.0' } as any); + + const res = mergeParams(parser, pathParams, operationParams) as OpenAPIParameter[]; + expect(res).toHaveLength(3); + expect(res[0]).toEqual(pathParams[1]); + expect(res[1]).toEqual(operationParams[0]); + expect(res[2]).toEqual(operationParams[1]); + }); + }); }); diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index bce5efcacf..188e52f840 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -1,4 +1,5 @@ -import { OpenAPIOperation, OpenAPISchema } from '../types'; +import { OpenAPIParser } from '../services/OpenAPIParser'; +import { OpenAPIOperation, OpenAPIParameter, OpenAPISchema, Referenced } from '../types'; export function getStatusCodeType(statusCode: string | number, defaultAsError = false): string { if (statusCode === 'default') { @@ -178,4 +179,24 @@ export function sortByRequired( }); } +export function mergeParams( + parser: OpenAPIParser, + pathParams: Array> = [], + operationParams: Array> = [], +): Array> { + const operationParamNames = {}; + operationParams.forEach(param => { + param = parser.shalowDeref(param); + operationParamNames[param.name + '_' + param.in] = true; + }); + + // filter out path params overriden by operation ones with the same name + pathParams = pathParams.filter(param => { + param = parser.shalowDeref(param); + return !operationParamNames[param.name + '_' + param.in]; + }); + + return pathParams.concat(operationParams); +} + export const SECURITY_SCHEMES_SECTION = 'section/Authentication/';