Use case: validating tagged json objects coming over the wire
type Carrot = {
type: 'carrot';
};
type Hotdog = {
type: 'hotdog';
};
type Food = Hotdog | Carrot;
function validateFood(mightBeFood: unknown): Food {
ow(
mightBeFood,
ow.any(
ow.object.exactShape({
type: ow.string.equals('carrot'),
}),
ow.object.exactShape({
type: ow.string.equals('hotdog'),
}),
),
);
return mightBeFood;
// TS2322: Type '{ type: string; } | { type: string; }' is not assignable to type 'Food'.
// Type '{ type: string; }' is not assignable to type 'Food'.
// Type '{ type: string; }' is not assignable to type 'Hotdog'.
// Types of property 'type' are incompatible.
// Type 'string' is not assignable to type '"hotdog"'.
}
As far as I could tell, there's no built in way to do this as StringPredicate#addValidator
and StringPredicate#validate
don't seem to let you change the type contained in the predicate.
So, I created a custom predicate:
// exactString.ts
import { Predicate } from 'ow';
class ExactStringPredicate<S extends string> extends Predicate<S> {
expected: S;
constructor(expected: S) {
super('string');
this.expected = expected;
}
equals(): Predicate<S> {
return this.addValidator({
message: (value, label) =>
`Expected ${label} to be \`${this.expected}\`, got \`${value}\``,
validator: (value): value is S => {
return value === this.expected;
},
});
}
}
// these shenanigans are to make it so that we don't have to new this up every time
const exactString = <S extends string>(expected: S) =>
new ExactStringPredicate(expected).equals();
export default exactString;
ow.string.equals
Currently this doesn't allow any further narrowing:
class StringPredicate extends Predicate<string> {
equals(expected: string): this;
}
It could possibly be:
class StringPredicate extends Predicate<string> {
equals<S extends string>(expected: S): Predicate<S>;
}
This does prevent any further chaining onto that predicate, but one could argue that a string matching equals
cannot be further validated. Certainly none of the other included validators can give you any more useful information
// this no longer works
ow.string.equals('hotdog').includes('dog)
StringPredicate
If one wanted to maintain chaining, could do something like this:
class StringPredicate<S extends string> extends Predicate<S> {
equals<S2 extends string>(expected: S2): StringPredicate<S2>;
}
Thus the returned type would still be a StringPredicate
.
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too