Skip to content

Commit c676682

Browse files
author
Alec Gibson
committed
Add enum support
The `enum` is a popular aspect of TypeScript, and can be embedded in interfaces as a concise, but descriptive, shorthand for literal string unions: ```typescript enum Colour { White = '000000', Black = 'ffffff' } ``` This change adds an exported member `enum` to `io-ts`, based on [this suggestion][1] by @noe132 It means that `enum`s can be reused directly in `io-ts`: ```typescript const T = t.enum(Colour) ``` [1]: #216 (comment)
1 parent f13a10a commit c676682

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
**Note**: Gaps between patch versions are faulty/broken releases.
1515
**Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
1616

17+
# 2.3.0
18+
19+
- **New Feature**
20+
- Add support for `enum`
21+
1722
# 2.2.13
1823

1924
- **Bug Fix**

src/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,36 @@ export const array = <C extends Mixed>(item: C, name: string = `Array<${item.nam
827827
item
828828
)
829829

830+
enum Enum {}
831+
/**
832+
* @since 2.3.0
833+
*/
834+
export class EnumType<E extends typeof Enum> extends Type<E[keyof E]> {
835+
public readonly _tag: 'EnumType' = 'EnumType'
836+
private _enum: E
837+
private _enumValues: Set<string | number>
838+
public constructor(e: E, name?: string) {
839+
super(
840+
name || 'enum',
841+
(u): u is E[keyof E] => {
842+
if (!this._enumValues.has(u as any)) return false
843+
// Don't allow key names from number enum reverse mapping
844+
if (typeof (this._enum as any)[u as string] === 'number') return false
845+
return true
846+
},
847+
(u, c) => (this.is(u) ? success(u) : failure(u, c)),
848+
identity
849+
)
850+
this._enum = e
851+
this._enumValues = new Set(Object.values(e))
852+
}
853+
}
854+
855+
/**
856+
* @since 2.3.0
857+
*/
858+
const enumType = <E extends typeof Enum>(e: E, name?: string) => new EnumType<E>(e, name)
859+
830860
/**
831861
* @since 1.0.0
832862
*/
@@ -1871,6 +1901,13 @@ export {
18711901
undefinedType as undefined
18721902
}
18731903

1904+
export {
1905+
/**
1906+
* @since 2.1.0
1907+
*/
1908+
enumType as enum
1909+
}
1910+
18741911
export {
18751912
/**
18761913
* Use `UnknownArray` instead

test/enum.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import * as assert from 'assert'
2+
import * as t from '../src/index'
3+
import * as _ from '../src/Decoder'
4+
import { isLeft } from 'fp-ts/lib/Either'
5+
6+
describe('enum', () => {
7+
enum A {
8+
Foo = 'foo',
9+
Bar = 'bar'
10+
}
11+
12+
enum B {
13+
Foo,
14+
Bar
15+
}
16+
17+
describe('name', () => {
18+
it('should assign a default name', () => {
19+
const T = t.enum(A)
20+
assert.strictEqual(T.name, 'enum')
21+
})
22+
23+
it('should accept a name', () => {
24+
const T = t.enum(A, 'T')
25+
assert.strictEqual(T.name, 'T')
26+
})
27+
})
28+
29+
describe('is', () => {
30+
it('should check an enum string value', () => {
31+
const T = t.enum(A)
32+
assert.strictEqual(T.is(A.Foo), true)
33+
assert.strictEqual(T.is('bar'), true)
34+
assert.strictEqual(T.is('invalid'), false)
35+
assert.strictEqual(T.is(null), false)
36+
assert.strictEqual(T.is(A), false)
37+
})
38+
39+
it('should check an enum integer value', () => {
40+
const T = t.enum(B)
41+
assert.strictEqual(T.is(B.Foo), true)
42+
assert.strictEqual(T.is(1), true)
43+
assert.strictEqual(T.is('Foo'), false)
44+
assert.strictEqual(T.is('invalid'), false)
45+
assert.strictEqual(T.is(null), false)
46+
assert.strictEqual(T.is(B), false)
47+
})
48+
})
49+
50+
describe('decode', () => {
51+
it('should decode an enum string value', () => {
52+
const T = t.enum(A)
53+
assert.deepStrictEqual(T.decode(A.Foo), _.success(A.Foo))
54+
assert.deepStrictEqual(T.decode('bar'), _.success('bar'))
55+
})
56+
57+
it('should decode an enum integer value', () => {
58+
const T = t.enum(B)
59+
assert.deepStrictEqual(T.decode(B.Foo), _.success(B.Foo))
60+
assert.deepStrictEqual(T.decode(1), _.success(1))
61+
})
62+
63+
it('should fail decoding an invalid string value', () => {
64+
const T = t.enum(A)
65+
assert.deepStrictEqual(isLeft(T.decode('invalid')), true)
66+
})
67+
68+
it('should fail decoding an invalid integer value', () => {
69+
const T = t.enum(B)
70+
assert.deepStrictEqual(isLeft(T.decode(2)), true)
71+
})
72+
})
73+
74+
describe('encode', () => {
75+
it('should encode an enum string value', () => {
76+
const T = t.enum(A)
77+
assert.deepStrictEqual(T.encode(A.Foo), A.Foo)
78+
})
79+
80+
it('should encode an enum integer value', () => {
81+
const T = t.enum(B)
82+
assert.deepStrictEqual(T.encode(B.Foo), B.Foo)
83+
})
84+
})
85+
})

0 commit comments

Comments
 (0)