Created
July 18, 2020 15:31
-
-
Save schicks/8a037fe7b3b4d1b0ceb505e8afcc9db3 to your computer and use it in GitHub Desktop.
Runtypes constraint function to restrict to known properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Record, String, Number, Runtype, Lazy, Array} from 'runtypes' | |
const noAdditionalProperties = < | |
T, | |
R extends Runtype<T> | |
>(rt: R) => (t: T): boolean | string => { | |
const reflected = rt.reflect | |
switch (reflected.tag) { | |
case 'record': { | |
return Object.entries(t).every(([key, value]) => { | |
return key in reflected.fields | |
? noAdditionalProperties(reflected.fields[key])(value) === true | |
: false | |
}) || 'object contains additional properties' | |
} | |
case 'constraint': { | |
return noAdditionalProperties(reflected.underlying)(t) | |
} | |
case 'array': { | |
if (Array(reflected.element).guard(t)) { | |
return t.every( | |
el => noAdditionalProperties(reflected.element)(el) === true | |
) || 'some element contains additional properties' | |
} else return 'Not a valid array' | |
} | |
case 'string': | |
case 'boolean': | |
case 'number': return true | |
default: return `properties constraint not supported for tag: ${reflected.tag}` | |
} | |
} | |
describe('noAdditionalProperties constraint', () => { | |
type Character = {name: string, age: number} | |
const DTO: Runtype<Character> = Record({ | |
name: String, | |
age: Number | |
}).withConstraint(noAdditionalProperties(Lazy(() => DTO))) | |
const MrBill = {name: 'mr bill', age: 44, socialSecurity: 123456789} | |
test('records/constrained records', () => { | |
const {name, age } = MrBill | |
DTO.check({name, age}) | |
expect(DTO.guard(MrBill)).toBe(false) | |
}) | |
test('nested records', () => { | |
const nestedDTO: Runtype<{ | |
show: string | |
cast: Character[] | |
}> = Record({ | |
show: String, | |
cast: Array(Record({ | |
name: String, | |
age: Number | |
})) | |
}).withConstraint(noAdditionalProperties(Lazy(() => nestedDTO))) | |
const {name, age} = MrBill | |
nestedDTO.check({ | |
show: 'SNL', | |
cast: [{name, age}] | |
}) | |
expect(nestedDTO.guard({show: 'SNL', cast: [MrBill]})).toBe(false) | |
}) | |
test('arrays', () => { | |
const {name, age} = MrBill | |
const ArrayDTO: Runtype<Character[]> = Array(DTO) | |
.withConstraint(noAdditionalProperties(Lazy(() => ArrayDTO))) | |
ArrayDTO.check([{name, age}]) | |
expect(ArrayDTO.guard([MrBill])).toBe(false) | |
}) | |
}) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment