Reputation: 438
I'm hitting a problem with a TypeScript project. I have a generic Processor
that performs validation and processes data by accepting Validator
and Executor
objects which are also both generic, inferring their arguments from the Processor
:
type ExecutorFunction<Args extends any[], Return extends any> = (...args: Args) => Return;
class Validator<Args extends any[]> {
private validators: ExecutorFunction<Args, boolean>[] = [];
constructor(...validators: ExecutorFunction<Args, boolean>[]) {
this.validators.push(...validators);
}
validate(...args: Args): boolean {
return this.validators.every((validator) => validator(...args));
}
}
class Executor<Args extends any[], Return extends any> {
private executors: ExecutorFunction<Args, Return>[] = [];
constructor(...executors: ExecutorFunction<Args, Return>[]) {
this.executors.push(...executors);
}
execute(...args: Args): Return[] {
return this.executors.map((executor) => executor(...args));
}
}
class Processor<Args extends any[], Return extends any> {
private validators: Validator<Args>[] = [];
private executors: Executor<Args, Return>[] = [];
constructor(...args: (Validator<Args> | Executor<Args, Return>)[]) {
args.forEach((item) => {
if (item instanceof Validator) {
this.validators.push(item);
return;
}
this.executors.push(item);
});
}
process(...args: Args): Return[] {
return this.executors.flatMap((executor) => executor.execute(...args));
}
validate(...args: Args): boolean {
return this.validators.every((validator) => validator.validate(...args));
}
}
This works fine for simple cases, but if I add a Validator
that accepts other validators (an OrValidator
for example) the child Validator
s lose their generic value:
class OrValidator<Args extends any[]> extends Validator<Args> {
constructor(...childValidators: Validator<Args>[]) {
super((...args) =>
childValidators.some((validator) => validator.validate(...args))
);
}
}
class NumberProcessor extends Processor<[number, number, number], number> {}
const numberProcessor = new NumberProcessor(
new Validator((a) => a > 0),
new Validator((a, b) => b > 10),
new Validator((a, b, c) => Number.isFinite(c)),
new OrValidator(
new Validator((a) => a > 0),
new Validator((a, b) => a > b), // This and the line below error because it only expects 1 argument
new Validator((a, b, c) => c < 99)
),
new Executor((a, b, c) => (a + b) * c)
);
I'd like the OrValidator
to correctly pick up the NumberProcessor
s generic values, am I doing something wrong/missing something obvious?
After fixing the missing ...
spread operator, the examples where I manually add the generic types to each nested items do work as expected, but the original question still stands as to why this isn't inferred automatically for the nested items.
Upvotes: 0
Views: 74
Reputation: 1827
This is a problem with the infer of the functions inside the constructor of OrValidator
.
Change them to
const numberProcessor = new NumberProcessor(
new Validator((a) => a > 0),
new Validator((a, b) => b > 10),
new Validator((a, b, c) => Number.isFinite(c)),
new OrValidator(
new Validator((a, b, c) => c < 99),
new Validator((a) => a > 0),
new Validator((a, b) => a > b),
),
new Executor((a, b, c) => (a + b) * c)
);
Or type the new OrValidator
:
const numberProcessor = new NumberProcessor(
new Validator((a) => a > 0),
new Validator((a, b) => b > 10),
new Validator((a, b, c) => Number.isFinite(c)),
new OrValidator<[number ,number, number]>(
new Validator((a) => a > 0),
new Validator((a, b) => a > b),
new Validator((a, b, c) => c < 99),
),
new Executor((a, b, c) => (a + b) * c)
);
Upvotes: 1