Skip to content

Instantly share code, notes, and snippets.

@err0r500
Last active January 17, 2022 14:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save err0r500/996c0472529e368a318bde8c59e9cf45 to your computer and use it in GitHub Desktop.
Save err0r500/996c0472529e368a318bde8c59e9cf45 to your computer and use it in GitHub Desktop.
io-ts type refinement
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