Custom Validation
All attribute types support adding custom validation during the parsing step (see the Parser
action for more details).
There are three kinds of validators:
putValidate
: Applied on put actions (e.g.PutItemCommand
)updateValidate
: Applied on update actions (e.g.UpdateItemCommand
)keyValidate
: Overrides other validators on key attributes (ignored otherwise)
The validate
method is a shorthand that acts as keyValidate
on key attributes and putValidate
otherwise.
☝️ In order for the .validate(...)
shorthand to work properly on key attributes, make sure to use it after calling .key()
.
Validators
A custom validator is a function that takes an input (validated by the schema) and returns a boolean
.
For instance, you can make sure that a string
attribute has more than 3 characters like this:
const mySchema = schema({
name: string().validate(
// 🙌 Types are correctly inferred!
name => name.length > 3
)
})
// ❌ Raises a `parsing.customValidationFailed` error
mySchema.build(Parser).parse({ name: 'foo' })
In case of invalid value, you can return a string
to provide more context through the error message:
const mySchema = schema({
name: string().validate(name =>
name.length > 3 ? true : 'Provide a longer name'
)
})
mySchema.build(Parser).parse({ name: 'foo' })
// => ❌ Custom validation for attribute 'name' failed with message: Provide a longer name.
Finally, note that the attribute schema is also passed to the validator:
import type { Attribute } from 'dynamodb-toolbox/attributes'
const validator = (input: unknown, attr: Attribute) => {
... // custom validation here
}
const mySchema = schema({
name: string().validate(validator)
})
Recursive Schemas
Validators are a great way to create recursive schemas:
import { Parser } from 'dynamodb-toolbox/schema/actions/parse'
const isValidBulletList = (bulletList: unknown): boolean =>
bulletListSchema.build(Parser).validate(bulletList)
const bulletListSchema = schema({
title: string(),
subBulletList: any()
.optional()
.validate(isValidBulletList)
})
Actually, you can improve the performances of this code by instanciating a single Parser
:
🔎 Show code
let bulletListParser:
| Parser<typeof bulletListSchema>
| undefined
const isValidBulletList = (
bulletList: unknown
): boolean => {
if (bulletListParser === undefined) {
bulletListParser = bulletListSchema.build(Parser)
}
return bulletListParser.validate(bulletList)
}
const bulletListSchema = schema({
title: string(),
subBulletList: any()
.optional()
.validate(isValidBulletList)
})
In those cases, type inference only works partially as the subBulletList
property is inferred as unknown
.
However, a slight override of the inferred types gets you there:
import type { FormattedValue } from 'dynamodb-toolbox/schema/actions/format'
// 🙌 Works as intended!
type FormattedBulletList = FormattedValue<
typeof bulletListSchema
> & { subBulletList?: FormattedBulletList }