-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Default values from schema not applied to Request object #13
Comments
I believe this is the desired behaviour for |
Oh, I overlooked the |
I believe the types still don't function correctly though. It appears that the types are using the schema input types rather than the schema output types. |
Can confirm with member default set app.get(
"/endpoint",
processRequest({
query: z.object({
sort: z.enum(["NAME_ASC", "NAME_DSC"]).default("NAME_ASC"),
}),
}),
async (request, response) => {
const sort = request.query.sort; // "NAME_ASC" | "NAME_DSC" | undefined
}
); without member default set app.get(
"/endpoint",
processRequest({
query: z.object({
sort: z.enum(["NAME_ASC", "NAME_DSC"]),
}),
}),
async (request, response) => {
const sort = request.query.sort; // "NAME_ASC" | "NAME_DSC"
}
); with a default on the wrapping object. now the entire object could be undefined... app.get(
"/endpoint",
processRequest({
query: z.object({
sort: z.enum(["NAME_ASC", "NAME_DSC"]).default("NAME_ASC"),
}).default({}),
}),
async (request, response) => {
request.query // {...} | undefined
request.query.sort // error as request.query may be undefined
}
); even if the default is explicitly stated... still could be undefined. app.get(
"/endpoint",
processRequest({
query: z.object({
sort: z.enum(["NAME_ASC", "NAME_DSC"]).default("NAME_ASC"),
}).default({
sort: "NAME_ASC"
}),
}),
async (request, response) => {
request.query // {...} | undefined
request.query.sort // error as request.query may be undefined
}
); |
I tested this again today, and // GET /test
router.get('/test', processRequest(
query: z.object({
limit: z.number().min(0).optional().default(10),
})
)),
(req, res) => {
req.query.limit // 10
} In the library, if |
The issue is not the values, it's the inferred types which are incorrect.
|
Oh I see, you're right, the type is still |
I ended up writing my own middleware which addresses this issue. The usage is not the same, but regardless you may find it useful. I would submit a PR to fix this issue but it appears as though this project is unmaintained. import { RequestHandler } from 'express';
import { ZodError, z } from 'zod';
const types = ['query', 'params', 'body'] as const;
/**
* A middleware generator that validates incoming requests against a set of schemas.
* @param schemas The schemas to validate against.
* @returns A middleware function that validates the request.
*/
export default function validate<TParams extends Validation = {}, TQuery extends Validation = {}, TBody extends Validation = {}>(
schemas: ValidationSchemas<TParams, TQuery, TBody>
): RequestHandler<ZodOutput<TParams>, any, ZodOutput<TBody>, ZodOutput<TQuery>> {
// Create validation objects for each type
const validation = {
params: z.object(schemas.params ?? {}).strict() as z.ZodObject<TParams>,
query: z.object(schemas.query ?? {}).strict() as z.ZodObject<TQuery>,
body: z.object(schemas.body ?? {}).strict() as z.ZodObject<TBody>
};
return (req, res, next) => {
const errors: Array<ErrorListItem> = [];
// Validate all types (params, query, body)
for (const type of types) {
const parsed = validation[type].safeParse(req[type]);
// @ts-expect-error This is fine
if (parsed.success) req[type] = parsed.data;
else errors.push({ type, errors: parsed.error });
}
// Return all errors if there are any
if (errors.length > 0) return res.status(400).send(errors.map(error => ({ type: error.type, errors: error.errors })));
return next();
};
}
/**
* The types of validation that can be performed.
*/
type DataType = (typeof types)[number];
/**
* An error item for a specific type.
*/
interface ErrorListItem {
type: DataType;
errors: ZodError<any>;
}
/**
* Generic validation type for a route (either params, query, or body).
*/
type Validation = Record<string, z.ZodTypeAny>;
/**
* The schemas provided to the validate middleware.
*/
interface ValidationSchemas<TParams extends Validation, TQuery extends Validation, TBody extends Validation> {
params?: TParams;
query?: TQuery;
body?: TBody;
}
/**
* The output type of a validation schema.
*/
type ZodOutput<T extends Validation> = z.ZodObject<T>['_output']; A really basic usage example looks like this: app.post('/test', validate({ body: { limit: z.number().min(0).optional().default(10) } }), async (req, res) => {
// req.body.limit -> number
}); I may end up making my own package for this tomorrow given I've used this in a couple projects now. Hope it helps! |
I have a validation schema for the
limit
parameter, which has a default value:The middleware validates this parameter in the query, but it does not add default values to the parameters. Therefore,
req.query
still contains the original incoming object.This may be intentional, as the library is not intended for sanitization and default values.
The text was updated successfully, but these errors were encountered: