diff --git a/src/index.ts b/src/index.ts index ad9b84b..d434cb3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,9 +31,9 @@ export function compile (rootSchema: Types.Any) { } return test(root, [], context) - .then( - () => root, - () => Promise.reject(new MultiError(errors)) - ) + .then(() => root) + .catch((error) => { + return Promise.reject(errors.length ? new MultiError(errors) : error) + }) } } diff --git a/src/support/promises.ts b/src/support/promises.ts index 72bad91..a794611 100644 --- a/src/support/promises.ts +++ b/src/support/promises.ts @@ -36,7 +36,7 @@ export function promiseEvery (fns: Array<() => Promise>): Promise { // Handle each of the function results. function handle (result: Promise, index: number) { - return result.then( + return Promise.resolve(result).then( function (result) { results[index] = result diff --git a/src/test/object.spec.ts b/src/test/object.spec.ts index fa96d3f..ee13eb6 100644 --- a/src/test/object.spec.ts +++ b/src/test/object.spec.ts @@ -25,4 +25,28 @@ test('object', t => { }) }) }) + + t.test('patterns', t => { + const schema = new Types.Object({ + patterns: { + '^[0-9]+$': new Types.String() + } + }) + + const validate = compile(schema) + + t.test('accept valid patterns', t => { + return validate({ '123': '123' }) + }) + + t.test('error on bad pattern properties', t => { + t.plan(2) + + return validate({ '123': 123 }) + .catch(err => { + t.equal(err.errors.length, 1) + t.deepEqual(err.errors[0].path, ['123']) + }) + }) + }) }) diff --git a/src/types/object.ts b/src/types/object.ts index aa24d4f..2450b63 100644 --- a/src/types/object.ts +++ b/src/types/object.ts @@ -1,9 +1,10 @@ import { Any, AnyOptions } from './any' import { promiseEvery } from '../support/promises' -import { allowEmpty, ValidationContext, wrap } from '../support/test' +import { allowEmpty, ValidationContext, wrap, TestFn } from '../support/test' export interface ObjectOptions extends AnyOptions { properties?: ObjectProperties + patterns?: ObjectProperties } export interface ObjectProperties { @@ -14,6 +15,7 @@ export class Object extends Any { type = 'object' properties: ObjectProperties = {} + patterns: ObjectProperties = {} constructor (options: ObjectOptions) { super(options) @@ -22,8 +24,12 @@ export class Object extends Any { this.properties = options.properties } + if (options.patterns != null) { + this.patterns = options.patterns + } + this._tests.push(allowEmpty(isObject)) - this._tests.push(allowEmpty(toPropertiesTest(this.properties))) + this._tests.push(allowEmpty(toPropertiesTest(this.properties, this.patterns))) } } @@ -42,18 +48,38 @@ function isObject (value: any, path: string[], context: ValidationContext) { /** * Test all properties in an object definition. */ -function toPropertiesTest (properties: ObjectProperties) { - const keys = global.Object.keys(properties) - const propertyTests = zip(keys, keys.map(key => wrap(properties[key]))) +function toPropertiesTest (properties: ObjectProperties, patterns: ObjectProperties) { + const patternTests = global.Object.keys(patterns) + .map<[RegExp, TestFn]>(key => { + return [new RegExp(key), wrap(patterns[key])] + }) + + const propertyTests = global.Object.keys(properties) + .map<[string, TestFn]>(key => { + return [key, wrap(properties[key])] + }) return function (object: any, path: string[], context: ValidationContext) { + const keys = global.Object.keys(object) + // TODO(blakeembrey): Validate _all_ keys when intersection is corrected. return promiseEvery(keys.map(function (key) { return function () { - const test = propertyTests[key] const value = object[key] - return test(value, path.concat(key), context) + for (const [prop, test] of propertyTests) { + if (prop === key) { + return test(value, path.concat(key), context) + } + } + + for (const [match, test] of patternTests) { + if (match.test(key)) { + return test(value, path.concat(key), context) + } + } + + // TODO: Throw an error when property does not match. } })).then((values) => zip(keys, values)) }