Skip to content

Commit 068f16a

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 647e530 commit 068f16a

File tree

6 files changed

+116
-13
lines changed

6 files changed

+116
-13
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. **Note**: A feature tagged as Experimental is in a
1515
high state of flux, you're at risk of it changing without notice.
1616

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

1924
- **Bug Fix**

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ You can also use the [`withMessage`](https://gcanti.github.io/io-ts-types/module
312312
| union | `A \| B` | `t.union([ A, B ])` |
313313
| intersection | `A & B` | `t.intersection([ A, B ])` |
314314
| keyof | `keyof M` | `t.keyof(M)` (**only supports string keys**) |
315+
| enum | `enum A {}` | `t.enum(A)` |
315316
| recursive types | | `t.recursion(name, definition)` |
316317
| branded types / refinements | ✘ | `t.brand(A, predicate, brand)` |
317318
| integer | ✘ | `t.Int` (built-in branded codec) |

package-lock.json

Lines changed: 31 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "io-ts",
3-
"version": "2.0.1",
3+
"version": "2.1.0",
44
"description": "TypeScript compatible runtime type system for IO validation",
55
"files": [
66
"lib",

src/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,24 @@ export const array = <C extends Mixed>(codec: C, name: string = `Array<${codec.n
689689
codec
690690
)
691691

692+
/**
693+
* @since 2.1.0
694+
*/
695+
export class EnumType<A> extends Type<A> {
696+
public readonly _tag: 'EnumType' = 'EnumType'
697+
constructor(name: string, is: EnumType<A>['is'], validate: EnumType<A>['validate'], encode: EnumType<A>['encode']) {
698+
super(name, is, validate, encode)
699+
}
700+
}
701+
702+
/**
703+
* @since 2.1.0
704+
*/
705+
const enumType = <A>(e: any, name: string = 'Enum'): EnumType<A> => {
706+
const is = (u: unknown): u is A => Object.keys(e).some(k => e[k] === u)
707+
return new EnumType<A>(name, is, (u, c) => (is(u) ? success(u) : failure(u, c)), identity)
708+
}
709+
692710
/**
693711
* @since 1.0.0
694712
*/
@@ -1614,6 +1632,13 @@ export {
16141632
undefinedType as undefined
16151633
}
16161634

1635+
export {
1636+
/**
1637+
* @since 2.1.0
1638+
*/
1639+
enumType as enum
1640+
}
1641+
16171642
export {
16181643
/**
16191644
* Use `UnknownArray` instead

test/enum.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as assert from 'assert'
2+
import * as t from '../src/index'
3+
import { assertSuccess, assertFailure } from './helpers'
4+
5+
describe('enum', () => {
6+
enum A {
7+
Foo = 'foo',
8+
Bar = 'bar'
9+
}
10+
11+
describe('name', () => {
12+
it('should assign a default name', () => {
13+
const T = t.enum(A)
14+
assert.strictEqual(T.name, 'Enum')
15+
})
16+
17+
it('should accept a name', () => {
18+
const T = t.enum(A, 'T')
19+
assert.strictEqual(T.name, 'T')
20+
})
21+
})
22+
23+
describe('is', () => {
24+
it('should check an enum value', () => {
25+
const T = t.enum(A)
26+
assert.strictEqual(T.is(A.Foo), true)
27+
assert.strictEqual(T.is('bar'), true)
28+
assert.strictEqual(T.is('invalid'), false)
29+
assert.strictEqual(T.is(null), false)
30+
assert.strictEqual(T.is(A), false)
31+
})
32+
})
33+
34+
describe('decode', () => {
35+
it('should decode an enum value', () => {
36+
const T = t.enum(A)
37+
assertSuccess(T.decode(A.Foo), A.Foo)
38+
assertSuccess(T.decode('bar'), 'bar')
39+
})
40+
41+
it('should fail decoding an invalid value', () => {
42+
const T = t.enum(A)
43+
assertFailure(T, 'invalid', ['Invalid value "invalid" supplied to : Enum'])
44+
})
45+
})
46+
47+
describe('encode', () => {
48+
it('should encode an enum value', () => {
49+
const T = t.enum(A)
50+
assert.deepStrictEqual(T.encode(A.Foo), A.Foo)
51+
})
52+
})
53+
})

0 commit comments

Comments
 (0)