Last active
April 22, 2022 19:30
-
-
Save lordnull/0017acfd2a971b4abcd980d4f1e06630 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -module(from_json). | |
| -ifdef(TEST). | |
| -include_lib("eunit/include/eunit.hrl"). | |
| -endif. | |
| -type json_number() :: number(). | |
| -type json_string() :: binary(). | |
| -type json_null() :: null. | |
| -type json_boolean() :: true | false. | |
| -type json_array() :: [ json() ]. | |
| -type json_array(T) :: [ T ]. | |
| -type json_key() :: atom() | binary(). | |
| -type json_object() :: #{ json_key() => json() }. | |
| -type json() :: json_number() | json_string() | json_null() | json_boolean() | json_array() | json_object(). | |
| -type decoder(T) :: fun((json()) -> decode_result(T)). | |
| -type decode_result(T) :: {ok, T} | {error, iolist()}. | |
| -export_type([ json_number/0, json_string/0, json_null/0, json_boolean/0, | |
| json_array/0, json_array/1, json_object/0, json/0, decoder/1 ]). | |
| -export([ decode_string/2, decode_json/2 ]). | |
| -export([ number/0, integer/0, string/0, null/0, boolean/0]). | |
| -export([ array/1, object/1, proplist/1]). | |
| -export([ map/2, field/2, optional_field/3, at/2, index/2 ]). | |
| -export([ nullable/1, maybe/1, one_of/1, as_is/0, ok/1, fail/1]). | |
| -export([ fold_over/2, tuple/2, validate/2]). | |
| -export([ the_atom/1, an_atom/1]). | |
| -export([ and_then/2]). | |
| -spec decode_string(binary(), decoder(T)) -> decode_result(T). | |
| decode_string(String, Decoder) -> | |
| decode_json(jsx:decode(String, [return_maps]), Decoder). | |
| -spec decode_json(json(), decoder(T)) -> decode_result(T). | |
| decode_json(Json, Decoder) -> | |
| Decoder(Json). | |
| -spec number() -> decoder(number()). | |
| number() -> | |
| fun number/1. | |
| -ifdef(TEST). | |
| number_test_() -> | |
| [ fun() -> {ok, 3} = decode_json(3, number()) end | |
| , fun() -> {error, _} = decode_json(null, number()) end | |
| , fun() -> {ok, 27.5} = decode_json(27.5, number()) end | |
| ]. | |
| -endif. | |
| number(Json) when is_integer(Json); is_float(Json) -> | |
| {ok, Json}; | |
| number(_) -> | |
| {error, <<"not number">>}. | |
| -spec integer() -> decoder(integer()). | |
| integer() -> | |
| fun integer/1. | |
| -ifdef(TEST). | |
| integer_test_() -> | |
| [ fun() -> {ok, 67} = decode_json(67, integer()) end | |
| , fun() -> {error, _} = decode_json(35.8, integer()) end | |
| , fun() -> {error, _} = decode_json(2.0, integer()) end | |
| ]. | |
| -endif. | |
| integer(Json) when is_integer(Json) -> | |
| {ok, Json}; | |
| integer(_) -> | |
| {error, <<"not an integer">>}. | |
| -spec string() -> decoder(binary()). | |
| string() -> | |
| fun string/1. | |
| -ifdef(TEST). | |
| string_test_() -> | |
| [ fun() -> {ok, <<"hi!">>} = decode_json(<<"hi!">>, string()) end | |
| , fun() -> {error, _} = decode_json(32, string()) end | |
| , fun() -> {error, _} = decode_json(null, string()) end | |
| ]. | |
| -endif. | |
| string(Json) when is_binary(Json) -> | |
| {ok, Json}; | |
| string(_) -> | |
| {error, <<"not string">>}. | |
| -spec null() -> decoder(undefined). | |
| null() -> | |
| fun null/1. | |
| -ifdef(TEST). | |
| null_test_() -> | |
| [ fun() -> {ok, undefined} = decode_json(null, null()) end | |
| , fun() -> {error, _} = decode_json(<<"hi!">>, null()) end | |
| , fun() -> {error, _} = decode_json(#{}, null()) end | |
| ]. | |
| -endif. | |
| null(null) -> | |
| {ok, undefined}; | |
| null(_) -> | |
| {error, <<"not null">>}. | |
| -spec boolean() -> decoder(boolean()). | |
| boolean() -> | |
| fun boolean/1. | |
| -ifdef(TEST). | |
| boolean_test_() -> | |
| [ fun() -> {ok, true} = decode_json(true, boolean()) end | |
| , fun() -> {ok, false} = decode_json(false, boolean()) end | |
| , fun() -> {error, _} = decode_json(5, boolean()) end | |
| ]. | |
| -endif. | |
| boolean(Json) when Json =:= true; Json =:= false -> | |
| {ok, Json}; | |
| boolean(_) -> | |
| {error, <<"not boolean">>}. | |
| -spec array(decoder(T)) -> decoder([T]). | |
| array(Decoder) -> | |
| fun(Json) -> | |
| array(Json, Decoder) | |
| end. | |
| -ifdef(TEST). | |
| array_test_() -> | |
| [ fun() -> {ok, [1,4,7]} = decode_json([1,4,7], array(number())) end | |
| , fun() -> {ok, [<<"hi">>, <<"yo">>]} = decode_json([<<"hi">>, <<"yo">>], array(string())) end | |
| , fun() -> {error, _} = decode_json([<<"hi">>], array(number())) end | |
| , fun() -> {ok, [false]} = decode_json([false], array(boolean())) end | |
| , fun() -> {error, _} = decode_json(#{}, array(null())) end | |
| ]. | |
| -endif. | |
| array(Json, Validator) when is_list(Json) -> | |
| array_map(Json, Validator, 0, []); | |
| array(_Json, _Validator) -> | |
| {error, <<"not array">>}. | |
| array_map([], _Validator, _Count, Acc) -> | |
| {ok, lists:reverse(Acc)}; | |
| array_map([Hd | Tail], Validator, Count, Acc) -> | |
| case Validator(Hd) of | |
| {ok, NewVal} -> | |
| array_map(Tail, Validator, Count + 1, [NewVal | Acc]); | |
| {error, Wut} -> | |
| {error, [Wut, <<" in array at item ">>, integer_to_list(Count)]} | |
| end. | |
| -spec map(fun((A) -> B), decoder(A)) -> decoder(B). | |
| map(ValueTransform, Decoder) -> | |
| fun(Json) -> | |
| case Decoder(Json) of | |
| {ok, MidValue} -> | |
| {ok, ValueTransform(MidValue)}; | |
| Error -> | |
| Error | |
| end | |
| end. | |
| -ifdef(TEST). | |
| map_test_() -> | |
| [ fun() -> {ok, pants} = decode_json(3, map(fun(_) -> pants end, number())) end | |
| , fun() -> {error, _} = decode_json(null, map(fun(_) -> pants end, number())) end | |
| ]. | |
| -endif. | |
| -spec nullable(decoder(T)) -> decoder(T | undefined). | |
| nullable(Decoder) -> | |
| fun | |
| (null) -> | |
| {ok, undefined}; | |
| (Json) -> | |
| Decoder(Json) | |
| end. | |
| -ifdef(TEST). | |
| nullable_test_() -> | |
| [ fun() -> {ok, undefined} = decode_json(null, nullable(number())) end | |
| , fun() -> {ok, 7} = decode_json(7, nullable(number())) end | |
| , fun() -> {error, _} = decode_json(<<"hi">>, nullable(number())) end | |
| ]. | |
| -endif. | |
| -spec object(#{json_key() => decoder(T)}) -> decoder(#{json_key() => T}). | |
| object(DecodeMap) when is_map(DecodeMap) -> | |
| fun(Json) -> | |
| decode_object(Json, DecodeMap, #{}) | |
| end. | |
| -ifdef(TEST). | |
| object_test_() -> | |
| Input = #{ | |
| <<"int">> => 7, | |
| <<"str">> => <<"goober">>, | |
| <<"bool">> => false, | |
| <<"obj">> => #{} | |
| }, [ | |
| fun() -> | |
| Expected = #{<<"int">> => 7, <<"bool">> => false}, | |
| Decoder = #{<<"int">> => number(), <<"bool">> => boolean()}, | |
| {ok, Expected} = decode_json(Input, object(Decoder)) | |
| end, | |
| fun() -> | |
| Decoder = #{<<"int">> => string()}, | |
| {error, _} = decode_json(Input, object(Decoder)) | |
| end, | |
| fun() -> | |
| Decoder = #{}, | |
| {ok, #{}} = decode_json(Input, object(Decoder)) | |
| end, | |
| fun() -> | |
| Decoder = #{<<"obj">> => object(#{})}, | |
| {ok, #{<<"obj">> := #{}}} = decode_json(Input, object(Decoder)) | |
| end, | |
| fun() -> | |
| Decoder = #{<<"no-field">> => string()}, | |
| {error, _} = decode_json(Input, object(Decoder)) | |
| end ]. | |
| -endif. | |
| decode_object(Json, DecodeMap, AccObj) when is_map(Json), is_map(DecodeMap) -> | |
| DecodeList = maps:to_list(DecodeMap), | |
| decode_object(Json, DecodeList, AccObj); | |
| decode_object(_Json, [], Acc) -> | |
| {ok, Acc}; | |
| decode_object(Json, [{Key, Decoder} | Tail], Acc) when is_map(Json) -> | |
| case (field(Key, Decoder))(Json) of | |
| {ok, Val} -> | |
| decode_object(Json, Tail, Acc#{Key => Val}); | |
| {error, Wut} -> | |
| {error, [<<"invalid value at key ">>, to_json_key(Key), " ", Wut]} | |
| end. | |
| to_json_key(Atom) when is_atom(Atom) -> | |
| atom_to_binary(Atom, utf8); | |
| to_json_key(Binary) when is_binary(Binary) -> | |
| Binary. | |
| -spec proplist(#{json_key() => decoder(T)}) -> decoder([{ json_key(), decoder(T)}]). | |
| proplist(DecodeMap) when is_map(DecodeMap) -> | |
| DecodeList = maps:to_list(DecodeMap), | |
| fun(Json) -> | |
| decode_to_proplist(Json, DecodeList, []) | |
| end. | |
| -ifdef(TEST). | |
| proplist_test_() -> | |
| [ fun() -> | |
| {ok, [{<<"a">>, 3}]} = decode_json(#{<<"a">> => 3}, proplist(#{<<"a">> => number()})) | |
| end | |
| , fun() -> | |
| {ok, []} = decode_json(#{<<"a">> => 3}, proplist(#{})) | |
| end | |
| , fun() -> | |
| {error, _} = decode_json(#{<<"a">> => <<"hi">>}, proplist(#{<<"a">> => number()})) | |
| end ]. | |
| -endif. | |
| decode_to_proplist(Json, _Decoders, _Acc) when not is_map(Json) -> | |
| {error, <<"not an object">>}; | |
| decode_to_proplist(_Json, [], Acc) -> | |
| {ok, Acc}; | |
| decode_to_proplist(Json, [{Key, Decoder} | Tail], Acc) -> | |
| case Decoder(maps:get(Key, Json, undefined)) of | |
| {ok, Val} -> | |
| decode_to_proplist(Json, Tail, [{Key, Val} | Acc]); | |
| {error, Wut} -> | |
| {error, [<<"error at key ">>, to_json_key(Key), " ", Wut]} | |
| end. | |
| -spec field(json_key(), decoder(T)) -> decoder(T). | |
| field(Key, Decoder) -> | |
| fun(Json) -> | |
| decode_field(Json, Key, Decoder) | |
| end. | |
| -ifdef(TEST). | |
| field_test_() -> | |
| [ fun() -> {ok, 3} = decode_json(#{<<"a">> => 3}, field(<<"a">>, number())) end | |
| , fun() -> {error, _} = decode_json(#{<<"a">> => 3}, field(<<"a">>, string())) end | |
| , fun() -> {ok, 7} = decode_json(#{<<"a">> => 7}, field(a, number())) end | |
| , fun() -> {ok, 9} = decode_json(#{a => 9}, field(<<"a">>, number())) end | |
| ]. | |
| -endif. | |
| decode_field(Json, _Key, _Validator) when not is_map(Json) -> | |
| {error, <<"not an object">>}; | |
| decode_field(Json, Key, Validator) when is_atom(Key) -> | |
| BinKey = atom_to_binary(Key, utf8), | |
| case {maps:find(Key, Json), maps:find(BinKey, Json)} of | |
| {error, error} -> | |
| {error, [Key, <<" is missing">>]}; | |
| {{ok, Value}, _} -> | |
| Validator(Value); | |
| {_, {ok, Value}} -> | |
| Validator(Value) | |
| end; | |
| decode_field(Json, Key, Validator) when is_binary(Key) -> | |
| case maps:find(Key, Json) of | |
| error -> | |
| Keys = maps:keys(Json), | |
| AtomKeys = [ K || K <- Keys, is_atom(K) ], | |
| case [ Matching || Matching <- AtomKeys, atom_to_binary(Matching, utf8) =:= Key ] of | |
| [] -> | |
| {error, [Key, <<" is missing">>]}; | |
| [MatchKey | _] -> | |
| Validator(maps:get(MatchKey, Json)) | |
| end; | |
| {ok, Value} -> | |
| Validator(Value) | |
| end. | |
| -spec optional_field(json_key(), T, decoder(T)) -> decoder(T). | |
| optional_field(Key, IfMissing, Decoder) -> | |
| fun(Json) -> | |
| decode_optional_field(Json, Key, IfMissing, Decoder) | |
| end. | |
| -ifdef(TEST). | |
| optional_field_test_() -> | |
| [ fun() -> | |
| {ok, 3} = decode_json(#{}, optional_field(<<"a">>, 3, number())) | |
| end, | |
| fun() -> | |
| {ok, 7} = decode_json(#{<<"a">> => 7}, optional_field(<<"a">>, 3, number())) | |
| end, | |
| fun() -> | |
| {error, _} = decode_json(#{<<"a">> => <<"hi">>}, optional_field(<<"a">>, 3, number())) | |
| end, | |
| fun() -> | |
| {ok, 9} = decode_json(#{<<"a">> => 9}, optional_field(a, 7, number())) | |
| end, | |
| fun() -> | |
| {ok, 5} = decode_json(#{ a => 5}, optional_field(<<"a">>, 7, number())) | |
| end | |
| ]. | |
| -endif. | |
| decode_optional_field(Json, Key, IfMissing, Validator) -> | |
| case decode_field(Json, Key, Validator) of | |
| {error, [_, <<" is missing">>]} -> | |
| {ok, IfMissing}; | |
| Else -> | |
| Else | |
| end. | |
| -spec at([json_key()], decoder(T)) -> decoder(T). | |
| at(KeyPath, Decoder) -> | |
| fun(Json) -> | |
| at(Json, KeyPath, [], Decoder) | |
| end. | |
| -ifdef(TEST). | |
| at_test_() -> | |
| [ fun() -> | |
| {ok, 3} = decode_json(#{a => #{ b => 3}}, at([a,b], number())) | |
| end, | |
| fun() -> | |
| {error, _} = decode_json(3, at([a,b], number())) | |
| end, | |
| fun() -> | |
| {error, _} = decode_json(#{a => #{ b => <<"hi">>}}, at([a,b],number())) | |
| end ]. | |
| -endif. | |
| at(Json, [], _PathWalked, Validator) -> | |
| Validator(Json); | |
| at(Json, [LastPath], PathWalked, Decoder) when is_map(Json) -> | |
| case Decoder(maps:get(LastPath, Json, undefined)) of | |
| {ok, _} = Ok -> | |
| Ok; | |
| {error, Wut} -> | |
| {error, [pretty_print_key([LastPath | PathWalked]), <<" did not pass decoder: ">>, Wut]} | |
| end; | |
| at(Json, [Key | Tail], PathWalked, Validator) when is_map(Json) -> | |
| case maps:get(Key, Json, undefined) of | |
| Obj when is_map(Obj) -> | |
| at(Obj, Tail, [Key | PathWalked], Validator); | |
| _NotObj -> | |
| {error, [pretty_print_key([Key | PathWalked]), <<" is not an object">>]} | |
| end; | |
| at(_Json, _Keys, PathWalked, _Decoder) -> | |
| {error, [pretty_print_key(PathWalked), <<" is not an object">>]}. | |
| pretty_print_key([]) -> | |
| []; | |
| pretty_print_key([Key]) -> | |
| [Key]; | |
| pretty_print_key([H | T]) -> | |
| pretty_print_key(T, [H]). | |
| pretty_print_key([P], Acc) -> | |
| [P, "." | Acc]; | |
| pretty_print_key([P | Tail], Acc) -> | |
| pretty_print_key(Tail, [P, ".", Acc]). | |
| -spec index(non_neg_integer(), decoder(T)) -> decoder(T). | |
| index(Idx, Decoder) -> | |
| fun(Json) -> | |
| index(Json, Idx, Decoder) | |
| end. | |
| -ifdef(TEST). | |
| index_test_() -> | |
| [ fun() -> {error, _} = decode_json([], index(0, number())) end | |
| , fun() -> {ok, 3} = decode_json([1,2,3], index(2, number())) end | |
| , fun() -> {error, _} = decode_json([1,2,<<"hi">>], index(2, number())) end | |
| ]. | |
| -endif. | |
| index([], _Idx, _Decoder) -> | |
| {error, <<"empty list">>}; | |
| index(Json, Idx, _Decoder) when is_list(Json), length(Json) < Idx -> | |
| {error, <<"list too short">>}; | |
| index(Json, Idx, Decoder) when is_list(Json) -> | |
| Decoder(lists:nth(Idx + 1, Json)); | |
| index(_Json, _Idx, _Decoder) -> | |
| {error, <<"not a list">>}. | |
| -spec maybe(decoder(T)) -> decoder(T | undefined). | |
| maybe(Decoder) -> | |
| fun(Json) -> | |
| maybe(Json, Decoder) | |
| end. | |
| -ifdef(TEST). | |
| maybe_test_() -> | |
| [ fun() -> {ok, undefined} = decode_json(3, maybe(string())) end | |
| , fun() -> {ok, 6} = decode_json(6, maybe(number())) end | |
| ]. | |
| -endif. | |
| maybe(Json, Decoder) -> | |
| case Decoder(Json) of | |
| {error, _} -> | |
| {ok, undefined}; | |
| Ok -> | |
| Ok | |
| end. | |
| -spec one_of([decoder(T)]) -> decoder(T). | |
| one_of(Decoders) when is_list(Decoders) -> | |
| fun(Json) -> | |
| one_of(Json, Decoders) | |
| end. | |
| -ifdef(TEST). | |
| one_of_test_() -> | |
| [ fun() -> {ok, 3} = decode_json(3, one_of([number(), string()])) end | |
| , fun() -> {ok, <<"hi">>} = decode_json(<<"hi">>, one_of([number(), string()])) end | |
| , fun() -> {error, _} = decode_json(false, one_of([number(), string()])) end | |
| ]. | |
| -endif. | |
| one_of(_Json, []) -> | |
| {error, <<"no valid conversions">>}; | |
| one_of(Json, [ValidatorHead | Tail]) -> | |
| case ValidatorHead(Json) of | |
| {error, _} -> | |
| one_of(Json, Tail); | |
| {ok, _} = Ok -> | |
| Ok | |
| end. | |
| -spec as_is() -> decoder(json()). | |
| as_is() -> | |
| fun(Json) -> {ok, Json} end. | |
| -ifdef(TEST). | |
| as_is_test() -> | |
| [ fun() -> {ok, 3} = decode_json(3, as_is()) end | |
| , fun() -> {ok, null} = decode_json(null, as_is()) end | |
| ]. | |
| -endif. | |
| -spec ok(T) -> decoder(T). | |
| ok(Value) -> | |
| fun(_) -> {ok, Value} end. | |
| -ifdef(TEST). | |
| ok_test() -> | |
| {ok, {1,5}} = decode_json(false, ok({1,5})). | |
| -endif. | |
| -spec fail(binary()) -> decoder(none()). | |
| fail(Why) -> | |
| fun(_) -> {error, Why} end. | |
| -ifdef(TEST). | |
| fail_test() -> | |
| {error, <<"ugh">>} = decode_json(true, fail(<<"ugh">>)). | |
| -endif. | |
| -spec fold_over(S, [{decoder(T), fun((T,S) -> S)}]) -> decoder(S). | |
| fold_over(InitState, DecodeAndUpdates) when is_list(DecodeAndUpdates) -> | |
| fun(Json) -> | |
| fold_over(Json, InitState, 1, DecodeAndUpdates) | |
| end. | |
| -ifdef(TEST). | |
| fold_over_test_() -> | |
| UDs = [{number(), fun(N,S) -> N * S end} || _ <- lists:seq(1,5)], | |
| [ fun() -> | |
| {ok, 32} = decode_json(2, fold_over(1, UDs)) | |
| end, | |
| fun() -> | |
| {error, _} = decode_json(1, fold_over(1, [{string(), fun(_, _) -> ok end} | UDs])) | |
| end ]. | |
| -endif. | |
| fold_over(_Json, FinalState, _Step, []) -> | |
| {ok, FinalState}; | |
| fold_over(Json, State, Step, [{Decode, Update} | Tail]) -> | |
| case Decode(Json) of | |
| {ok, Value} -> | |
| fold_over(Json, Update(Value, State), Step + 1, Tail); | |
| {error, Wut} -> | |
| {error, [<<"fold failed at step ">>, integer_to_list(Step), <<"due to ">>, Wut]} | |
| end. | |
| -spec tuple(tuple(), [{pos_integer(), decoder(_)}]) -> decoder(tuple()). | |
| tuple(InitialRecord, Decoders) -> | |
| fun(Json) -> | |
| tuple(Json, InitialRecord, Decoders) | |
| end. | |
| -ifdef(TEST). | |
| tuple_test_() -> | |
| [ fun() -> | |
| {ok, {r, 7}} = decode_json(#{f => 7}, tuple({r, undefined}, [{2, field(f, number())}])) | |
| end, | |
| fun() -> | |
| {ok, {r, 7}} = decode_json(7, tuple({r, undefined}, [{2, number()}])) | |
| end, | |
| fun() -> | |
| {error, _} = decode_json(#{f => <<"hi">>}, tuple({r, undefined}, [{2, number()}])) | |
| end ]. | |
| -endif. | |
| tuple(_Json, Record, []) -> | |
| {ok, Record}; | |
| tuple(Json, Record, [{FieldIdx, Decoder} | Tail]) -> | |
| case Decoder(Json) of | |
| {ok, Value} -> | |
| tuple(Json, setelement(FieldIdx, Record, Value), Tail); | |
| {error, Wut} -> | |
| {error, [<<"for field idx ">>, integer_to_list(FieldIdx), " ", Wut]} | |
| end. | |
| -spec validate(fun((A) -> decode_result(B)), decoder(A)) -> decoder(B). | |
| validate(Map, Decoder) -> | |
| fun(Json) -> | |
| validate(Json, Map, Decoder) | |
| end. | |
| -ifdef(TEST). | |
| validate_test_() -> | |
| Validate = fun(N) when N < 0 -> {error, too_low}; | |
| (N) when N < 5 -> {ok, low}; | |
| (N) when is_integer(N) -> {ok, high} | |
| end, | |
| [ fun() -> {ok, low} = decode_json(1, validate(Validate, number())) end | |
| , fun() -> {ok, high} = decode_json(200, validate(Validate, number())) end | |
| , fun() -> {error, _} = decode_json(<<"hi">>, validate(Validate, number())) end | |
| , fun() -> {error, too_low} = decode_json(-70, validate(Validate, number())) end | |
| ]. | |
| -endif. | |
| validate(Json, Map, Decoder) -> | |
| case Decoder(Json) of | |
| {ok, Value} -> | |
| Map(Value); | |
| Error -> | |
| Error | |
| end. | |
| the_atom(Atom) -> | |
| EncodedAtom = atom_to_binary(Atom, utf8), | |
| Validator = fun(String) -> | |
| if | |
| EncodedAtom =:= String -> | |
| {ok, Atom}; | |
| true -> | |
| {error, [String, " does not represent ", EncodedAtom]} | |
| end | |
| end, | |
| validate(Validator, string()). | |
| -ifdef(TEST). | |
| the_atom_test_() -> | |
| [ fun() -> {ok, goober} = decode_json(<<"goober">>, the_atom(goober)) end | |
| , fun() -> {error, <<"not string">>} = decode_json(5, the_atom(goober)) end | |
| , fun() -> {error, _} = decode_json(<<"pants">>, the_atom(goober)) end | |
| ]. | |
| -endif. | |
| an_atom(Atoms) -> | |
| Validators = lists:map(fun(Atom) -> | |
| the_atom(Atom) | |
| end, Atoms), | |
| one_of(Validators). | |
| -ifdef(TEST). | |
| an_atom_test_() -> | |
| [ fun() -> {ok, goober} = decode_json(<<"goober">>, an_atom([pants, goober])) end | |
| , fun() -> {ok, pants} = decode_json(<<"pants">>, an_atom([pants, goober])) end | |
| , fun() -> {error, _} = decode_json(<<"hello">>, an_atom([pants, goober])) end | |
| , fun() -> {error, _} = decode_json(5, an_atom([pants, goober])) end | |
| ]. | |
| -endif. | |
| -ifdef(TEST). | |
| and_then_test_() -> | |
| [ fun() -> {error, fart} = decode_json(1, and_then(number(), fun(_) -> fail(fart) end)) end | |
| , fun() -> {error, <<"not number">>} = decode_json(<<"a">>, and_then(number(), fun(_) -> ok(fart) end)) end | |
| , fun() -> {ok, fart} = decode_json(1, and_then(number(), fun(1) -> ok(fart) end)) end | |
| ]. | |
| -endif. | |
| -spec and_then(decoder(A), fun((A) -> decoder(B))) -> decoder(B). | |
| and_then(Decoder, Generator) -> | |
| fun(Json) -> | |
| case Decoder(Json) of | |
| {ok, Val} -> | |
| NextDecoder = Generator(Val), | |
| NextDecoder(Json); | |
| Error -> | |
| Error | |
| end | |
| end. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment