Skip to content

Instantly share code, notes, and snippets.

@schicks
Created July 18, 2020 15:31
Show Gist options
  • Save schicks/8a037fe7b3b4d1b0ceb505e8afcc9db3 to your computer and use it in GitHub Desktop.
Save schicks/8a037fe7b3b4d1b0ceb505e8afcc9db3 to your computer and use it in GitHub Desktop.
Runtypes constraint function to restrict to known properties
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