diff --git a/.changeset/happy-points-yawn.md b/.changeset/happy-points-yawn.md new file mode 100644 index 0000000000..3227cf4227 --- /dev/null +++ b/.changeset/happy-points-yawn.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +feat(medusa): Allow to register extended validators seemlesly diff --git a/packages/medusa/src/utils/__tests__/validator.spec.ts b/packages/medusa/src/utils/__tests__/validator.spec.ts new file mode 100644 index 0000000000..00c1162ea8 --- /dev/null +++ b/packages/medusa/src/utils/__tests__/validator.spec.ts @@ -0,0 +1,40 @@ +import { IsString } from "class-validator" +import { AdminPostProductsReq as MedusaAdminPostProductsReq } from "../../api/routes/admin/products/create-product" +import { registerOverriddenValidators, validator } from "../validator" + +class AdminPostProductsReq extends MedusaAdminPostProductsReq { + @IsString() + custom_attribute: string +} + +describe("Validator", function () { + it("should override the original validator", async function () { + let err = await validator(MedusaAdminPostProductsReq, { + title: "test", + }) + .then(() => void 0) + .catch((err) => err) + + expect(err).not.toBeDefined() + + registerOverriddenValidators(AdminPostProductsReq) + + err = await validator(MedusaAdminPostProductsReq, { + title: "test", + }) + .then(() => void 0) + .catch((err) => err) + + expect(err).toBeDefined() + expect(err.message).toEqual("custom_attribute must be a string") + + err = await validator(MedusaAdminPostProductsReq, { + title: "test", + custom_attribute: "test", + }) + .then(() => void 0) + .catch((err) => err) + + expect(err).not.toBeDefined() + }) +}) diff --git a/packages/medusa/src/utils/index.ts b/packages/medusa/src/utils/index.ts index ed84e7a9d0..497d5d3767 100644 --- a/packages/medusa/src/utils/index.ts +++ b/packages/medusa/src/utils/index.ts @@ -13,3 +13,4 @@ export * from "./product-category" export * from "./remove-undefined-properties" export * from "./set-metadata" export * from "./validate-id" +export { registerOverriddenValidators } from "./validator" diff --git a/packages/medusa/src/utils/validator.ts b/packages/medusa/src/utils/validator.ts index 802fb9ba30..a2030e8d7b 100644 --- a/packages/medusa/src/utils/validator.ts +++ b/packages/medusa/src/utils/validator.ts @@ -1,6 +1,34 @@ import { ClassConstructor, plainToInstance } from "class-transformer" import { validate, ValidationError, ValidatorOptions } from "class-validator" import { MedusaError } from "medusa-core-utils" +import { Constructor } from "@medusajs/types" + +const extendedValidators: Map> = new Map() + +/** + * When overriding a validator, you can register it to be used instead of the original one. + * For example, the place where you are overriding the core validator, you can call this function + * @example + * ```ts + * // /src/api/routes/admin/products/create-product.ts + * import { registerOverriddenValidators } from "@medusajs/medusa" + * import { AdminPostProductsReq as MedusaAdminPostProductsReq } from "@medusajs/medusa/dist/api/routes/admin/products/create-product" + * import { IsString } from "class-validator" + * + * class AdminPostProductsReq extends MedusaAdminPostProductsReq { + * @IsString() + * test: string + * } + * + * registerOverriddenValidators(AdminPostProductsReq) + * ``` + * @param extendedValidator + */ +export function registerOverriddenValidators( + extendedValidator: Constructor +): void { + extendedValidators.set(extendedValidator.name, extendedValidator) +} const reduceErrorMessages = (errs: ValidationError[]): string[] => { return errs.reduce((acc: string[], next) => { @@ -22,6 +50,8 @@ export async function validator( plain: V, config: ValidatorOptions = {} ): Promise { + typedClass = extendedValidators.get(typedClass.name) ?? typedClass + const toValidate = plainToInstance(typedClass, plain) // @ts-ignore const errors = await validate(toValidate, {