Skip to content

Commit

Permalink
Allow fallback for non-field decoders (fixes #29)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlms13 committed May 26, 2019
1 parent 9a4b191 commit 5aa87c2
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 25 deletions.
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ The following changes are currently in the `master` branch but have not been pub

### :rotating_light: Breaking

- `Decode.fallback` doesn't assume you want to work with `field`s, but you can use `Decode.(fallback(field("x", string), "default"))` if you want the old behavior
- `Decode.Pipeline.fallback` has the same new behavior, but it provides `fallbackField` to achieve the old behavior
- `Decode.ParseError.map` has been removed (it wasn't used internally or documented)

### :bug: Bug fixes

- `...AsResult.OfStringNel` actually collects multiple errors now
- `Decode.AsResult.OfStringNel` actually collects multiple errors now

### :sparkles: New features

- `Decode.alt` allows combining decode functions and picking the first success
- `...AsResult.OfStringNel` now includes all of the same `map`, `flatMap`, etc functions for decoders
- `Decode.AsResult.OfStringNel` now includes all of the same `map`, `flatMap`, etc functions for decoders

### :heavy_check_mark: Code quality

- Reorganize tests so that `...AsOption` tests decoders pass-vs-fail, `...AsResult` tests failure reporting
- Reorganize tests so that `Decode_AsOption` tests decoders pass-vs-fail, `Decode_AsResult_*` tests failure reporting
- Test coverage has increased to 100%
- Internally, many functions were re-written to use `map`, `flatMap`, `alt`, etc on the functions themselves, rather than running the decoders and transforming the output
- Internally, many functions were re-written to use `map`, `flatMap`, `alt`, etc on the decoders themselves, rather than running the decoders and transforming the output
- `Js.Dict.key` was changed to `string` in interface files for better editor suggestions

## 0.5.1 (May 22, 2019)
Expand Down
15 changes: 8 additions & 7 deletions src/DecodeBase.re
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ module DecodeBase = (T: TransformError, M: MONAD with type t('a) = T.t('a)) => {
| Some(v) => (_ => optional(decode, v)),
);

let fallback = (name, decode, recovery) =>
alt(field(name, decode), pure(recovery));
let fallback = (decode, recovery) => alt(decode, pure(recovery));

let tuple = ((fieldA, decodeA), (fieldB, decodeB)) =>
map2(
Expand All @@ -199,15 +198,16 @@ module DecodeBase = (T: TransformError, M: MONAD with type t('a) = T.t('a)) => {

let pipe = (a, b, json) => map2((|>), a, b, json);

let field = (name, decode) => pipe(field(name, decode));
let optionalField = (name, decode) => pipe(optionalField(name, decode));

let at = (fields, decode) => pipe(at(fields, decode));
let fallbackField = (name, decode, recovery) =>
pipe(fallback(field(name, decode), recovery));

let optionalField = (name, decode) => pipe(optionalField(name, decode));
let field = (name, decode) => pipe(field(name, decode));

let fallback = (name, decode, alt) => pipe(fallback(name, decode, alt));
let at = (fields, decode) => pipe(at(fields, decode));

let hardcoded = v => pipe(succeed(v));
let hardcoded = v => pipe(pure(v));

/**
* `run` takes a decoder and some json, and it passes that json to the
Expand Down Expand Up @@ -241,5 +241,6 @@ module DecodeBase = (T: TransformError, M: MONAD with type t('a) = T.t('a)) => {
let tuple = tuple;
let dict = dict;
let oneOf = oneOf;
let fallback = fallback;
};
};
6 changes: 4 additions & 2 deletions src/Decode_AsOption.rei
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ let field: (string, Js.Json.t => option('a), Js.Json.t) => option('a);
let optionalField:
(string, Js.Json.t => option('a), Js.Json.t) => option(option('a));

let fallback: (string, Js.Json.t => option('a), 'a, Js.Json.t) => option('a);
let fallback: (Js.Json.t => option('a), 'a, Js.Json.t) => option('a);

let oneOf:
(Js.Json.t => option('a), list(Js.Json.t => option('a)), Js.Json.t) =>
Expand Down Expand Up @@ -126,7 +126,7 @@ module Pipeline: {
) =>
option('b);

let fallback:
let fallbackField:
(
string,
Js.Json.t => option('a),
Expand All @@ -136,6 +136,8 @@ module Pipeline: {
) =>
option('b);

let fallback: (Js.Json.t => option('a), 'a, Js.Json.t) => option('a);

let hardcoded:
('a, Js.Json.t => option('a => 'c), Js.Json.t) => option('c);

Expand Down
11 changes: 9 additions & 2 deletions src/Decode_AsResult_OfParseError.rei
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ let optionalField:

let fallback:
(
string,
Js.Json.t => Belt.Result.t('a, Decode_ParseError.failure),
'a,
Js.Json.t
Expand Down Expand Up @@ -196,7 +195,7 @@ module Pipeline: {
) =>
Belt.Result.t('b, Decode_ParseError.failure);

let fallback:
let fallbackField:
(
string,
Js.Json.t => Belt.Result.t('a, Decode_ParseError.failure),
Expand All @@ -206,6 +205,14 @@ module Pipeline: {
) =>
Belt.Result.t('b, Decode_ParseError.failure);

let fallback:
(
Js.Json.t => Belt.Result.t('a, Decode_ParseError.failure),
'a,
Js.Json.t
) =>
Belt.Result.t('a, Decode_ParseError.failure);

let hardcoded:
(
'a,
Expand Down
17 changes: 10 additions & 7 deletions src/Decode_AsResult_OfStringNel.rei
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,7 @@ let optionalField:
Belt.Result.t(option('a), NonEmptyList.t(string));

let fallback:
(
string,
Js.Json.t => Belt.Result.t('a, NonEmptyList.t(string)),
'a,
Js.Json.t
) =>
(Js.Json.t => Belt.Result.t('a, NonEmptyList.t(string)), 'a, Js.Json.t) =>
Belt.Result.t('a, NonEmptyList.t(string));

let oneOf:
Expand Down Expand Up @@ -195,7 +190,7 @@ module Pipeline: {
) =>
Belt.Result.t('b, NonEmptyList.t(string));

let fallback:
let fallbackField:
(
string,
Js.Json.t => Belt.Result.t('a, NonEmptyList.t(string)),
Expand All @@ -205,6 +200,14 @@ module Pipeline: {
) =>
Belt.Result.t('b, NonEmptyList.t(string));

let fallback:
(
Js.Json.t => Belt.Result.t('a, NonEmptyList.t(string)),
'a,
Js.Json.t
) =>
Belt.Result.t('a, NonEmptyList.t(string));

let hardcoded:
(
'a,
Expand Down
10 changes: 7 additions & 3 deletions test/Decode_AsOption_test.re
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,16 @@ describe("Decode with alternatives/fallbacks", () => {
);

test("fallback (used on missing field)", () =>
expect(Decode.(fallback("x", boolean, false, Sample.jsonDictEmpty)))
expect(
Decode.(fallback(field("x", boolean), false, Sample.jsonDictEmpty)),
)
|> toEqual(Some(false))
);

test("fallback (used on invalid inner data)", () =>
expect(Decode.(fallback("title", intFromNumber, 1, Sample.jsonJobCeo)))
expect(
Decode.(fallback(field("title", intFromNumber), 1, Sample.jsonJobCeo)),
)
|> toEqual(Some(1))
);
});
Expand Down Expand Up @@ -382,7 +386,7 @@ describe("Decode records", () => {
Decode.Pipeline.(
succeed(Sample.makeJob)
|> hardcoded("Title")
|> fallback("x", string, "Company")
|> fallbackField("x", string, "Company")
|> at(["job", "manager", "job", "startDate"], date)
|> hardcoded(None)
);
Expand Down

0 comments on commit 5aa87c2

Please sign in to comment.