Browse Source

Extract collection logic into separate utils - fixes #45 (#46)

iss58
Sam Verschueren 7 years ago
committed by Sindre Sorhus
parent
commit
ad4b9d4ac5
  1. 6
      source/index.ts
  2. 81
      source/lib/predicates/map.ts
  3. 4
      source/lib/predicates/predicate.ts
  4. 41
      source/lib/predicates/set.ts
  5. 21
      source/lib/predicates/weak-map.ts
  6. 21
      source/lib/predicates/weak-set.ts
  7. 27
      source/lib/utils/has-items.ts
  8. 21
      source/lib/utils/of-type.ts

6
source/index.ts

@ -156,9 +156,11 @@ export interface Ow {
const main = <T>(value: T, predicate: Predicate<T>) => { const main = <T>(value: T, predicate: Predicate<T>) => {
for (const {validator, message} of predicate[validatorSymbol]) { for (const {validator, message} of predicate[validatorSymbol]) {
if (!validator(value)) { const result = validator(value);
if (typeof result !== 'boolean' || !result) {
// TODO: Modify the stack output to show the original `ow()` call instead of this `throw` statement // TODO: Modify the stack output to show the original `ow()` call instead of this `throw` statement
throw new ArgumentError(message(value), main); throw new ArgumentError(message(value, result), main);
} }
} }
}; };

81
source/lib/predicates/map.ts

@ -1,6 +1,7 @@
import * as isEqual from 'lodash.isequal'; import * as isEqual from 'lodash.isequal';
import ow from '../..';
import {Predicate, Context} from './predicate'; import {Predicate, Context} from './predicate';
import hasItems from '../utils/has-items';
import ofType from '../utils/of-type';
export class MapPredicate extends Predicate<Map<any, any>> { export class MapPredicate extends Predicate<Map<any, any>> {
constructor(context?: Context) { constructor(context?: Context) {
@ -49,25 +50,9 @@ export class MapPredicate extends Predicate<Map<any, any>> {
* @param keys The keys that should be a key in the Map. * @param keys The keys that should be a key in the Map.
*/ */
hasKeys(...keys: any[]) { hasKeys(...keys: any[]) {
const missingKeys: any[] = [];
return this.addValidator({ return this.addValidator({
message: () => `Expected Map to have keys \`${JSON.stringify(missingKeys)}\``, message: (_, missingKeys) => `Expected Map to have keys \`${JSON.stringify(missingKeys)}\``,
validator: map => { validator: map => hasItems(map, keys)
for (const key of keys) {
if (map.has(key)) {
continue;
}
missingKeys.push(key);
if (missingKeys.length === 5) {
return false;
}
}
return missingKeys.length === 0;
}
}); });
} }
@ -89,27 +74,9 @@ export class MapPredicate extends Predicate<Map<any, any>> {
* @param values The values that should be a value in the Map. * @param values The values that should be a value in the Map.
*/ */
hasValues(...values: any[]) { hasValues(...values: any[]) {
const missingValues: any[] = [];
return this.addValidator({ return this.addValidator({
message: () => `Expected Map to have values \`${JSON.stringify(missingValues)}\``, message: (_, missingValues) => `Expected Map to have values \`${JSON.stringify(missingValues)}\``,
validator: map => { validator: map => hasItems(new Set(map.values()), values)
const valueSet = new Set(map.values());
for (const value of values) {
if (valueSet.has(value)) {
continue;
}
missingValues.push(value);
if (missingValues.length === 5) {
return false;
}
}
return missingValues.length === 0;
}
}); });
} }
@ -135,23 +102,9 @@ export class MapPredicate extends Predicate<Map<any, any>> {
* @param predicate The predicate that should be applied against every key in the Map. * @param predicate The predicate that should be applied against every key in the Map.
*/ */
keysOfType<T>(predicate: Predicate<T>) { keysOfType<T>(predicate: Predicate<T>) {
let error: string;
return this.addValidator({ return this.addValidator({
message: () => error, message: (_, error) => error,
validator: map => { validator: map => ofType(map.keys(), predicate)
try {
for (const item of map.keys()) {
ow(item, predicate);
}
return true;
} catch (err) {
error = err.message;
return false;
}
}
}); });
} }
@ -161,23 +114,9 @@ export class MapPredicate extends Predicate<Map<any, any>> {
* @param predicate The predicate that should be applied against every value in the Map. * @param predicate The predicate that should be applied against every value in the Map.
*/ */
valuesOfType<T>(predicate: Predicate<T>) { valuesOfType<T>(predicate: Predicate<T>) {
let error: string;
return this.addValidator({ return this.addValidator({
message: () => error, message: (_, error) => error,
validator: map => { validator: map => ofType(map.values(), predicate)
try {
for (const item of map.values()) {
ow(item, predicate);
}
return true;
} catch (err) {
error = err.message;
return false;
}
}
}); });
} }

4
source/lib/predicates/predicate.ts

@ -5,8 +5,8 @@ import {not} from '../operators/not';
* @hidden * @hidden
*/ */
export interface Validator<T> { export interface Validator<T> {
message(value: T): string; message(value: T, result?: any): string;
validator(value: T): boolean; validator(value: T): any;
} }
/** /**

41
source/lib/predicates/set.ts

@ -1,6 +1,7 @@
import * as isEqual from 'lodash.isequal'; import * as isEqual from 'lodash.isequal';
import ow from '../..';
import {Predicate, Context} from './predicate'; import {Predicate, Context} from './predicate';
import hasItems from '../utils/has-items';
import ofType from '../utils/of-type';
export class SetPredicate extends Predicate<Set<any>> { export class SetPredicate extends Predicate<Set<any>> {
constructor(context?: Context) { constructor(context?: Context) {
@ -49,25 +50,9 @@ export class SetPredicate extends Predicate<Set<any>> {
* @param items The items that should be a item in the Set. * @param items The items that should be a item in the Set.
*/ */
has(...items: any[]) { has(...items: any[]) {
const missingItems: any[] = [];
return this.addValidator({ return this.addValidator({
message: () => `Expected Set to have items \`${JSON.stringify(missingItems)}\``, message: (_, missingItems) => `Expected Set to have items \`${JSON.stringify(missingItems)}\``,
validator: set => { validator: set => hasItems(set, items)
for (const item of items) {
if (set.has(item)) {
continue;
}
missingItems.push(item);
if (missingItems.length === 5) {
return false;
}
}
return missingItems.length === 0;
}
}); });
} }
@ -89,23 +74,9 @@ export class SetPredicate extends Predicate<Set<any>> {
* @param predicate The predicate that should be applied against every item in the Set. * @param predicate The predicate that should be applied against every item in the Set.
*/ */
ofType<T>(predicate: Predicate<T>) { ofType<T>(predicate: Predicate<T>) {
let error: string;
return this.addValidator({ return this.addValidator({
message: () => error, message: (_, error) => error,
validator: set => { validator: set => ofType(set, predicate)
try {
for (const item of set.keys()) {
ow(item, predicate);
}
return true;
} catch (err) {
error = err.message;
return false;
}
}
}); });
} }

21
source/lib/predicates/weak-map.ts

@ -1,4 +1,5 @@
import {Predicate, Context} from './predicate'; import {Predicate, Context} from './predicate';
import hasItems from '../utils/has-items';
export class WeakMapPredicate extends Predicate<WeakMap<any, any>> { export class WeakMapPredicate extends Predicate<WeakMap<any, any>> {
constructor(context?: Context) { constructor(context?: Context) {
@ -11,25 +12,9 @@ export class WeakMapPredicate extends Predicate<WeakMap<any, any>> {
* @param keys The keys that should be a key in the WeakMap. * @param keys The keys that should be a key in the WeakMap.
*/ */
hasKeys(...keys: any[]) { hasKeys(...keys: any[]) {
const missingKeys: any[] = [];
return this.addValidator({ return this.addValidator({
message: () => `Expected WeakMap to have keys \`${JSON.stringify(missingKeys)}\``, message: (_, missingKeys) => `Expected WeakMap to have keys \`${JSON.stringify(missingKeys)}\``,
validator: map => { validator: map => hasItems(map, keys)
for (const key of keys) {
if (map.has(key)) {
continue;
}
missingKeys.push(key);
if (missingKeys.length === 5) {
return false;
}
}
return missingKeys.length === 0;
}
}); });
} }

21
source/lib/predicates/weak-set.ts

@ -1,4 +1,5 @@
import {Predicate, Context} from './predicate'; import {Predicate, Context} from './predicate';
import hasItems from '../utils/has-items';
export class WeakSetPredicate extends Predicate<WeakSet<any>> { export class WeakSetPredicate extends Predicate<WeakSet<any>> {
constructor(context?: Context) { constructor(context?: Context) {
@ -11,25 +12,9 @@ export class WeakSetPredicate extends Predicate<WeakSet<any>> {
* @param items The items that should be a item in the WeakSet. * @param items The items that should be a item in the WeakSet.
*/ */
has(...items: any[]) { has(...items: any[]) {
const missingItems: any[] = [];
return this.addValidator({ return this.addValidator({
message: () => `Expected WeakSet to have items \`${JSON.stringify(missingItems)}\``, message: (_, missingItems) => `Expected WeakSet to have items \`${JSON.stringify(missingItems)}\``,
validator: set => { validator: set => hasItems(set, items)
for (const key of items) {
if (set.has(key)) {
continue;
}
missingItems.push(key);
if (missingItems.length === 5) {
return false;
}
}
return missingItems.length === 0;
}
}); });
} }

27
source/lib/utils/has-items.ts

@ -0,0 +1,27 @@
type Collection = Set<any> | Map<any, any> | WeakSet<any> | WeakMap<any, any>;
/**
* Retrieve the missing values in a collection based on an array of items.
*
* @hidden
* @param source Source collection to search through.
* @param items Items to search for.
* @param maxValues Maximum number of values after the search process is stopped. (Default: 5)
*/
export default (source: Collection, items: any[], maxValues = 5) => {
const missingValues: any[] = [];
for (const value of items) {
if (source.has(value)) {
continue;
}
missingValues.push(value);
if (missingValues.length === maxValues) {
return missingValues;
}
}
return missingValues.length === 0 ? true : missingValues;
};

21
source/lib/utils/of-type.ts

@ -0,0 +1,21 @@
import ow from '../..';
import {Predicate} from '../predicates/predicate';
/**
* Test all the values in the collection against a provided predicate.
*
* @hidden
* @param source Source collection to test.
* @param predicate Predicate to test every item in the source collection against.
*/
export default (source: IterableIterator<any> | Set<any>, predicate: Predicate): boolean | string => {
try {
for (const item of source) {
ow(item, predicate);
}
return true;
} catch (err) {
return err.message;
}
};
Loading…
Cancel
Save