Skip to content

Commit

Permalink
Add support for object property patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed May 24, 2016
1 parent 6a1bffc commit 06d5833
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 12 deletions.
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
2 changes: 1 addition & 1 deletion src/support/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function promiseEvery <T> (fns: Array<() => Promise<T>>): Promise<T[]> {

// Handle each of the function results.
function handle (result: Promise<any>, index: number) {
return result.then(
return Promise.resolve(result).then(
function (result) {
results[index] = result

Expand Down
24 changes: 24 additions & 0 deletions src/test/object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
})
})
})
})
40 changes: 33 additions & 7 deletions src/types/object.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -14,6 +15,7 @@ export class Object extends Any {

type = 'object'
properties: ObjectProperties = {}
patterns: ObjectProperties = {}

constructor (options: ObjectOptions) {
super(options)
Expand All @@ -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)))
}

}
Expand All @@ -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<any>]>(key => {
return [new RegExp(key), wrap(patterns[key])]
})

const propertyTests = global.Object.keys(properties)
.map<[string, TestFn<any>]>(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))
}
Expand Down

0 comments on commit 06d5833

Please sign in to comment.