diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 88640dc5..bdfbc5c7 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -154,12 +154,11 @@ export class OperationModel implements IMenuItem { ).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options)); if (this.options.sortPropsAlphabetically) { - sortByField(_parameters, 'name'); + return sortByField(_parameters, 'name'); } if (this.options.requiredPropsFirst) { - sortByRequired(_parameters); + return sortByRequired(_parameters); } - return _parameters; } @memoize diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 6b228711..7970be03 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -251,7 +251,7 @@ function buildFields( const props = schema.properties || {}; const additionalProps = schema.additionalProperties; const defaults = schema.default || {}; - const fields = Object.keys(props || []).map(fieldName => { + let fields = Object.keys(props || []).map(fieldName => { let field = props[fieldName]; if (!field) { @@ -280,11 +280,11 @@ function buildFields( }); if (options.sortPropsAlphabetically) { - sortByField(fields, 'name'); + fields = sortByField(fields, 'name'); } if (options.requiredPropsFirst) { // if not sort alphabetically sort in the order from required keyword - sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); + fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); } if (typeof additionalProps === 'object' || additionalProps === true) { diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index 244ab63c..514551f3 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -9,6 +9,7 @@ import { normalizeServers, pluralizeType, serializeParameterValue, + sortByRequired, } from '../'; import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services'; @@ -387,7 +388,9 @@ describe('Utils', () => { expect(pluralizeType('objects (Pet)')).toEqual('objects (Pet)'); expect(pluralizeType('strings ')).toEqual('strings '); expect(pluralizeType('objects or strings')).toEqual('objects or strings'); - expect(pluralizeType('objects (Pet) or numbers ')).toEqual('objects (Pet) or numbers '); + expect(pluralizeType('objects (Pet) or numbers ')).toEqual( + 'objects (Pet) or numbers ', + ); }); }); @@ -621,4 +624,370 @@ describe('Utils', () => { }); }); }); + + describe('OpenAPI sortByRequired', () => { + it('should equal to the old data when all items have no required props', () => { + let fields = [ + { + name: 'loginName', + required: false, + }, + { + name: 'displayName', + required: false, + }, + { + name: 'email', + required: false, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + expect(sortByRequired(fields as FieldModel[])).toEqual(fields); + }); + + it('other item should be the same order when some of items are required', () => { + let fields = [ + { + name: 'loginName', + required: true, + }, + { + name: 'displayName', + required: false, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + let sortedFields = [ + { + name: 'loginName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'displayName', + required: false, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + expect(sortByRequired(fields as FieldModel[])).toEqual(sortedFields); + }); + + it('should the order of required items is as same as the order parameter ', () => { + let fields = [ + { + name: 'loginName', + required: true, + }, + { + name: 'displayName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]; + expect( + sortByRequired(fields as FieldModel[], ['siteId', 'displayName', 'loginName', 'email']), + ).toEqual([ + { + name: 'displayName', + required: true, + }, + { + name: 'loginName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]); + expect(sortByRequired(fields as FieldModel[], ['email', 'displayName'])).toEqual([ + { + name: 'email', + required: true, + }, + { + name: 'displayName', + required: true, + }, + { + name: 'loginName', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]); + + expect(sortByRequired(fields as FieldModel[], ['displayName'])).toEqual([ + { + name: 'displayName', + required: true, + }, + { + name: 'loginName', + required: true, + }, + { + name: 'email', + required: true, + }, + { + name: 'space', + required: false, + }, + { + name: 'type', + required: false, + }, + { + name: 'depIds', + required: false, + }, + { + name: 'depNames', + required: false, + }, + { + name: 'password', + required: false, + }, + { + name: 'pwdControl', + required: false, + }, + { + name: 'csfLevel', + required: false, + }, + { + name: 'priority', + required: false, + }, + { + name: 'siteId', + required: false, + }, + ]); + }); + }); }); diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 2836cfed..854d8e45 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -1,6 +1,7 @@ import { dirname } from 'path'; const URLtemplate = require('url-template'); +import { FieldModel } from '../services/models'; import { OpenAPIParser } from '../services/OpenAPIParser'; import { OpenAPIEncoding, @@ -428,25 +429,30 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] { return res; } -export function sortByRequired( - fields: Array<{ required: boolean; name: string }>, - order: string[] = [], -) { - fields.sort((a, b) => { - if (!a.required && b.required) { - return 1; - } else if (a.required && !b.required) { - return -1; - } else if (a.required && b.required) { - return order.indexOf(a.name) - order.indexOf(b.name); - } else { - return 0; - } +export function sortByRequired(fields: FieldModel[], order: string[] = []) { + const mapped = fields.map((field, index) => { + return { + index, + weights: field.required + ? order.indexOf(field.name) === -1 + ? order.length - fields.length + : order.indexOf(field.name) - fields.length + : index, + }; }); + + mapped.sort((a, b) => { + return a.weights - b.weights || a.index - b.index; + }); + + return mapped.map(el => fields[el.index]); } -export function sortByField(fields: Array<{ [P in T]: string }>, param: T) { - fields.sort((a, b) => { +export function sortByField( + fields: FieldModel[], + param: keyof Pick, +) { + return [...fields].sort((a, b) => { return a[param].localeCompare(b[param]); }); }