Skip to content

Commit

Permalink
feat(alphabetical): improve validation results (#613)
Browse files Browse the repository at this point in the history
* perf(alphabetical): avoid sorting

* feat(alphabetical): improve validation results
  • Loading branch information
P0lip authored Sep 30, 2019
1 parent a05563c commit d019111
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 44 deletions.
17 changes: 6 additions & 11 deletions src/functions/__tests__/alphabetical.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ describe('alphabetical', () => {
}),
).toEqual([
{
message: 'properties are not in alphabetical order',
message: 'at least 2 properties are not in alphabetical order: "c" should be placed after "b"',
path: ['$', 'c'],
},
]);
});
Expand All @@ -30,7 +31,8 @@ describe('alphabetical', () => {
test('given an unsorted array of strings should return error', () => {
expect(runAlphabetical(['b', 'a'])).toEqual([
{
message: 'properties are not in alphabetical order',
message: 'at least 2 properties are not in alphabetical order: "b" should be placed after "a"',
path: ['$', 0],
},
]);
});
Expand All @@ -42,15 +44,8 @@ describe('alphabetical', () => {
test('given an unsorted array of numbers should return error', () => {
expect(runAlphabetical([10, 1])).toEqual([
{
message: 'properties are not in alphabetical order',
},
]);
});

test('given an unsorted array of numbers should return error', () => {
expect(runAlphabetical([10, 1])).toEqual([
{
message: 'properties are not in alphabetical order',
message: 'at least 2 properties are not in alphabetical order: "10" should be placed after "1"',
path: ['$', 0],
},
]);
});
Expand Down
81 changes: 48 additions & 33 deletions src/functions/alphabetical.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,66 @@
import { isEqual } from 'lodash';

import { IAlphaRuleOptions, IFunction, IFunctionResult } from '../types';
import { isObject } from '../utils';

export const alphabetical: IFunction<IAlphaRuleOptions> = (targetVal, opts) => {
const results: IFunctionResult[] = [];
const compare = (a: unknown, b: unknown): number => {
if ((typeof a === 'number' || Number.isNaN(Number(a))) && (typeof b === 'number' || !Number.isNaN(Number(b)))) {
return Math.min(1, Math.max(-1, Number(a) - Number(b)));
}

if (!targetVal) {
return results;
if (typeof a !== 'string' || typeof b !== 'string') {
return 0;
}

let targetArray: any[] = targetVal;
if (!Array.isArray(targetVal)) {
targetArray = Object.keys(targetVal);
return a.localeCompare(b);
};

const getUnsortedItems = <T>(arr: T[], compareFn: (a: T, B: T) => number): null | [number, number] => {
for (let i = 0; i < arr.length - 1; i += 1) {
if (compareFn(arr[i], arr[i + 1]) >= 1) {
return [i, i + 1];
}
}

// don't mutate original array
const copiedArray = targetArray.slice();
return null;
};

export const alphabetical: IFunction<IAlphaRuleOptions> = (targetVal, opts, paths) => {
const results: IFunctionResult[] = [];

if (copiedArray.length < 2) {
if (!isObject(targetVal)) {
return results;
}

const { keyedBy } = opts;
const targetArray: any[] | string[] = Array.isArray(targetVal) ? targetVal : Object.keys(targetVal);

// If we aren't expecting an object keyed by a specific property, then treat the
// object as a simple array.
if (keyedBy) {
copiedArray.sort((a, b) => {
if (typeof a !== 'object') {
return 0;
}

if (a[keyedBy] < b[keyedBy]) {
return -1;
} else if (a[keyedBy] > b[keyedBy]) {
return 1;
}

return 0;
});
} else {
copiedArray.sort();
if (targetArray.length < 2) {
return results;
}

if (!isEqual(targetArray, copiedArray)) {
const { keyedBy } = opts;

const unsortedItems = getUnsortedItems<unknown>(
targetArray,
keyedBy
? (a, b) => {
if (!isObject(a) || !isObject(b)) return 0;

return compare(a[keyedBy], b[keyedBy]);
}
: // If we aren't expecting an object keyed by a specific property, then treat the
// object as a simple array.
compare,
);

if (unsortedItems != null) {
const path = paths.target || paths.given;

results.push({
message: 'properties are not in alphabetical order',
...(!keyedBy && { path: [...path, Array.isArray(targetVal) ? unsortedItems[0] : targetArray[unsortedItems[0]]] }),
message: keyedBy
? 'properties are not in alphabetical order'
: `at least 2 properties are not in alphabetical order: "${
targetArray[unsortedItems[0]]
}" should be placed after "${targetArray[unsortedItems[1]]}"`,
});
}

Expand Down

0 comments on commit d019111

Please sign in to comment.