diff --git a/docs/modules/NonEmptyString.ts.md b/docs/modules/NonEmptyString.ts.md index adb2c0d..023f3a5 100644 --- a/docs/modules/NonEmptyString.ts.md +++ b/docs/modules/NonEmptyString.ts.md @@ -1,6 +1,6 @@ --- title: NonEmptyString.ts -nav_order: 19 +nav_order: 20 parent: Modules --- diff --git a/docs/modules/NumberFromString.ts.md b/docs/modules/NumberFromString.ts.md index 808479f..160b835 100644 --- a/docs/modules/NumberFromString.ts.md +++ b/docs/modules/NumberFromString.ts.md @@ -1,6 +1,6 @@ --- title: NumberFromString.ts -nav_order: 20 +nav_order: 21 parent: Modules --- diff --git a/docs/modules/UUID.ts.md b/docs/modules/UUID.ts.md index d26af6f..4d702b5 100644 --- a/docs/modules/UUID.ts.md +++ b/docs/modules/UUID.ts.md @@ -1,6 +1,6 @@ --- title: UUID.ts -nav_order: 27 +nav_order: 29 parent: Modules --- diff --git a/docs/modules/mapFromEntries.ts.md b/docs/modules/mapFromEntries.ts.md new file mode 100644 index 0000000..adb561a --- /dev/null +++ b/docs/modules/mapFromEntries.ts.md @@ -0,0 +1,44 @@ +--- +title: mapFromEntries.ts +nav_order: 17 +parent: Modules +--- + +# mapFromEntries overview + +Added in v0.5.18 + +--- + +

Table of contents

