I wanted to create a generic validation function for my api route params, however, I ran into an issue where the function doesn't narrow down the type after it's called.
This is the function:
export default function (
param: QueryValue | QueryValue[],
paramName: string,
type: 'string' | 'number' | { [key: string]: unknown },
rules: Array<(param: QueryValue | QueryValue[]) => { valid: boolean, errorMessage: string } | boolean> | null = null
): SuccessfulValidation | FailedValidation {
// if the type is an enum
if (typeof type === 'object' && type !== null) {
if (!isEnumKey(type)(param)) {
return {
valid: false,
error: createError({
statusCode: 400,
statusMessage: `Invalid "${paramName}" query parameter. The value must be one of the supported values.`,
})
}
}
} else if (['string'].includes(<string>type)) {
if (typeof param !== type) {
return {
valid: false,
error: createError({
statusCode: 400,
statusMessage: `Invalid "${paramName}" query parameter. The value must be a ${type}.`,
})
}
}
} else if (type === 'number') {
if (typeof param !== 'string' || isNaN(parseInt(param))) {
return {
valid: false,
error: createError({
statusCode: 400,
statusMessage: `Invalid "${paramName}" query parameter. The value must be a number.`,
})
}
}
} else if (typeof param !== typeof type) {
return {
valid: false,
error: createError({
statusCode: 400,
statusMessage: `Invalid "${paramName}" query parameter. The value must be a ${typeof type}.`,
})
}
}
// if the rules are provided, validate the param against them
if (rules) {
for (const rule of rules) {
const result = rule(param)
if (typeof result === 'boolean') {
if (!result) {
return {
valid: false,
error: createError({
statusCode: 400,
statusMessage: `Invalid "${paramName}" query parameter. Rule validation failed.`,
})
}
}
} else {
if (!result.valid) {
return {
valid: false,
error: createError({
statusCode: 400,
statusMessage: result.errorMessage,
})
}
}
}
}
}
return {
valid: true,
error: null,
}
}
interface SuccessfulValidation {
valid: true;
error: null;
}
interface FailedValidation {
valid: false;
error: ReturnType<typeof createError>;
}
And this is an example useage:
const validation = routeParamValidate(queryOffset, 'offset', 'number')
if (!validation.valid) {
return validation.error
}
// I would expect `queryOffset` to be typed as `number` here,
// but it stays as ` QueryValue | QueryValue[]`
I have this function, which does narrow down types:
export default function <T extends { [s: string]: unknown }>(e: T) {
return (token: unknown): token is T[keyof T] => Object.values(e).includes(token as T[keyof T])
}
But I wasn't able to come up with a solution to the problem just from looking at this function alone. How could I improve the first one to make sure it narrows down the type? Also, what are some good resources to have a look at to get into more advanced typescript stuff like this? Most tutorials usually only really focus on the basics.
First of all, nice jobs, love this kind of idea! I add something like that in my other compony, so i'll try my best to help you to create an implementation but I dont have the code anymore
Abstract
we want a function that that a parameter (one or more queryValue) and the name of the parameter
we want to convert it an expected type and assert the fact that it is the proper type.
we want to be able to add rules to the validation for exemple if it is a boolean. but it is not mandatory.
problematic
So we want a function that looks like:
(re)typing
introducing generics
even if this type looks good, we could improve. We have better typings options.
for exemple, validationType, is it really a parameter by it self or is it just a rule? ?. It will be easier to narrow down the types and will be easier for setting up the rules. let's change the signatures:
doing it like this we will have a better way of adding new types and we wont need to change the signatures anymore!
but how are we going to check the type itself ? we will do that in the implemenation as a rule.
implementation
hope this base will help
improvement
You could sign the CustomValidationRule like:
So that you have a proper error handling
class validator
how ever, even if it fun and all, I would use the class validator
https://www.npmjs.com/package/class-validator
best typescript lesson for me
https://www.typescriptlang.org/docs/handbook/2/types-from-types.html
it will save you days, cheers