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 }