Skip to content

Commit

Permalink
Add enum support
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
Alec Gibson committed Jan 4, 2021
1 parent f13a10a commit c676682
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
**Note**: Gaps between patch versions are faulty/broken releases.
**Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.

# 2.3.0

- **New Feature**
- Add support for `enum`

# 2.2.13

- **Bug Fix**
Expand Down
37 changes: 37 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,36 @@ export const array = <C extends Mixed>(item: C, name: string = `Array<${item.nam
item
)

enum Enum {}
/**
* @since 2.3.0
*/
export class EnumType<E extends typeof Enum> extends Type<E[keyof E]> {
public readonly _tag: 'EnumType' = 'EnumType'
private _enum: E
private _enumValues: Set<string | number>
public constructor(e: E, name?: string) {
super(
name || 'enum',
(u): u is E[keyof E] => {
if (!this._enumValues.has(u as any)) return false
// Don't allow key names from number enum reverse mapping
if (typeof (this._enum as any)[u as string] === 'number') return false
return true
},
(u, c) => (this.is(u) ? success(u) : failure(u, c)),
identity
)
this._enum = e
this._enumValues = new Set(Object.values(e))
}
}

/**
* @since 2.3.0
*/
const enumType = <E extends typeof Enum>(e: E, name?: string) => new EnumType<E>(e, name)

/**
* @since 1.0.0
*/
Expand Down Expand Up @@ -1871,6 +1901,13 @@ export {
undefinedType as undefined
}

export {
/**
* @since 2.1.0
*/
enumType as enum
}

export {
/**
* Use `UnknownArray` instead
Expand Down
85 changes: 85 additions & 0 deletions test/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as assert from 'assert'
import * as t from '../src/index'
import * as _ from '../src/Decoder'
import { isLeft } from 'fp-ts/lib/Either'

describe('enum', () => {
enum A {
Foo = 'foo',
Bar = 'bar'
}

enum B {
Foo,
Bar
}

describe('name', () => {
it('should assign a default name', () => {
const T = t.enum(A)
assert.strictEqual(T.name, 'enum')
})

it('should accept a name', () => {
const T = t.enum(A, 'T')
assert.strictEqual(T.name, 'T')
})
})

describe('is', () => {
it('should check an enum string value', () => {
const T = t.enum(A)
assert.strictEqual(T.is(A.Foo), true)
assert.strictEqual(T.is('bar'), true)
assert.strictEqual(T.is('invalid'), false)
assert.strictEqual(T.is(null), false)
assert.strictEqual(T.is(A), false)
})

it('should check an enum integer value', () => {
const T = t.enum(B)
assert.strictEqual(T.is(B.Foo), true)
assert.strictEqual(T.is(1), true)
assert.strictEqual(T.is('Foo'), false)
assert.strictEqual(T.is('invalid'), false)
assert.strictEqual(T.is(null), false)
assert.strictEqual(T.is(B), false)
})
})

describe('decode', () => {
it('should decode an enum string value', () => {
const T = t.enum(A)
assert.deepStrictEqual(T.decode(A.Foo), _.success(A.Foo))
assert.deepStrictEqual(T.decode('bar'), _.success('bar'))
})

it('should decode an enum integer value', () => {
const T = t.enum(B)
assert.deepStrictEqual(T.decode(B.Foo), _.success(B.Foo))
assert.deepStrictEqual(T.decode(1), _.success(1))
})

it('should fail decoding an invalid string value', () => {
const T = t.enum(A)
assert.deepStrictEqual(isLeft(T.decode('invalid')), true)
})

it('should fail decoding an invalid integer value', () => {
const T = t.enum(B)
assert.deepStrictEqual(isLeft(T.decode(2)), true)
})
})

describe('encode', () => {
it('should encode an enum string value', () => {
const T = t.enum(A)
assert.deepStrictEqual(T.encode(A.Foo), A.Foo)
})

it('should encode an enum integer value', () => {
const T = t.enum(B)
assert.deepStrictEqual(T.encode(B.Foo), B.Foo)
})
})
})

0 comments on commit c676682

Please sign in to comment.