Skip to content

Commit fc14fe0

Browse files
author
Elias Mulhall
committed
Add decoder method for testing constraints on a decoder
1 parent 875a42f commit fc14fe0

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

src/decoder.ts

+26
Original file line numberDiff line numberDiff line change
@@ -666,4 +666,30 @@ export class Decoder<A> {
666666
new Decoder<B>((json: any) =>
667667
Result.andThen(value => f(value).decode(json), this.decode(json))
668668
);
669+
670+
/**
671+
* Add constraints to a decoder _without_ changing the resulting type. The
672+
* `test` argument is a predicate function which returns true for valid
673+
* inputs. When `test` fails on an input, the decoder fails with the given
674+
* `errorMessage`.
675+
*
676+
* ```
677+
* const chars = (length: number): Decoder<string> =>
678+
* string().where(
679+
* (s: string) => s.length === length,
680+
* `expected a string of length ${length}`
681+
* );
682+
*
683+
* chars(5).run('12345')
684+
* // => {ok: true, result: '12345'}
685+
*
686+
* chars(2).run('HELLO')
687+
* // => {ok: false, error: {... message: 'expected a string of length 2'}}
688+
*
689+
* chars(12).run(true)
690+
* // => {ok: false, error: {... message: 'expected a string, got a boolean'}}
691+
* ```
692+
*/
693+
where = (test: (value: A) => boolean, errorMessage: string): Decoder<A> =>
694+
this.andThen((value: A) => (test(value) ? Decoder.succeed(value) : Decoder.fail(errorMessage)));
669695
}

test/json-decode.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,47 @@ describe('andThen', () => {
771771
});
772772
});
773773

774+
describe('where', () => {
775+
const chars = (length: number): Decoder<string> =>
776+
string().where((s: string) => s.length === length, `expected a string of length ${length}`);
777+
778+
const range = (min: number, max: number): Decoder<number> =>
779+
number().where(
780+
(n: number) => n >= min && n <= max,
781+
`expected a number between ${min} and ${max}`
782+
);
783+
784+
it('can test for strings of a given length', () => {
785+
expect(chars(7).run('7777777')).toEqual({ok: true, result: '7777777'});
786+
787+
expect(chars(7).run('666666')).toMatchObject({
788+
ok: false,
789+
error: {message: 'expected a string of length 7'}
790+
});
791+
});
792+
793+
it('can test for numbers in a given range', () => {
794+
expect(range(1, 9).run(7)).toEqual({ok: true, result: 7});
795+
796+
expect(range(1, 9).run(12)).toMatchObject({
797+
ok: false,
798+
error: {message: 'expected a number between 1 and 9'}
799+
});
800+
});
801+
802+
it('reports when the base decoder fails', () => {
803+
expect(chars(7).run(false)).toMatchObject({
804+
ok: false,
805+
error: {message: 'expected a string, got a boolean'}
806+
});
807+
808+
expect(range(0, 1).run(null)).toMatchObject({
809+
ok: false,
810+
error: {message: 'expected a number, got null'}
811+
});
812+
});
813+
});
814+
774815
describe('Result', () => {
775816
describe('can run a decoder with default value', () => {
776817
const decoder = number();

0 commit comments

Comments
 (0)