具有类型缩小功能的 Typescript 路由参数验证函数

Typescript route param validation function with type narrowing

提问人:Matej 提问时间:9/7/2023 更新时间:9/7/2023 访问量:26

问:

我想为我的 api 路由参数创建一个通用验证函数,但是,我遇到了一个问题,即该函数在调用后没有缩小类型范围。

这是功能:

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>;
}

这是一个示例用法:

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[]`

我有这个函数,它确实缩小了类型范围:

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])
}

但是,仅凭这个功能,我无法想出解决问题的方法。 我怎样才能改进第一个以确保它缩小了类型范围?另外,有哪些好的资源可以查看,以进入像这样更高级的打字稿内容?大多数教程通常只关注基础知识。

TypeScript 类型 类型缩小

评论


答:

0赞 Armand Biestro 9/7/2023 #1

首先,好工作,喜欢这种想法!我在另一个组件中添加了类似的东西,所以我会尽力帮助您创建一个实现,但我不再有代码了

抽象

我们想要一个参数(一个或多个queryValue)和参数名称的函数

我们希望将其转换为预期类型,并断言它是正确的类型。

如果示例是布尔值,我们希望能够将规则添加到验证中。但这不是强制性的。

问题

因此,我们想要一个如下所示的函数:

export ValidationType = 'string' | 'number' | { [key: string]: unknown }

export type CustomValidationRule = (value) => boolean

export type GlobalValidatationFunction(
    param: QueryValue | QueryValue[],
    paramName: string,
    type: ValidationType,
    rules?: CustomValidationRules[]
)

(重新)打字

泛型简介

即使这种类型看起来不错,我们也可以改进。我们有更好的打字选项。

例如,validationType,它真的是它自己的参数还是只是一个规则? ?.缩小类型范围会更容易,设置规则也会更容易。让我们更改签名:

export type QueryValue = string;
export type ValidationType = string | number | { [key: string]: unknown };

export type CustomValidationRule = (value: unknown) => boolean;

export function validate(
  param: QueryValue | QueryValue[],
  paramName: string,
  rules: CustomValidationRule[],
) {
  *****
}

这样做,我们将有更好的方法来添加新类型,并且我们不再需要更改签名!

但是我们将如何检查类型本身 ?我们通常会在实施中这样做。

实现

export type QueryValue = string;
export type ValidationType = string | number | { [key: string]: unknown };

export type CustomValidationRule = (value: unknown) => boolean;

export function validate(
  param: QueryValue | QueryValue[],
  paramName: string,
  rules: CustomValidationRule[],
) {
  let valueToCheck: QueryValue; // typed a string for me
  // param always as a string.
  // you implement that, I dont have the interface
  if (!isArray(param)) valueToCheck = param;
  else valueToCheck = '';
  const errors: string[] = [];
  rules.forEach((rule) => {
    if (!rule(valueToCheck)) {
      errors.push(`${paramName} is invalid`);
    }
  });
  return {
    valid: errors.length > 0,
    statusCode: errors.length > 0 ? 400 : 200,
    errors: errors.reduce((acc, error) => `${acc}\n${error}`) ?? '',
  };
};

validate('5616545', 'your take', [
  (t: unknown) => Number.isNaN(t),
  (t: unknown) => Number(t) > 0,
]);

希望这个基地会有所帮助

起色

您可以对 CustomValidationRule 进行签名,如下所示:

export type CustomValidationRule = (value: unknown) => {
  success: boolean,
  error: string,
};

以便您进行适当的错误处理

类验证器

无论如何,即使它很有趣,我也会使用类验证器

https://www.npmjs.com/package/class-validator

最适合我的打字稿课程

https://www.typescriptlang.org/docs/handbook/2/types-from-types.html

它会为你节省几天, 干杯