+ +- [MapFromEntriesC (interface)](#mapfromentriesc-interface) +- [mapFromEntries](#mapfromentries) + +--- + +# MapFromEntriesC (interface) + +**Signature** + +```ts +export interface MapFromEntriesC + extends t.Type, t.TypeOf>, Array<[t.OutputOf, t.OutputOf]>, unknown> {} +``` + +Added in v0.5.18 + +# mapFromEntries + +**Signature** + +```ts +export function mapFromEntries( + keyCodec: K, + KO: Ord>, + valueCodec: V, + name: string = `Map<${keyCodec.name}, ${valueCodec.name}>` +): MapFromEntriesC { ... } +``` + +Added in v0.5.18 diff --git a/docs/modules/mapOutput.ts.md b/docs/modules/mapOutput.ts.md index 98eaf42..6d8de00 100644 --- a/docs/modules/mapOutput.ts.md +++ b/docs/modules/mapOutput.ts.md @@ -1,6 +1,6 @@ --- title: mapOutput.ts -nav_order: 17 +nav_order: 18 parent: Modules --- diff --git a/docs/modules/nonEmptyArray.ts.md b/docs/modules/nonEmptyArray.ts.md index fb74f9c..3ce3a7d 100644 --- a/docs/modules/nonEmptyArray.ts.md +++ b/docs/modules/nonEmptyArray.ts.md @@ -1,6 +1,6 @@ --- title: nonEmptyArray.ts -nav_order: 18 +nav_order: 19 parent: Modules --- diff --git a/docs/modules/option.ts.md b/docs/modules/option.ts.md index b1dbac5..74ea05a 100644 --- a/docs/modules/option.ts.md +++ b/docs/modules/option.ts.md @@ -1,6 +1,6 @@ --- title: option.ts -nav_order: 21 +nav_order: 22 parent: Modules --- diff --git a/docs/modules/optionFromNullable.ts.md b/docs/modules/optionFromNullable.ts.md index 8dc3728..cba2606 100644 --- a/docs/modules/optionFromNullable.ts.md +++ b/docs/modules/optionFromNullable.ts.md @@ -1,6 +1,6 @@ --- title: optionFromNullable.ts -nav_order: 22 +nav_order: 23 parent: Modules --- diff --git a/docs/modules/readonlyMapFromEntries.ts.md b/docs/modules/readonlyMapFromEntries.ts.md new file mode 100644 index 0000000..81201fc --- /dev/null +++ b/docs/modules/readonlyMapFromEntries.ts.md @@ -0,0 +1,44 @@ +--- +title: readonlyMapFromEntries.ts +nav_order: 24 +parent: Modules +--- + +# readonlyMapFromEntries overview + +Added in v0.5.18 + +--- + +

Table of contents

+ +- [ReadonlyMapFromEntriesC (interface)](#readonlymapfromentriesc-interface) +- [readonlyMapFromEntries](#readonlymapfromentries) + +--- + +# ReadonlyMapFromEntriesC (interface) + +**Signature** + +```ts +export interface ReadonlyMapFromEntriesC + extends t.Type, t.TypeOf>, ReadonlyArray<[t.OutputOf, t.OutputOf]>, unknown> {} +``` + +Added in v0.5.18 + +# readonlyMapFromEntries + +**Signature** + +```ts +export function readonlyMapFromEntries( + keyCodec: K, + KO: Ord>, + valueCodec: V, + name: string = `ReadonlyMap<${keyCodec.name}, ${valueCodec.name}>` +): ReadonlyMapFromEntriesC { ... } +``` + +Added in v0.5.18 diff --git a/docs/modules/readonlyNonEmptyArray.ts.md b/docs/modules/readonlyNonEmptyArray.ts.md index b67520b..c296f82 100644 --- a/docs/modules/readonlyNonEmptyArray.ts.md +++ b/docs/modules/readonlyNonEmptyArray.ts.md @@ -1,6 +1,6 @@ --- title: readonlyNonEmptyArray.ts -nav_order: 23 +nav_order: 25 parent: Modules --- diff --git a/docs/modules/readonlySetFromArray.ts.md b/docs/modules/readonlySetFromArray.ts.md index b8da047..bc6d5ac 100644 --- a/docs/modules/readonlySetFromArray.ts.md +++ b/docs/modules/readonlySetFromArray.ts.md @@ -1,6 +1,6 @@ --- title: readonlySetFromArray.ts -nav_order: 24 +nav_order: 26 parent: Modules --- diff --git a/docs/modules/regexp.ts.md b/docs/modules/regexp.ts.md index 7611cfa..ccc0477 100644 --- a/docs/modules/regexp.ts.md +++ b/docs/modules/regexp.ts.md @@ -1,6 +1,6 @@ --- title: regexp.ts -nav_order: 25 +nav_order: 27 parent: Modules --- diff --git a/docs/modules/setFromArray.ts.md b/docs/modules/setFromArray.ts.md index 831c088..e7df609 100644 --- a/docs/modules/setFromArray.ts.md +++ b/docs/modules/setFromArray.ts.md @@ -1,6 +1,6 @@ --- title: setFromArray.ts -nav_order: 26 +nav_order: 28 parent: Modules --- diff --git a/docs/modules/withEncode.ts.md b/docs/modules/withEncode.ts.md index 438d697..61dc08d 100644 --- a/docs/modules/withEncode.ts.md +++ b/docs/modules/withEncode.ts.md @@ -1,6 +1,6 @@ --- title: withEncode.ts -nav_order: 28 +nav_order: 30 parent: Modules --- diff --git a/docs/modules/withFallback.ts.md b/docs/modules/withFallback.ts.md index ae09957..85ea090 100644 --- a/docs/modules/withFallback.ts.md +++ b/docs/modules/withFallback.ts.md @@ -1,6 +1,6 @@ --- title: withFallback.ts -nav_order: 29 +nav_order: 31 parent: Modules --- diff --git a/docs/modules/withMessage.ts.md b/docs/modules/withMessage.ts.md index 257c99e..8a940be 100644 --- a/docs/modules/withMessage.ts.md +++ b/docs/modules/withMessage.ts.md @@ -1,6 +1,6 @@ --- title: withMessage.ts -nav_order: 30 +nav_order: 32 parent: Modules --- diff --git a/docs/modules/withValidate.ts.md b/docs/modules/withValidate.ts.md index e060f0d..cafd55f 100644 --- a/docs/modules/withValidate.ts.md +++ b/docs/modules/withValidate.ts.md @@ -1,6 +1,6 @@ --- title: withValidate.ts -nav_order: 31 +nav_order: 33 parent: Modules --- diff --git a/src/index.ts b/src/index.ts index 35cb43c..4c0972e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -147,3 +147,13 @@ export * from './withEncode' * @since 0.5.13 */ export * from './BooleanFromNumber' + +/** + * @since 0.5.18 + */ +export * from './mapFromEntries' + +/** + * @since 0.5.18 + */ +export * from './readonlyMapFromEntries' diff --git a/src/mapFromEntries.ts b/src/mapFromEntries.ts new file mode 100644 index 0000000..eefb9a0 --- /dev/null +++ b/src/mapFromEntries.ts @@ -0,0 +1,60 @@ +/** + * @since 0.5.18 + */ +import * as A from 'fp-ts/lib/Array' +import { chain } from 'fp-ts/lib/Either' +import { fromFoldable, toArray } from 'fp-ts/lib/Map' +import { Ord } from 'fp-ts/Ord' +import { getLastSemigroup } from 'fp-ts/lib/Semigroup' +import { pipe, Predicate } from 'fp-ts/function' +import * as t from 'io-ts' + +interface Next { + readonly done?: boolean + readonly value: A +} + +const every = (pk: Predicate, pv: Predicate) => (ma: Map): boolean => { + const entries = ma.entries() + let e: Next<[K, V]> + while (!(e = entries.next()).done) { + if (pk(e.value[0]) === false || pv(e.value[1]) === false) { + return false + } + } + return true +} + +/** + * @since 0.5.18 + */ +export interface MapFromEntriesC + extends t.Type, t.TypeOf>, Array<[t.OutputOf, t.OutputOf]>, unknown> {} + +/** + * @since 0.5.18 + */ +export function mapFromEntries( + keyCodec: K, + KO: Ord>, + valueCodec: V, + name: string = `Map<${keyCodec.name}, ${valueCodec.name}>` +): MapFromEntriesC { + const arr = t.array(t.tuple([keyCodec, valueCodec])) + const toArrayO = toArray(KO) + const fromArrayO = fromFoldable(KO, getLastSemigroup>(), A.Foldable) + const everyO = every(keyCodec.is, valueCodec.is) + return new t.Type( + name, + (u): u is Map, t.TypeOf> => u instanceof Map && everyO(u), + (u, c) => + pipe( + arr.validate(u, c), + chain(as => { + const map = fromArrayO(as) + return map.size !== as.length ? t.failure(u, c) : t.success(map) + }) + ), + a => arr.encode(toArrayO(a)) + ) +} diff --git a/src/readonlyMapFromEntries.ts b/src/readonlyMapFromEntries.ts new file mode 100644 index 0000000..b2b3e80 --- /dev/null +++ b/src/readonlyMapFromEntries.ts @@ -0,0 +1,24 @@ +/** + * @since 0.5.18 + */ +import { Ord } from 'fp-ts/lib/Ord' +import * as t from 'io-ts' +import { mapFromEntries } from './mapFromEntries' + +/** + * @since 0.5.18 + */ +export interface ReadonlyMapFromEntriesC + extends t.Type, t.TypeOf>, ReadonlyArray<[t.OutputOf, t.OutputOf]>, unknown> {} + +/** + * @since 0.5.18 + */ +export function readonlyMapFromEntries( + keyCodec: K, + KO: Ord>, + valueCodec: V, + name: string = `ReadonlyMap<${keyCodec.name}, ${valueCodec.name}>` +): ReadonlyMapFromEntriesC { + return mapFromEntries(keyCodec, KO, valueCodec, name) as any +} diff --git a/test/mapFromEntries.ts b/test/mapFromEntries.ts new file mode 100644 index 0000000..5f73533 --- /dev/null +++ b/test/mapFromEntries.ts @@ -0,0 +1,55 @@ +import * as assert from 'assert' +import { pipe } from 'fp-ts/function' +import { Ord, ordString, contramap } from 'fp-ts/Ord' +import * as t from 'io-ts' +import { assertSuccess, assertFailure } from './helpers' + +import { mapFromEntries } from '../src/mapFromEntries' + +describe('mapFromEntries', () => { + const K = t.type({ a: t.string }) + const KO: Ord> = pipe( + ordString, + contramap(m => m.a) + ) + const C = t.type({ b: t.number }) + const T = mapFromEntries(K, KO, C) + + it('name', () => { + const T = mapFromEntries(K, KO, C, 'T') + assert.strictEqual(T.name, 'T') + }) + + it('is', () => { + assert.strictEqual(T.is(new Map()), true) + assert.strictEqual(T.is(new Map([[{ a: 'a' }, { b: 1 }]])), true) + assert.strictEqual(T.is(null), false) + assert.strictEqual(T.is(undefined), false) + assert.strictEqual(T.is({}), false) + assert.strictEqual(T.is([]), false) + assert.strictEqual(T.is(new Map([['a', 'b']])), false) + assert.strictEqual(T.is(new Map([['a', { b: 1 }]])), false) + assert.strictEqual(T.is(new Map([[{ a: 'a' }, 1]])), false) + }) + + it('decode', () => { + assertSuccess(T.decode([]), new Map()) + assertSuccess( + T.decode([[{ a: '1' }, { b: 1 }], [{ a: '2' }, { b: 2 }]]), + new Map([[{ a: '1' }, { b: 1 }], [{ a: '2' }, { b: 2 }]]) + ) + assertFailure( + T, + [[{ a: '1' }, { b: 1 }], [{ a: '1' }, { b: 2 }]], + ['Invalid value [[{"a":"1"},{"b":1}],[{"a":"1"},{"b":2}]] supplied to : Map<{ a: string }, { b: number }>'] + ) + }) + + it('encode', () => { + assert.deepStrictEqual(T.encode(new Map()), []) + assert.deepStrictEqual(T.encode(new Map([[{ a: '1' }, { b: 1 }], [{ a: '2' }, { b: 2 }]])), [ + [{ a: '1' }, { b: 1 }], + [{ a: '2' }, { b: 2 }] + ]) + }) +}) diff --git a/test/readonlyMapFromEntries.ts b/test/readonlyMapFromEntries.ts new file mode 100644 index 0000000..f21a470 --- /dev/null +++ b/test/readonlyMapFromEntries.ts @@ -0,0 +1,37 @@ +import * as assert from 'assert' +import { pipe } from 'fp-ts/function' +import { Ord, ordString, contramap } from 'fp-ts/Ord' +import * as t from 'io-ts' +import { assertSuccess, assertFailure } from './helpers' + +import { readonlyMapFromEntries } from '../src/readonlyMapFromEntries' + +describe('readonlyMapFromEntries', () => { + const K = t.type({ a: t.string }) + const KO: Ord> = pipe( + ordString, + contramap(m => m.a) + ) + const C = t.type({ b: t.number }) + const T = readonlyMapFromEntries(K, KO, C) + + it('name', () => { + const T = readonlyMapFromEntries(K, KO, C, 'T') + assert.strictEqual(T.name, 'T') + }) + + it('decode', () => { + assertSuccess(T.decode([]), new Map()) + assertSuccess( + T.decode([[{ a: '1' }, { b: 1 }], [{ a: '2' }, { b: 2 }]]), + new Map([[{ a: '1' }, { b: 1 }], [{ a: '2' }, { b: 2 }]]) + ) + assertFailure( + T, + [[{ a: '1' }, { b: 1 }], [{ a: '1' }, { b: 2 }]], + [ + 'Invalid value [[{"a":"1"},{"b":1}],[{"a":"1"},{"b":2}]] supplied to : ReadonlyMap<{ a: string }, { b: number }>' + ] + ) + }) +})