Last active
January 17, 2022 14:21
-
-
Save err0r500/996c0472529e368a318bde8c59e9cf45 to your computer and use it in GitHub Desktop.
io-ts type refinement
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import assert from 'assert'; | |
import { isLeft, isRight } from 'fp-ts/lib/Either'; | |
import { unsafeCoerce } from 'fp-ts/lib/function'; | |
import * as t from 'io-ts'; | |
// Int | |
export const IntC = t.brand( | |
t.number, | |
(s): s is t.Branded<number, { readonly IntBrand: unique symbol }> => | |
Number.isInteger(s), | |
'IntBrand', | |
); | |
export type Int = t.TypeOf<typeof IntC>; | |
// OtherInt | |
export const OtherIntC = t.brand( | |
t.number, | |
(s): s is t.Branded<number, { readonly OtherIntBrand: unique symbol }> => | |
Number.isInteger(s), | |
'OtherIntBrand', | |
); | |
export type OtherInt = t.TypeOf<typeof OtherIntC>; | |
// PositiveInt | |
export const PositiveIntC = t.brand( | |
IntC, | |
(s): s is t.Branded<Int, { readonly PositiveIntBrand: unique symbol }> => | |
s > 0, | |
'PositiveIntBrand', | |
); | |
export type PositiveInt = t.TypeOf<typeof PositiveIntC>; | |
describe('types', () => { | |
const expectsNum = (_x: number): void => {}; | |
const expectsInt = (_x: Int): void => {}; | |
const expectsPositiveInt = (_x: PositiveInt): void => {}; | |
const expectsOtherInt = (_x: OtherInt): void => {}; | |
const negInt: number = -1; | |
const posInt: number = 1; | |
const posNotInt: number = 1.2; | |
describe('number', () => { | |
describe('usage', () => { | |
it('can be used as', () => { | |
expectsNum(posNotInt); | |
}); | |
it('can NOT be used as', () => { | |
// @ts-expect-error | |
expectsInt(posNotInt); | |
// @ts-expect-error | |
expectsPositiveInt(posNotInt); | |
// @ts-expect-error | |
expectsOtherInt(posNotInt); | |
}); | |
}); | |
}); | |
describe('int', () => { | |
describe('decoding', () => { | |
it('is NOT ok for posNotInt', () => { | |
assert(isLeft(IntC.decode(posNotInt))); | |
}); | |
it('is ok for negInt', () => { | |
assert(isRight(IntC.decode(negInt))); | |
}); | |
it('is ok for posInt', () => { | |
assert(isRight(IntC.decode(posInt))); | |
}); | |
it('is ok for otherInt', () => { | |
assert(isRight(OtherIntC.decode(posInt))); | |
}); | |
}); | |
describe('usage', () => { | |
const i: Int = unsafeCoerce(negInt); | |
it('can be used as', () => { | |
expectsNum(i); | |
expectsInt(i); | |
}); | |
it('can NOT be used as', () => { | |
// @ts-expect-error | |
expectsPositiveInt(i); | |
// @ts-expect-error | |
expectsOtherInt(i); | |
}); | |
}); | |
}); | |
describe('positiveInt', () => { | |
describe('decoding', () => { | |
it('is NOT ok for posNotInt', () => { | |
assert(isLeft(PositiveIntC.decode(posNotInt))); | |
}); | |
it('is NOT ok for negInt', () => { | |
assert(isLeft(PositiveIntC.decode(negInt))); | |
}); | |
it('is ok for posInt', () => { | |
assert(isRight(PositiveIntC.decode(posInt))); | |
}); | |
}); | |
describe('usage', () => { | |
const i: PositiveInt = unsafeCoerce(posInt); | |
it('can be used as', () => { | |
expectsNum(i); | |
expectsInt(i); | |
expectsPositiveInt(i); | |
}); | |
it('can be used as', () => { | |
// @ts-expect-error | |
expectsOtherInt(i); | |
}); | |
}); | |
}); | |
describe('compatibility', () => { | |
it('can interoperate freely', () => { | |
assert(posNotInt + negInt + posInt === 1.2); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment