Validate TypeScript CDK stack with Zod
k.goto
Posted on June 18, 2023
I have tried to validate CDK stacks written in TypeScript with a validation tool called Zod.
Assumptions
I am using CDK with TypeScript.
I also use v2 CDK.
❯ cdk --version
2.31.0 (build b67950d)
What is Zod?
It is a TypeScript validation tool, see GitHub.
Zod is a tool that allows you to perform validation based on defined rules at the timing of parsing object information, such as requests, into objects of a defined type.
You can write validation rules in the same file as the type definition, so it is very easy to perform validation.
import { z } from "zod";
export const RequestSchema = z.object({
title: z.number(),
message: z.string().min(1),
mailAddress: z.string().email().min(1),
tags: z.array(z.string().min(1)).optional(),
});
export type Request = z.infer<typeof RequestSchema>;
// Validation is performed here at the same time.
const request: Request = RequestSchema.parse(body);
As above, not only type, optional (.optional()
), and empty character check (.min(1)
), but also email format handling (.email()
) can be performed.
In case of a validation error, the following error is output.
{
"issues": [
{
"validation": "email",
"code": "invalid_string",
"message": "Invalid email",
"path": [
"mailAddress"
]
}
],
"name": "ZodError"
}
Code Examples
Let's start with a code example.
I will take the three main files as examples.
- Zod schema
- Stack class
- Validator class
If you implement validation with the following code, the defined validation will be performed at cdk synth
time.
Zod Schema
First, define the parameters you want to validate as a Zod schema and define the validation rules by Zod for the properties of the schema.
Also, by using z.infer<typeof StackInputSchema>
, you can define a type from the schema, which is useful to use as a type when dealing with stack classes and validator classes.
import { z } from "zod";
export const StackInputSchema = z.object({
topicName: z.string().min(1),
mailAddress: z.string().email().min(1),
});
export type StackInput = z.infer<typeof StackInputSchema>;
Stack class
This is a simple example. The init()
is used to validate and the create()
is used to create the resource.
The StackInput
type defined earlier is used to define a variable for validation, which is then passed directly to the constructor of the validator.
export class SampleStack extends Stack {
private stackInput: StackInput;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
this.init();
this.create();
}
private init(): void {
this.stackInput = {
topicName: props.config.topicName, // Pass parameters from outside the stack via StackProps
mailAddress: props.config.mailAddress,
};
const stackValidator = new StackValidator(this.stackInput);
this.node.addValidation(stackValidator);
}
private create() {
// ...
In init()
, create an instance of the validator class, pass the information to be validated in the constructor, and pass the instance to the method this.node.addValidation
.
This is the validation method provided by CDK, which allows multiple validations to be registered, and the validation is run during cdk synth.
Validator class
This is a class to write a concrete validation process, and it performs the validation process with Zod in validate()
.
By cutting out from the stack class as a validator class like this, if you stop using Zod and change the validation method, you do not have to modify the stack class itself, which has the advantage of making it more maintainable.
import { IValidation } from "constructs";
import { StackInput, StackInputSchema } from "../types/stack-input";
export class StackValidator implements IValidation {
private stackInput: StackInput;
constructor(stackInput: StackInput) {
this.stackInput = stackInput;
}
public validate(): string[] {
const errors: string[] = [];
try {
StackInputSchema.parse(this.stackInput);
} catch (e) {
errors.push(JSON.stringify(e));
}
return errors;
}
}
By passing the object to be validated to the parse method of the schema defined in the Zod schema file described below, the parsing and validation are performed together.
If successful, the object will be stored in a variable of the type you also define, and if unsuccessful, a Zod validation error will be thrown.
StackInputSchema.parse(this.stackInput);
Validation run
Now, let's quickly run cdk synth
with the email address intentionally formatted improperly (abcde.fgh
).
/Users/goto/github/mail-queues/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:2
`);throw new Error(`Validation failed with the following errors:
^
Error: Validation failed with the following errors:
[SampleStack] {"issues":[{"validation":"email","code":"invalid_string","message":"Invalid email","path":["senderAddress"]}],"name":"ZodError"}
at validateTree (/Users/goto/github/mail-queues/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:2:12)
at Object.synthesize (/Users/goto/github/mail-queues/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:1:598)
at App.synth (/Users/goto/github/mail-queues/node_modules/aws-cdk-lib/core/lib/stage.js:1:1866)
at process.<anonymous> (/Users/goto/github/mail-queues/node_modules/aws-cdk-lib/core/lib/app.js:1:1164)
at Object.onceWrapper (events.js:520:26)
at process.emit (events.js:400:28)
at process.emit (domain.js:475:12)
at process.emit.sharedData.processEmitHook.installedValue [as emit] (/Users/goto/github/mail-queues/node_modules/@cspotcode/source-map-support/source-map-support.js:745:40)
Subprocess exited with error 1
As defined in Zod's schema, it caught the email validation and output an error.
Finally
I wrote an article on validation with CDK last time, and this time I put more focus on TypeScript and included Zod for convenience.
After all, it is easy to leave validation to a framework.
Posted on June 18, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.