Aquila169 / zod-express-middleware

Express middleware to validate requests using zod schema's.
MIT License
82 stars 13 forks source link

transform() breaks RequestValidation #1

Open potados99 opened 3 years ago

potados99 commented 3 years ago

Example below works:

const app = express();

const params = z.object({
  num: z.string(),
})

app.get('/', validateRequestParams(params), async (req, res, next) => {
  req.params.num
});

However, when I add .transform() to the num, the validateRequestParams() refuses to take the schema:

const app = express();

const params = z.object({
  num: z.string().transform((v) => Number.parseInt(v)),
});

app.get('/', validateRequestParams(params/*this part turns red*/), async (req, res, next) => {
  req.params.num;
});

It gives me this error:

TS2345: Argument of type 'ZodObject<{ num: ZodEffects<ZodString, number>; }, "strip", ZodTypeAny, { num: number; }, { num: string; }>' is not assignable to parameter of type 'ZodType<{ num: number; }, ZodTypeDef, { num: number; }>'.  

The types of '_input.num' are incompatible between these types.     

Type 'string' is not assignable to type 'number'.

I managed to solve it by modifying the RequestValidation type:

export declare type RequestValidation<TParams, TQuery, TBody> = {
    params?: ZodSchema<TParams, ZodTypeDef, any>;
    query?: ZodSchema<TQuery, ZodTypeDef, any>;
    body?: ZodSchema<TBody, ZodTypeDef, any>;
};

The error is resolved, but not sure if it is the best way to handle it. Any suggestions?

wobsoriano commented 2 years ago

I'd do it like this:

import {  z } from "zod"

// copy of the private Zod utility type of ZodObject
type UnknownKeysParam = "passthrough" | "strict" | "strip"

type TQuery<U extends UnknownKeysParam = any> =
  | z.ZodObject<any, U>
  | z.ZodUnion<[TQuery<U>, ...TQuery<U>[]]>
  | z.ZodIntersection<TQuery<U>, TQuery<U>>
  | z.ZodDiscriminatedUnion<string, z.Primitive, z.ZodObject<any, U>>;

export const validateRequestQuery: <TQ extends TQuery>(
  zodSchema: TQ,
) => RequestHandler<ParamsDictionary, any, any, TQ> = (schema) => (req, res, next) => {
  const parsed = schema.safeParse(req.query);
  if (parsed.success) {
    return next();
  } else {
    return sendErrors([{ type: 'Query', errors: parsed.error }], res);
  }
};
potados99 commented 2 years ago

@wobsoriano Thanks! I'll try it.

MikaStark commented 7 months ago

Casting through unknown helps me :

z.string()
  .transform(Number)
  .pipe(z.number().int().positive()) as unknown as z.ZodNumber