Repository: gcanti/io-ts (ts)

Issue:
Bigint codec
## 🚀 Feature request

### Current Behavior

No `bigint` codec.

### Desired Behavior

Having a `bigint` codec available, like there's for other primitives (`string`, `number`, etc.).

### Suggested Solution

Add a `bigint` codec to the library.

### Who does this impact? Who is this for?

People who would like to implement codecs on top of bigint, or at least that's my case.

### Describe alternatives you've considered

Using my own implementation.

### Your environment

| Software   | Version(s) |
| ---------- | ---------- |
| io-ts      | 2.0.0 |
| fp-ts      | 2.0.5 |
| TypeScript | 3.5.3 |
| Node | 13.6.0 |


Pull request description:
WIP: Add codec for bigint primitive
Currently not passing a TSLint rule based on an obsolete ESLint rule which is not aware of the bigint primitive.

Closes #410 

Patch:
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index f4f3db8..c5704d3 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -16,6 +16,7 @@ Added in v1.0.0
 - [~~AnyC~~ (interface)](#anyc-interface)
 - [AnyProps (interface)](#anyprops-interface)
 - [ArrayC (interface)](#arrayc-interface)
+- [BigIntC (interface)](#bigintc-interface)
 - [BooleanC (interface)](#booleanc-interface)
 - [Brand (interface)](#brand-interface)
 - [BrandC (interface)](#brandc-interface)
@@ -88,6 +89,7 @@ Added in v1.0.0
 - [AnyDictionaryType (class)](#anydictionarytype-class)
 - [~~AnyType~~ (class)](#anytype-class)
 - [ArrayType (class)](#arraytype-class)
+- [BigIntType (class)](#biginttype-class)
 - [BooleanType (class)](#booleantype-class)
 - [DictionaryType (class)](#dictionarytype-class)
 - [ExactType (class)](#exacttype-class)
@@ -123,6 +125,7 @@ Added in v1.0.0
 - [UnknownRecord](#unknownrecord)
 - [appendContext](#appendcontext)
 - [array](#array)
+- [bigint](#bigint)
 - [boolean](#boolean)
 - [brand](#brand)
 - [exact](#exact)
@@ -212,6 +215,16 @@ export interface ArrayC<C extends Mixed> extends ArrayType<C, Array<TypeOf<C>>,
 
 Added in v1.5.3
 
+# BigIntC (interface)
+
+**Signature**
+
+```ts
+export interface BigIntC extends BigIntType {}
+```
+
+Added in v2.1.0
+
 # BooleanC (interface)
 
 **Signature**
@@ -1093,6 +1106,19 @@ export class ArrayType<C, A, O, I> {
 
 Added in v1.0.0
 
+# BigIntType (class)
+
+**Signature**
+
+```ts
+export class BigIntType {
+  constructor() { ... }
+  ...
+}
+```
+
+Added in v2.1.0
+
 # BooleanType (class)
 
 **Signature**
@@ -1642,6 +1668,16 @@ export const array = <C extends Mixed>(codec: C, name: string = `Array<${codec.n
 
 Added in v1.0.0
 
+# bigint
+
+**Signature**
+
+```ts
+export const bigint: BigIntC = ...
+```
+
+Added in v2.1.0
+
 # boolean
 
 **Signature**
diff --git a/src/index.ts b/src/index.ts
index d1e5274..32bfdd8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -363,6 +363,32 @@ export interface NumberC extends NumberType {}
  */
 export const number: NumberC = new NumberType()
 
+/**
+ * @since 2.1.0
+ */
+export class BigIntType extends Type<bigint> {
+  readonly _tag: 'BigIntType' = 'BigIntType'
+  constructor() {
+    super(
+      'bigint',
+      // tslint:disable-next-line: valid-typeof
+      (u): u is bigint => typeof u === 'bigint',
+      (u, c) => (this.is(u) ? success(u) : failure(u, c)),
+      identity
+    )
+  }
+}
+
+/**
+ * @since 2.1.0
+ */
+export interface BigIntC extends BigIntType {}
+
+/**
+ * @since 2.1.0
+ */
+export const bigint: BigIntC = new BigIntType()
+
 /**
  * @since 1.0.0
  */
diff --git a/tsconfig.json b/tsconfig.json
index f3a809a..6929892 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,7 +13,7 @@
     "moduleResolution": "node",
     "forceConsistentCasingInFileNames": true,
     "stripInternal": true,
-    "lib": ["es2015"]
+    "lib": ["es2015", "ESNext.BigInt"]
   },
   "include": ["./src/**/*"]
 }


Test patch:
diff --git a/test/default-types.ts b/test/default-types.ts
index ac4767c..2b4ca6b 100644
--- a/test/default-types.ts
+++ b/test/default-types.ts
@@ -114,6 +114,29 @@ describe('boolean', () => {
   })
 })
 
+describe('bigint', () => {
+  const T = t.bigint
+  it('should decode bigint values', () => {
+    assertSuccess(T.decode(BigInt(0)))
+    assertSuccess(T.decode(BigInt(15)))
+    const decodedBigNumber = T.decode(BigInt(Number.MAX_SAFE_INTEGER) + BigInt(4))
+    assertSuccess(decodedBigNumber)
+    if (decodedBigNumber._tag === 'Right') {
+      assert.equal(decodedBigNumber.right.toString(), '9007199254740995')
+    }
+  })
+
+  it('should not decode non-bigint values', () => {
+    assertFailure(T, true, ['Invalid value true supplied to : bigint'])
+    assertFailure(T, 'test', ['Invalid value "test" supplied to : bigint'])
+    assertFailure(T, 123, ['Invalid value 123 supplied to : bigint'])
+    assertFailure(T, {}, ['Invalid value {} supplied to : bigint'])
+    assertFailure(T, [], ['Invalid value [] supplied to : bigint'])
+    assertFailure(T, null, ['Invalid value null supplied to : bigint'])
+    assertFailure(T, undefined, ['Invalid value undefined supplied to : bigint'])
+  })
+})
+
 describe('Integer', () => {
   it('should validate integers', () => {
     // tslint:disable-next-line: deprecation
diff --git a/test/helpers.ts b/test/helpers.ts
index 539adcd..be532e1 100644
--- a/test/helpers.ts
+++ b/test/helpers.ts
@@ -31,6 +31,7 @@ export function assertSuccess<T>(result: t.Validation<T>, expected?: T): void {
         if (expected !== undefined) {
           assert.deepStrictEqual(a, expected)
         }
+        return true
       }
     )
   )