Skip to content

Commit 875a42f

Browse files
author
Elias Mulhall
committed
Improve documentation and tests for andThen method
1 parent 0b6fc1c commit 875a42f

File tree

2 files changed

+43
-8
lines changed

2 files changed

+43
-8
lines changed

src/decoder.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -618,10 +618,13 @@ export class Decoder<A> {
618618
* Chain together a sequence of decoders. The first decoder will run, and
619619
* then the function will determine what decoder to run second. If the result
620620
* of the first decoder succeeds then `f` will be applied to the decoded
621-
* value. If it fails the error will propagate through. One use case for
622-
* `andThen` is returning a custom error message.
621+
* value. If it fails the error will propagate through.
623622
*
624-
* Example:
623+
* This is a very powerful method -- it can act as both the `map` and `where`
624+
* methods, can improve error messages for edge cases, and can be used to
625+
* make a decoder for custom types.
626+
*
627+
* Example of adding an error message:
625628
* ```
626629
* const versionDecoder = valueAt(['version'], number());
627630
* const infoDecoder3 = object({a: boolean()});
@@ -642,13 +645,22 @@ export class Decoder<A> {
642645
* // =>
643646
* // {
644647
* // ok: false,
645-
* // error: {
646-
* // ...
647-
* // at: 'input',
648-
* // message: 'Unable to decode info, version 5 is not supported.'
649-
* // }
648+
* // error: {... message: 'Unable to decode info, version 5 is not supported.'}
650649
* // }
651650
* ```
651+
*
652+
* Example of decoding a custom type:
653+
* ```
654+
* // nominal type for arrays with a length of at least one
655+
* type NonEmptyArray<T> = T[] & { __nonEmptyArrayBrand__: void };
656+
*
657+
* const nonEmptyArrayDecoder = <T>(values: Decoder<T>): Decoder<NonEmptyArray<T>> =>
658+
* array(values).andThen(arr =>
659+
* arr.length > 0
660+
* ? succeed(createNonEmptyArray(arr))
661+
* : fail(`expected a non-empty array, got an empty array`)
662+
* );
663+
* ```
652664
*/
653665
andThen = <B>(f: (value: A) => Decoder<B>): Decoder<B> =>
654666
new Decoder<B>((json: any) =>

test/json-decode.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,29 @@ describe('andThen', () => {
746746
});
747747
});
748748
});
749+
750+
it('creates decoders for custom types', () => {
751+
type NonEmptyArray<T> = T[] & {__nonEmptyArrayBrand__: void};
752+
const createNonEmptyArray = <T>(arr: T[]): NonEmptyArray<T> => arr as NonEmptyArray<T>;
753+
754+
const nonEmptyArrayDecoder = <T>(values: Decoder<T>): Decoder<NonEmptyArray<T>> =>
755+
array(values).andThen(
756+
arr =>
757+
arr.length > 0
758+
? succeed(createNonEmptyArray(arr))
759+
: fail(`expected a non-empty array, got an empty array`)
760+
);
761+
762+
expect(nonEmptyArrayDecoder(number()).run([1, 2, 3])).toEqual({
763+
ok: true,
764+
result: [1, 2, 3]
765+
});
766+
767+
expect(nonEmptyArrayDecoder(number()).run([])).toMatchObject({
768+
ok: false,
769+
error: {message: 'expected a non-empty array, got an empty array'}
770+
});
771+
});
749772
});
750773

751774
describe('Result', () => {

0 commit comments

Comments
 (0)