diff --git a/packages/medusa/package.json b/packages/medusa/package.json index d48e90f93c..6639fd2c4f 100644 --- a/packages/medusa/package.json +++ b/packages/medusa/package.json @@ -102,7 +102,8 @@ "scrypt-kdf": "^2.0.1", "ulid": "^2.3.0", "uuid": "^9.0.0", - "winston": "^3.8.2" + "winston": "^3.8.2", + "zod": "^3.22.4" }, "gitHead": "cd1f5afa5aa8c0b15ea957008ee19f1d695cbd2e" } diff --git a/packages/medusa/src/api-v2/utils/__tests__/validate-body.ts b/packages/medusa/src/api-v2/utils/__tests__/validate-body.ts new file mode 100644 index 0000000000..281191b451 --- /dev/null +++ b/packages/medusa/src/api-v2/utils/__tests__/validate-body.ts @@ -0,0 +1,73 @@ +import { z } from "zod" +import { zodValidator } from "../validate-body" + +describe("zodValidator", () => { + it("should validate and return validated", async () => { + const schema = z.object({ + id: z.string(), + name: z.string(), + }) + + const toValidate = { + id: "1", + name: "Tony Stark", + } + + const validated = await zodValidator(schema, toValidate) + + expect(JSON.stringify(validated)).toBe( + JSON.stringify({ + id: "1", + name: "Tony Stark", + }) + ) + }) + + it("should show human readable error message", async () => { + const schema = z + .object({ + id: z.string(), + test: z.object({ + name: z.string(), + test2: z.object({ + name: z.string(), + }), + }), + }) + .strict() + + const toValidate = { + id: "1", + name: "Tony Stark", + company: "Stark Industries", + } + + const errorMessage = await zodValidator(schema, toValidate).catch( + (e) => e.message + ) + + expect(errorMessage).toContain( + "Invalid request body: " + ) + }) + + it("should allow for non-strict parsing", async () => { + const schema = z.object({ + id: z.string(), + }) + + const toValidate = { + id: "1", + name: "Tony Stark", + company: "Stark Industries", + } + + const validated = await zodValidator(schema, toValidate, { strict: false }) + + expect(JSON.stringify(validated)).toBe( + JSON.stringify({ + id: "1", + }) + ) + }) +}) diff --git a/packages/medusa/src/api-v2/utils/validate-body.ts b/packages/medusa/src/api-v2/utils/validate-body.ts new file mode 100644 index 0000000000..cf673eefaf --- /dev/null +++ b/packages/medusa/src/api-v2/utils/validate-body.ts @@ -0,0 +1,48 @@ +import { MedusaError } from "@medusajs/utils" +import { NextFunction } from "express" +import { z, ZodError } from "zod" +import { MedusaRequest, MedusaResponse } from "../../types/routing" + +export async function zodValidator( + zodSchema: z.ZodObject, + body: T, + config: { strict?: boolean } = { strict: true } +): Promise { + try { + let schema = zodSchema + if (config.strict) { + schema = schema.strict() + } + + return await schema.parseAsync(body) + } catch (err) { + if (err instanceof ZodError) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Invalid request body: ${JSON.stringify(err.errors)}` + ) + } + + throw err + } +} + +export function validateAndTransformBody( + zodSchema: z.ZodObject, + config?: { + strict?: boolean + } +): ( + req: MedusaRequest, + res: MedusaResponse, + next: NextFunction +) => Promise { + return async (req: MedusaRequest, _: MedusaResponse, next: NextFunction) => { + try { + req.validatedBody = await zodValidator(zodSchema, req.body, config) + next() + } catch (e) { + next(e) + } + } +} diff --git a/yarn.lock b/yarn.lock index f8c36f5641..57f8db521e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8577,6 +8577,7 @@ __metadata: ulid: ^2.3.0 uuid: ^9.0.0 winston: ^3.8.2 + zod: ^3.22.4 peerDependencies: medusa-interfaces: ^1.3.7 typeorm: ^0.3.16 @@ -52991,7 +52992,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:3.22.4": +"zod@npm:3.22.4, zod@npm:^3.22.4": version: 3.22.4 resolution: "zod@npm:3.22.4" checksum: 7578ab283dac0eee66a0ad0fc4a7f28c43e6745aadb3a529f59a4b851aa10872b3890398b3160f257f4b6817b4ce643debdda4fb21a2c040adda7862cab0a587