Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enum support #366

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
71 changes: 53 additions & 18 deletions docs/modules/index.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ Added in v1.0.0
- [Branded (type alias)](#branded-type-alias)
- [DictionaryType (class)](#dictionarytype-class)
- [\_tag (property)](#_tag-property-10)
- [EnumType (class)](#enumtype-class)
- [\_tag (property)](#_tag-property-11)
- [ExactC (interface)](#exactc-interface)
- [ExactType (class)](#exacttype-class)
- [\_tag (property)](#_tag-property-11)
- [\_tag (property)](#_tag-property-12)
- [HasProps (type alias)](#hasprops-type-alias)
- [HasPropsIntersection (interface)](#haspropsintersection-interface)
- [HasPropsReadonly (interface)](#haspropsreadonly-interface)
Expand All @@ -135,69 +137,70 @@ Added in v1.0.0
- [Int (type alias)](#int-type-alias)
- [IntBrand (interface)](#intbrand-interface)
- [InterfaceType (class)](#interfacetype-class)
- [\_tag (property)](#_tag-property-12)
- [\_tag (property)](#_tag-property-13)
- [IntersectionC (interface)](#intersectionc-interface)
- [IntersectionType (class)](#intersectiontype-class)
- [\_tag (property)](#_tag-property-13)
- [\_tag (property)](#_tag-property-14)
- [KeyofC (interface)](#keyofc-interface)
- [KeyofType (class)](#keyoftype-class)
- [\_tag (property)](#_tag-property-14)
- [\_tag (property)](#_tag-property-15)
- [LiteralC (interface)](#literalc-interface)
- [LiteralType (class)](#literaltype-class)
- [\_tag (property)](#_tag-property-15)
- [\_tag (property)](#_tag-property-16)
- [Mixed (interface)](#mixed-interface)
- [NullC (interface)](#nullc-interface)
- [NullType (class)](#nulltype-class)
- [\_tag (property)](#_tag-property-16)
- [\_tag (property)](#_tag-property-17)
- [NumberC (interface)](#numberc-interface)
- [NumberType (class)](#numbertype-class)
- [\_tag (property)](#_tag-property-17)
- [\_tag (property)](#_tag-property-18)
- [OutputOf (type alias)](#outputof-type-alias)
- [OutputOfDictionary (type alias)](#outputofdictionary-type-alias)
- [OutputOfPartialProps (type alias)](#outputofpartialprops-type-alias)
- [OutputOfProps (type alias)](#outputofprops-type-alias)
- [PartialC (interface)](#partialc-interface)
- [PartialType (class)](#partialtype-class)
- [\_tag (property)](#_tag-property-18)
- [\_tag (property)](#_tag-property-19)
- [Props (interface)](#props-interface)
- [ReadonlyArrayC (interface)](#readonlyarrayc-interface)
- [ReadonlyArrayType (class)](#readonlyarraytype-class)
- [\_tag (property)](#_tag-property-19)
- [\_tag (property)](#_tag-property-20)
- [ReadonlyC (interface)](#readonlyc-interface)
- [ReadonlyType (class)](#readonlytype-class)
- [\_tag (property)](#_tag-property-20)
- [\_tag (property)](#_tag-property-21)
- [RecordC (interface)](#recordc-interface)
- [RecursiveType (class)](#recursivetype-class)
- [\_tag (property)](#_tag-property-21)
- [\_tag (property)](#_tag-property-22)
- [type (property)](#type-property)
- [RefinementType (class)](#refinementtype-class)
- [\_tag (property)](#_tag-property-22)
- [\_tag (property)](#_tag-property-23)
- [StringC (interface)](#stringc-interface)
- [StringType (class)](#stringtype-class)
- [\_tag (property)](#_tag-property-23)
- [\_tag (property)](#_tag-property-24)
- [TupleC (interface)](#tuplec-interface)
- [TupleType (class)](#tupletype-class)
- [\_tag (property)](#_tag-property-24)
- [\_tag (property)](#_tag-property-25)
- [TypeC (interface)](#typec-interface)
- [TypeOf (type alias)](#typeof-type-alias)
- [TypeOfDictionary (type alias)](#typeofdictionary-type-alias)
- [TypeOfPartialProps (type alias)](#typeofpartialprops-type-alias)
- [TypeOfProps (type alias)](#typeofprops-type-alias)
- [UndefinedC (interface)](#undefinedc-interface)
- [UndefinedType (class)](#undefinedtype-class)
- [\_tag (property)](#_tag-property-25)
- [\_tag (property)](#_tag-property-26)
- [UnionC (interface)](#unionc-interface)
- [UnionType (class)](#uniontype-class)
- [\_tag (property)](#_tag-property-26)
- [\_tag (property)](#_tag-property-27)
- [UnknownArrayC (interface)](#unknownarrayc-interface)
- [UnknownC (interface)](#unknownc-interface)
- [UnknownRecordC (interface)](#unknownrecordc-interface)
- [UnknownType (class)](#unknowntype-class)
- [\_tag (property)](#_tag-property-27)
- [\_tag (property)](#_tag-property-28)
- [VoidC (interface)](#voidc-interface)
- [VoidType (class)](#voidtype-class)
- [\_tag (property)](#_tag-property-28)
- [\_tag (property)](#_tag-property-29)
- [appendContext](#appendcontext)
- [enum](#enum)
- [exact](#exact)
- [failure](#failure)
- [failures](#failures)
Expand Down Expand Up @@ -1546,6 +1549,28 @@ readonly _tag: "DictionaryType"

Added in v1.0.0

## EnumType (class)

**Signature**

```ts
export declare class EnumType<E> {
constructor(e: E, name?: string)
}
```

Added in v2.3.0

### \_tag (property)

**Signature**

```ts
readonly _tag: "EnumType"
```

Added in v2.3.0

## ExactC (interface)

**Signature**
Expand Down Expand Up @@ -2460,6 +2485,16 @@ export declare const appendContext: (c: Context, key: string, decoder: Decoder<a

Added in v1.0.0

## enum

**Signature**

```ts
export declare const enum: <E extends typeof Enum>(e: E, name?: string) => EnumType<E>
```

Added in v2.3.0

## exact

Strips additional properties
Expand Down
40 changes: 40 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,39 @@ 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]> {
/**
* @since 2.3.0
*/
readonly _tag: 'EnumType' = 'EnumType'
private readonly _enum: E
private readonly _enumValues: Set<string | number>
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 +1904,13 @@ export {
undefinedType as undefined
}

export {
/**
* @since 2.3.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)
})
})
})