Skip to content

Commit

Permalink
gcanti#434 Handle missing properties correctly
Browse files Browse the repository at this point in the history
A property can only be missing if the property is optional.
Properties with types that accept undefined values must
always be present unless they are optional properties.
  • Loading branch information
leilapearson committed Apr 1, 2020
1 parent dc25290 commit 5914804
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 41 deletions.
43 changes: 31 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ export const type = <P extends Props>(props: P, name: string = getInterfaceTypeN
if (UnknownRecord.is(u)) {
for (let i = 0; i < len; i++) {
const k = keys[i]
if (!types[i].is(u[k])) {
if (!(k in u) || !types[i].is(u[k])) {
return false
}
}
Expand All @@ -814,12 +814,15 @@ export const type = <P extends Props>(props: P, name: string = getInterfaceTypeN
const k = keys[i]
const ak = a[k]
const type = types[i]
const result = type.validate(ak, appendContext(c, k, type, ak))
const result =
ak === undefined && !(k in a)
? failure(ak, appendContext(c, k, type, ak))
: type.validate(ak, appendContext(c, k, type, ak))
if (isLeft(result)) {
pushAll(errors, result.left)
} else {
const vak = result.right
if (vak !== ak || (vak === undefined && !hasOwnProperty.call(a, k))) {
if (vak !== ak || (vak === undefined && !(k in a))) {
/* istanbul ignore next */
if (a === o) {
a = { ...o }
Expand Down
2 changes: 1 addition & 1 deletion test/exact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('exact', () => {

it('should succeed validating an undefined field', () => {
const T = t.exact(t.type({ foo: t.string, bar: t.union([t.string, t.undefined]) }))
assertSuccess(T.decode({ foo: 'foo' }))
assertSuccess(T.decode({ foo: 'foo', bar: undefined }))
})

it('should return the same reference if validation succeeded', () => {
Expand Down
6 changes: 5 additions & 1 deletion test/recursion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,13 @@ describe('recursion', () => {

it('should fail validating an invalid value', () => {
assertFailure(T, 1, ['Invalid value 1 supplied to : T'])
assertFailure(T, {}, ['Invalid value undefined supplied to : T/a: number'])
assertFailure(T, {}, [
'Invalid value undefined supplied to : T/a: number',
'Invalid value undefined supplied to : T/b: (T | undefined | null)'
])
assertFailure(T, { a: 1, b: {} }, [
'Invalid value undefined supplied to : T/b: (T | undefined | null)/0: T/a: number',
'Invalid value undefined supplied to : T/b: (T | undefined | null)/0: T/b: (T | undefined | null)',
'Invalid value {} supplied to : T/b: (T | undefined | null)/1: undefined',
'Invalid value {} supplied to : T/b: (T | undefined | null)/2: null'
])
Expand Down
4 changes: 2 additions & 2 deletions test/strict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('strict', () => {
assert.strictEqual(T.is(undefined), false)
})

it('#423', () => {
it('should allow properties to be satisified by getters - #423', () => {
class A {
get a() {
return 'a'
Expand All @@ -52,7 +52,7 @@ describe('strict', () => {

it('should succeed validating an undefined field', () => {
const T = t.strict({ foo: t.string, bar: t.union([t.string, t.undefined]) })
assertSuccess(T.decode({ foo: 'foo' }))
assertSuccess(T.decode({ foo: 'foo', bar: undefined }))
})

it('should return the same reference if validation succeeded', () => {
Expand Down

0 comments on commit 5914804

Please sign in to comment.