You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Valibot正确校验构造函数参数?为何校验未生效?

Valibot未校验构造函数参数的问题分析与解决

问题重现

Perl(Moops实现,校验正常)

use strictures;
use Moops;
use Data::Dx;

library MyTypes declares NonNegativeInt {
    # 假设Types::Common::Numeric的PositiveOrZeroInt不存在
    declare NonNegativeInt, as Int, where { $_ >= 0 };
};

class Span types MyTypes :ro {
    has from => isa => NonNegativeInt, required => true;
    has to => isa => NonNegativeInt, required => true;
    fun BUILD(InstanceOf["Span"] $self, HashRef $args) {
        die qq("from" $args->{from} must be smaller than "to" $args->{to})
            if $args->{from} > $args->{to};
    }
}

Dx Span->new(from => -123, to => 234); # 按预期抛出错误
Dx Span->new(from =>  123, to => 234); # 正常运行
Dx Span->new(from => 1123, to => 234); # 按预期抛出错误

# 运行命令
# cpanm -nv -L local strictures Moops Data::Dx
# perl -I local/lib/perl5 index.pl

JavaScript/TypeScript(Valibot实现,校验失效)

import * as v from "valibot";

const NonNegativeBigInt = v.pipe(v.bigint(), v.toMinValue(0n));
type NonNegativeBigInt = v.InferOutput<typeof NonNegativeBigInt>;

class Span {
    readonly #from: NonNegativeBigInt;
    readonly #to: NonNegativeBigInt;
    constructor(readonly from: NonNegativeBigInt, readonly to: NonNegativeBigInt) {
        if (from > to) {
            throw new RangeError(`"from" ${from} must be smaller than "to" ${to}`);
        }
        this.#from = from;
        this.#to = to;
    }
    toString(): String {
        return `Span(${this.#from}, ${this.#to})`;
    }
}

console.log(new Span(-123n, 234n)); // 未抛出错误,不符合预期
console.log(new Span( 123n, 234n)); // 正常运行
console.log(new Span(1123n, 234n)); // 按预期抛出错误

# 运行命令
# npm i -D tsx
# npm i -S valibot
# npx tsx index.ts

问题原因

  • TypeScript类型擦除:NonNegativeBigInt是TypeScript静态类型,运行时会被完全擦除,JS引擎不会识别该约束,传入负BigInt时无任何校验触发。
  • Valibot规则未执行:仅通过v.pipe定义了校验规则,但未在构造函数中主动调用Valibot的校验方法(如v.parse)。Valibot是运行时校验工具,必须显式执行规则才会生效。

解决方案

要让Valibot生效,需在构造函数中主动执行校验逻辑,以下是两种常见实现方式:

方式1:直接校验单个参数

在构造函数中用v.parse校验每个参数,不符合规则时直接抛出错误:

import * as v from "valibot";

const NonNegativeBigInt = v.pipe(v.bigint(), v.toMinValue(0n));
type NonNegativeBigInt = v.InferOutput<typeof NonNegativeBigInt>;

class Span {
    readonly #from: NonNegativeBigInt;
    readonly #to: NonNegativeBigInt;
    constructor(from: bigint, to: bigint) {
        // 显式调用Valibot校验参数
        const validatedFrom = v.parse(NonNegativeBigInt, from);
        const validatedTo = v.parse(NonNegativeBigInt, to);
        
        if (validatedFrom > validatedTo) {
            throw new RangeError(`"from" ${validatedFrom} must be smaller than "to" ${validatedTo}`);
        }
        this.#from = validatedFrom;
        this.#to = validatedTo;
        // 可选:绑定校验后的值到公开属性
        Object.assign(this, { from: validatedFrom, to: validatedTo });
    }
    toString(): string {
        return `Span(${this.#from}, ${this.#to})`;
    }
}

console.log(new Span(-123n, 234n)); // 抛出Valibot校验错误
console.log(new Span(123n, 234n)); // 正常运行
console.log(new Span(1123n, 234n)); // 抛出RangeError

方式2:定义完整参数Schema

如果参数是对象形式,可定义完整Schema校验整个参数对象,适配复杂场景:

import * as v from "valibot";

const SpanSchema = v.object({
    from: v.pipe(v.bigint(), v.toMinValue(0n)),
    to: v.pipe(v.bigint(), v.toMinValue(0n)),
});

type SpanParams = v.InferInput<typeof SpanSchema>;

class Span {
    readonly #from: v.InferOutput<typeof SpanSchema>["from"];
    readonly #to: v.InferOutput<typeof SpanSchema>["to"];
    constructor(params: SpanParams) {
        // 校验整个参数对象
        const validatedParams = v.parse(SpanSchema, params);
        
        if (validatedParams.from > validatedParams.to) {
            throw new RangeError(`"from" ${validatedParams.from} must be smaller than "to" ${validatedParams.to}`);
        }
        this.#from = validatedParams.from;
        this.#to = validatedParams.to;
    }
    toString(): string {
        return `Span(${this.#from}, ${this.#to})`;
    }
}

console.log(new Span({ from: -123n, to: 234n })); // 抛出Valibot校验错误
console.log(new Span({ from: 123n, to: 234n })); // 正常运行
console.log(new Span({ from: 1123n, to: 234n })); // 抛出RangeError

补充说明

  • 若需更友好的错误处理,可用v.safeParse替代v.parse,它返回包含成功/失败状态的对象,而非直接抛出错误。
  • TypeScript静态类型可与Valibot运行时校验配合,既在编译阶段提示错误,又在运行阶段保障数据合法性。

内容的提问来源于stack exchange,提问作者daxim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:13:14