如何自定义Mongoose中非字符串值的CastError错误信息?
你遇到的这个问题我之前也踩过坑!核心原因是Mongoose的执行顺序在搞事情——类型转换(Cast)发生在自定义验证逻辑和pre('validate')钩子之前。当你传入非字符串值时,Mongoose会先尝试把它转成指定的String类型,转换失败直接抛出默认的CastError,这时候你写的validate函数和验证钩子根本没机会运行。而你试的type: [String, 'message']其实是定义字符串数组类型的语法,不是用来自定义Cast错误的,所以完全没效果。
下面给你几个实用的解决方案,按需选就行:
方案1:用字段set函数提前拦截
在字段定义里加个自定义set函数,在值被设置的时候就检查类型,不符合直接抛自定义错误,跳过Mongoose的默认类型转换步骤:
@Prop({ required: [true, 'firstName is required'], trim: true, set: (value: any) => { // 先检查类型,不是字符串直接抛错 if (typeof value !== 'string') { throw new Error('firstName must be a string'); } // 符合要求的话,执行trim后返回 return value.trim(); } }) firstName: string;
这样你调用create传入非字符串firstName时,set函数会先触发,直接抛出你想要的错误,Mongoose不会再走后续的类型转换流程,你的测试就能直接捕获到预期的错误消息。
方案2:全局替换CastError默认消息
如果想统一处理所有字符串字段的CastError,可以在项目启动文件里修改Mongoose的全局错误模板:
import mongoose from 'mongoose'; // 替换字符串类型的CastError默认提示 mongoose.Error.messages.cast.string = '{PATH} must be a string';
这里的{PATH}会自动替换成对应的字段名(比如firstName),之后所有字符串字段转换失败时,都会抛出firstName must be a string这类格式的错误,不用每个字段单独配置。
方案3:测试/业务逻辑中手动修改错误消息
如果只是在测试里需要验证这个场景,可以捕获CastError后手动修改它的message:
it('should throw `firstName must be a string` if not a string', async () => { const USER_WITH_NOT_STRING_FIRSTNAME = { lastName: faker.person.lastName(), photoUrl: faker.image.avatar(), email: faker.internet.email(), firstName: {}, }; try { await UserModel.create(USER_WITH_NOT_STRING_FIRSTNAME); } catch (err) { if (err instanceof mongoose.Error.CastError && err.path === 'firstName') { // 替换成自定义消息 err.message = 'firstName must be a string'; } throw err; } });
这样你的测试断言就能顺利通过了。
方案4:Web框架全局错误中间件处理
如果是在Express/NestJS这类Web框架里,可以写个全局错误处理中间件,自动把CastError转换成友好的自定义消息。比如NestJS的全局异常过滤器:
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common'; import { Response } from 'express'; import { CastError } from 'mongoose'; @Catch(CastError) export class CastErrorFilter implements ExceptionFilter { catch(exception: CastError, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); // 针对不同字段和类型定制提示 let customMessage = `${exception.path} must be a valid ${exception.kind}`; if (exception.path === 'firstName' && exception.kind === 'string') { customMessage = 'firstName must be a string'; } response.status(400).json({ statusCode: 400, message: customMessage, error: 'Bad Request', }); } }
注册这个过滤器后,所有CastError都会被转换成友好的自定义消息返回给客户端。
内容来源于stack exchange

