如何在express中间件中使用zod验证请求?

问题描述 投票:0回答:1

我必须编写 zod express 验证器中间件,它是从 zod-express-middleware

稍作修改的

这是我的自定义请求验证器

export type ErrorListItem = {
  type: 'Query' | 'Params' | 'Body' | 'Response';
  errors: ZodError<any>;
};

type RequestSchemas<TParams, TQuery, TBody, TResponse> = Partial<{
  params: ZodSchema<TParams>;
  query: ZodSchema<TQuery>;
  body: ZodSchema<TBody>;
  response: ZodSchema<TResponse>;
}>;

type RequestCallback<TParams, TQuery, TBody, TResponse> = (
  data: {
    params: TParams;
    body: TBody;
    query: TQuery;
  },
  req: Express.Request,
  res: Express.Response
) => Promise<TResponse>;

type ValidatedRequest = (
  req: Express.Request,
  res: Express.Response,
  next: Express.NextFunction
) => Promise<void>;

export const doValidatedRequest =
  <TParams, TQuery, TBody, TResponse>(
    schemas: RequestSchemas<TParams, TQuery, TBody, TResponse>,
    cb: RequestCallback<TParams, TQuery, TBody, TResponse>,
    succesStatusCode: number = 200
  ): ValidatedRequest =>
  async (req, res, next) => {
    try {
      const errors: ErrorListItem[] = [];

      const requestData = {
        params: {} as any,
        body: {} as any,
        query: {} as any
      };

      if (schemas.params) {
        const parsedParams = schemas.params.safeParse(req.params);
        if (parsedParams.success) {
          requestData.params = parsedParams.data;
        } else {
          errors.push({ type: 'Params', errors: parsedParams.error });
        }
      }

      if (schemas.body) {
        const parsedBody = schemas.body.safeParse(req.body);
        if (parsedBody.success) {
          requestData.body = parsedBody.data;
        } else {
          errors.push({ type: 'Body', errors: parsedBody.error });
        }
      }

      if (schemas.query) {
        const parsedQuery = schemas.query.safeParse(req.query);
        if (parsedQuery.success) {
          requestData.query = parsedQuery.data;
        } else {
          errors.push({ type: 'Query', errors: parsedQuery.error });
        }
      }

      if (errors.length) {
        next(errors);
      }

      const response = await cb(requestData, req, res);
      if (schemas.response) {
        const parsedResponse = schemas.response.safeParse(response);
        if (parsedResponse.success) {
          res.status(succesStatusCode).json(parsedResponse.data);
        } else {
          errors.push({ type: 'Response', errors: parsedResponse.error });
        }
      } else {
        // Response not specified and thus not expected
        res.status(succesStatusCode).json(response);
      }
    } catch (error) {
      next(error);
    }
  };

现在当我尝试使用它时

router.get('/', doValidatedRequest({}, async ({}, req, res) => someFunction(req, res));

router.get('/:id', doValidatedRequest({
  query: z.object({
    id: z.string()
  }),
}, async ({ query: { id }}) => someFunction(id));

我不断收到 eslint 错误

Promise returned in function argument where a void return was expected. eslint(@typescript-eslint/no-misused-promises)

我如何定义

someFunction

似乎并不重要
const someFunction = async (): Promise<void> => { // do something }
const someFunction = async (): Promise<string> => { return "OK" }
const someFunction = async (id: string): Promise<string> => { return id }
const someFunction = (): void => { // do something }
const someFunction = (): string => { return "OK" }
const someFunction = (id: string): string => { return id }

有人能看出这是为什么吗?我似乎找不到原因,而且我也想知道它是否会在某个时候引起任何进一步的问题。我该如何改进这个中间件?

其想法是,这可以用于验证请求参数和响应,如果需要,将经过验证且类型正确的参数传递给控制器函数,然后控制器函数将可选的经过验证的响应传递给验证器,然后验证器将响应发送回请求者.

express validation middleware zod
1个回答
0
投票

我的定义似乎有两个问题,都可以在这个片段中看到

type ValidatedRequest = (
  req: Express.Request,
  res: Express.Response,
  next: Express.NextFunction
) => Promise<void>;

export const doValidatedRequest =
  <TParams, TQuery, TBody, TResponse>(
    schemas: RequestSchemas<TParams, TQuery, TBody, TResponse>,
    cb: RequestCallback<TParams, TQuery, TBody, TResponse>,
    succesStatusCode: number = 200
  ): ValidatedRequest =>
  async (req, res, next) => {

第一个是我将

ValidatedRequest
的返回类型输入为
Promise<void>
,当这是
doValidatedRequest
的返回类型时,TS看到有一个
Promise<void>
返回。整个
ValidatedRequest
类型可能有点多余,
doValidatedRequest
的返回类型可以只是
express.RequestHandler

其次,我将

ValidatedRequest
(或
RequestHandler
)函数定义为
async (req, res, next) => ...
,这也告诉 TS 有某种要返回的承诺。

第三,我想我有一些奇怪的 eslint 规则告诉我我不应该在快速中间件中使用承诺。不确定,快递是否可以处理这些。我想应该可以吧

无论如何,现在的解决方法是将

doValidatedRequest
重新定义为

export const doValidatedRequest =
  <TParams, TQuery, TBody, TResponse>(
    schemas: RequestSchemas<TParams, TQuery, TBody, TResponse>,
    cb: RequestCallback<TParams, TQuery, TBody, TResponse>,
    succesStatusCode = 200
  ): RequestHandler =>
  (req, res, next) => {
    const errors: ErrorListItem[] = [];

    const requestData = {
      params: {} as any,
      body: {} as any,
      query: {} as any
    };

    if (schemas.params) {
      const parsedParams = schemas.params.safeParse(req.params);
      if (parsedParams.success) {
        requestData.params = parsedParams.data;
      } else {
        errors.push({ type: 'Params', errors: parsedParams.error });
      }
    }

    if (schemas.body) {
      const parsedBody = schemas.body.safeParse(req.body);
      if (parsedBody.success) {
        requestData.body = parsedBody.data;
      } else {
        errors.push({ type: 'Body', errors: parsedBody.error });
      }
    }

    if (schemas.query) {
      const parsedQuery = schemas.query.safeParse(req.query);
      if (parsedQuery.success) {
        requestData.query = parsedQuery.data;
      } else {
        errors.push({ type: 'Query', errors: parsedQuery.error });
      }
    }

    if (errors.length) {
      next(errors);
    }

    cb(requestData, req, res)
      .then(response => {
        if (schemas.response) {
          const parsedResponse = schemas.response.safeParse(response);
          if (parsedResponse.success) {
            res.status(succesStatusCode).json(parsedResponse.data);
          } else {
            errors.push({ type: 'Response', errors: parsedResponse.error });
          }
        } else {
          // Response not specified and thus not expected
          res.status(succesStatusCode).json(response);
        }
      })
      .catch(next);
  };

这应该和原来的一样有效,并且没有显示任何类型的 TS/eslint 错误

© www.soinside.com 2019 - 2024. All rights reserved.