我有一个基本的 Express 应用程序。提供基于令牌的身份验证。处理 zod 以创建模式来验证传入数据。
说我有两个模式:
Zod 允许我们根据我们的模式推断类型,例如:
type SchemaBasedType = z.infer<typeof schmea>
根据我的模式,我有两种类型:CreateUserRequest和LoginUserRequest。
首先像这样创建验证中间件:
export const validateRequest =
<T extends ZodTypeAny>(schema: T): RequestHandler =>
async (req, res, next) => {
try {
const userRequestData: Record<string, unknown> = req.body;
const validationResult = (await schema.spa(userRequestData)) as z.infer<T>;
if (!validationResult.success) {
throw new BadRequest(fromZodError(validationResult.error).toString());
}
req.payload = validationResult.data;
next();
} catch (error: unknown) {
next(error);
}
};
如上所述,该中间件接受根据 zod 文档键入的模式参数。 在我看来,使用“payload”属性扩展请求对象是一个很好的决定,我可以在其中放置有效数据。
然后问题就开始了。 TS 不知道有效负载实际上是什么。这就是声明合并即将到来的地方。 首先,尝试这样的事情很诱人:
declare global {
namespace Express {
export interface Request {
payload?: any;
}
}
}
但这似乎不是一个好主意,因为我们确切地知道我们的有效负载签名是什么。 然后我尝试了基于 zod 类型的联合类型:
payload?: CreateUserRequest | LoginUserRequest;
通过这种方法,我发现了一个错误,一些更窄类型的字段在其他类型中不存在;
然后我尝试使用通用,
declare global {
namespace Express {
export interface Request<T> {
payload?: T;
}
}
}
这似乎是一个解决方案,但是 Request 接口已经有 5 个通用参数:
interface Request<
P = ParamsDictionary,
ResBody = any,
ReqBody = any,
ReqQuery = ParsedQs,
LocalsObj extends Record<string, any> = Record<string, any>
我什至无法想象它应该如何合并,这意味着我的通用参数将是第一个,还是最后一个? 无论如何,这似乎是一种错误的方式,因为毕竟我没有通过 IDE 提示看到扩展接口。 在堆栈的某个地方我遇到了这种方法:
declare global {
namespace Express {
export interface Request<
Payload = any,
P = ParamsDictionary,
ResBody = any,
ReqBody = any,
ReqQuery = ParsedQs,
LocalsObj extends Record<string, any> = Record<string, any>
> {
payload?: Payload;
}
}
}
它甚至在鼠标引导后给了我一个提示,但我不确定 any 是一个好的类型,因为我们已经有了由 zod 推断的类型。如果未指定 Payload = any,我不会收到类型提示。
我没有想法和堆栈,因为我不是 ts 和后端架构方面的专家;
最后,我想要得到这样的东西:
authRouter.post("/register", validateRequest(createUserSchema), AuthController.register);
编译器知道有效负载签名等于 CreateUserRequest
authRouter.post("/login", validateRequest(loginUserSchema), AuthController.login)
;
编译器知道有效负载签名等于 LoginUserRequest
我应该在哪里正确指定我的预期类型以及如何处理它们?
花了时间寻找解决方案,得出以下结论:
正如@jcalz提到的,我们不能让通用接口变得更通用;
我所有尝试根据传递的模式明确输入有效负载属性,而不使用通用参数,例如:
type Parse =(模式:T,数据:未知)=> z.infer
导出类型 ParsedObj = ReturnType
有效负载是:
declare global {
namespace Express {
export interface Request {
payload?: ParsedObj;
}
}
}
没有成功,因为它依赖于一个通用参数,即调用之前的“any”,所以接下来的代码中我收到“any”。
因此,只能信任 zod 验证的结果,因为它只选择模式中描述的 props,我们不会收到未使用的属性。
如果您根据您使用快递的经验,有更好的方法,欢迎您。