Fix OTP 28 dialyzer warnings, rewrite gmser_dyn decoder #55
78
README.md
78
README.md
@ -49,8 +49,25 @@ range from 10 to 200, and also to stay within 1 byte.)
|
|||||||
When encoding `map` types, the map elements are first sorted.
|
When encoding `map` types, the map elements are first sorted.
|
||||||
|
|
||||||
When specifying a map type for template-driven encoding, use
|
When specifying a map type for template-driven encoding, use
|
||||||
the `#{items => [{Key, Value}]}` construct.
|
the `#{items => [{Key, ValueType} | {opt, Key, ValueType}]}` construct.
|
||||||
|
The key names are included in the encoding, and are match against the item
|
||||||
|
specs during decoding. If the key names don't match, the decoding fails, unless
|
||||||
|
for an `{opt, K, V}` item, in which case that item spec is skipped.
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
T = #{items => [{a,int},{opt,b,int},{c,int}]}
|
||||||
|
E1 = gmser_dyn:encode_typed(T, #{a => 1, b => 2, c => 3}) ->
|
||||||
|
[<<0>>,<<1>>,[<<252>>,
|
||||||
|
[[[<<255>>,<<97>>],[<<248>>,<<1>>]],
|
||||||
|
[[<<255>>,<<98>>],[<<248>>,<<2>>]],
|
||||||
|
[[<<255>>,<<99>>],[<<248>>,<<3>>]]]]]
|
||||||
|
E2 = gmser_dyn:encode_typed(T, #{a => 1, c => 3}) ->
|
||||||
|
[<<0>>,<<1>>,[<<252>>,
|
||||||
|
[[[<<255>>,<<97>>],[<<248>>,<<1>>]],
|
||||||
|
[[<<255>>,<<99>>],[<<248>>,<<3>>]]]]]
|
||||||
|
gmser_dyn:decode_typed(T,E2) ->
|
||||||
|
#{c => 3,a => 1}
|
||||||
|
```
|
||||||
|
|
||||||
## Labels
|
## Labels
|
||||||
|
|
||||||
@ -119,6 +136,10 @@ inserting the top-level tag), leading to some compression of the output.
|
|||||||
This also means that the serialized term cannot be decoded without the same
|
This also means that the serialized term cannot be decoded without the same
|
||||||
schema information on the decoder side.
|
schema information on the decoder side.
|
||||||
|
|
||||||
|
In some cases, the type tags will still be emitted. These are when alternative types
|
||||||
|
appear, and for enumerated map types (`#{items => ...}`). In the latter case, it is
|
||||||
|
due to the support for optional items.
|
||||||
|
|
||||||
In the case of a directly provided template, all type information is inserted,
|
In the case of a directly provided template, all type information is inserted,
|
||||||
such that the serialized term can be decoded without any added type information.
|
such that the serialized term can be decoded without any added type information.
|
||||||
The template types are still enforced during encoding.
|
The template types are still enforced during encoding.
|
||||||
@ -135,10 +156,14 @@ ET(lt2i, [{1,2}]) -> [<<0>>,<<1>>,[<<3,232>>,[[<<1>>,<<2>>]]]]
|
|||||||
### Alternative types
|
### Alternative types
|
||||||
|
|
||||||
The dynamic encoder supports two additions to the `gmserialization` template
|
The dynamic encoder supports two additions to the `gmserialization` template
|
||||||
language: `any` and `#{alt => [AltTypes]}`.
|
language: `any`, `#{alt => [AltTypes]}` and `#{switch => [AltTypes]}`.
|
||||||
|
|
||||||
|
#### `any`
|
||||||
|
|
||||||
The `any` type doesn't have an associated code, but enforces dynamic encoding.
|
The `any` type doesn't have an associated code, but enforces dynamic encoding.
|
||||||
|
|
||||||
|
#### `alt`
|
||||||
|
|
||||||
The `#{alt => [Type]}` construct also enforces dynamic encoding, and will try
|
The `#{alt => [Type]}` construct also enforces dynamic encoding, and will try
|
||||||
to encode as each type in the list, in the specified order, until one matches.
|
to encode as each type in the list, in the specified order, until one matches.
|
||||||
|
|
||||||
@ -150,6 +175,55 @@ gmser_dyn:encode_typed(anyint,-5) -> [<<0>>,<<1>>,[<<246>>,[<<247>>,<<5>>]]]
|
|||||||
gmser_dyn:encode_typed(anyint,5) -> [<<0>>,<<1>>,[<<246>>,[<<248>>,<<5>>]]]
|
gmser_dyn:encode_typed(anyint,5) -> [<<0>>,<<1>>,[<<246>>,[<<248>>,<<5>>]]]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `switch`
|
||||||
|
|
||||||
|
The `switch` type allows for encoding a 'tagged' object, where the tag determines
|
||||||
|
the type.
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
E1 = gmser_dyn:encode_typed(#{switch => #{name => binary, age => int}}, #{age => 29}) ->
|
||||||
|
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<97,103,101>>],[<<248>>,<<29>>]]]]]
|
||||||
|
gmser_dyn:decode_typed(#{switch => #{name => binary, age => int}}, E1) ->
|
||||||
|
#{age => 29}
|
||||||
|
E2 = gmser_dyn:encode_typed(#{switch => #{name => binary, age => int}}, #{name => <<"Ulf">>}) ->
|
||||||
|
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<110,97,109,101>>],[<<249>>,<<85,108,102>>]]]]]
|
||||||
|
gmser_dyn:decode_typed(#{switch => #{name => binary, age => int}}, E1) ->
|
||||||
|
#{name => <<"Ulf">>}
|
||||||
|
```
|
||||||
|
|
||||||
|
A practical use of `switch` would be in a protocol schema:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
t_msg(_) ->
|
||||||
|
#{switch => #{ call => t_call
|
||||||
|
, reply => t_reply
|
||||||
|
, notification => t_notification }}.
|
||||||
|
|
||||||
|
t_call(_) ->
|
||||||
|
#{items => [ {id, anyint}
|
||||||
|
, {req, t_req} ]}.
|
||||||
|
|
||||||
|
t_reply(_) ->
|
||||||
|
#{alt => [#{items => [ {id, anyint}
|
||||||
|
, {result, t_result} ]},
|
||||||
|
#{items => [ {id, anyint}
|
||||||
|
, {code, anyint}
|
||||||
|
, {message, binary} ]}
|
||||||
|
]}.
|
||||||
|
```
|
||||||
|
|
||||||
|
In this scenario, messages are 'taggged' as 1-element maps, e.g.:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
async_request(Msg) ->
|
||||||
|
Id = erlang:unique_integer(),
|
||||||
|
gmmp_cp:to_server(
|
||||||
|
whereis(gmmp_core_connector),
|
||||||
|
#{call => #{ id => Id
|
||||||
|
, req => Msg }}),
|
||||||
|
Id.
|
||||||
|
```
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
Note that `anyint` is a standard type. The static serializer supports only
|
Note that `anyint` is a standard type. The static serializer supports only
|
||||||
|
|||||||
@ -7,4 +7,11 @@
|
|||||||
{enacl,
|
{enacl,
|
||||||
{git,
|
{git,
|
||||||
"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
||||||
{ref, "4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}}]}.
|
{ref, "4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}},
|
||||||
|
{eblake2, "1.0.0"}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{dialyzer,
|
||||||
|
[ {plt_apps, all_deps},
|
||||||
|
{base_plt_apps, [erts, kernel, stdlib, enacl, base58, eblake2]}
|
||||||
|
]}.
|
||||||
10
rebar.lock
10
rebar.lock
@ -1,8 +1,16 @@
|
|||||||
|
{"1.2.0",
|
||||||
[{<<"base58">>,
|
[{<<"base58">>,
|
||||||
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
||||||
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
||||||
0},
|
0},
|
||||||
|
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||||
{<<"enacl">>,
|
{<<"enacl">>,
|
||||||
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
||||||
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
|
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
|
||||||
0}].
|
0}]}.
|
||||||
|
[
|
||||||
|
{pkg_hash,[
|
||||||
|
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>}]},
|
||||||
|
{pkg_hash_ext,[
|
||||||
|
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>}]}
|
||||||
|
].
|
||||||
|
|||||||
@ -24,10 +24,12 @@
|
|||||||
, deserialize/3 ]). %% (Bin, Vsn, Types) -> Term
|
, deserialize/3 ]). %% (Bin, Vsn, Types) -> Term
|
||||||
|
|
||||||
%% register a type schema, inspect existing schema
|
%% register a type schema, inspect existing schema
|
||||||
-export([ register_types/1
|
-export([ register_types/1 %% updates stored types
|
||||||
, registered_types/0
|
, registered_types/0 %% -"-
|
||||||
, registered_types/1
|
, registered_types/1 %% -"-
|
||||||
, latest_vsn/0
|
, add_types/1 %% Returns updated types
|
||||||
|
, add_types/2 %% -"-
|
||||||
|
, latest_vsn/0 %% -"-
|
||||||
, get_opts/1
|
, get_opts/1
|
||||||
, set_opts/1
|
, set_opts/1
|
||||||
, set_opts/2
|
, set_opts/2
|
||||||
@ -162,11 +164,36 @@ decode_typed(Type, Fields0, Vsn, Types0) ->
|
|||||||
Types = proper_types(Types0, Vsn),
|
Types = proper_types(Types0, Vsn),
|
||||||
case decode_tag_and_vsn(Fields0) of
|
case decode_tag_and_vsn(Fields0) of
|
||||||
{0, Vsn, Fields} ->
|
{0, Vsn, Fields} ->
|
||||||
decode_from_template(Type, Fields, Vsn, Types);
|
decode_typed_(Type, Fields, Vsn, Types);
|
||||||
Other ->
|
Other ->
|
||||||
error({illegal_encoding, Other})
|
error({illegal_encoding, Other})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
decode_typed_(Type, [CodeBin, Fields], Vsn, Types) when is_map(Type) ->
|
||||||
|
Code = decode_basic(int, CodeBin),
|
||||||
|
{Tag, Template} = template(Code, Vsn, Types),
|
||||||
|
Dyn = is_dyn_template(Tag),
|
||||||
|
case {Type, Template} of
|
||||||
|
{#{items := _}, map} -> decode_from_template(Type, Code, Fields, Dyn, Vsn, Types);
|
||||||
|
{#{items := _}, items} -> decode_from_template(Type, Code, Fields, Dyn, Vsn, Types);
|
||||||
|
{#{alt := _}, _} -> decode_from_template(Type, Code, Fields, Dyn, Vsn, Types);
|
||||||
|
{#{switch:= _}, map} -> decode_from_template(Type, Code, Fields, Dyn, Vsn, Types);
|
||||||
|
_ ->
|
||||||
|
error(badarg)
|
||||||
|
end;
|
||||||
|
decode_typed_(Type, [CodeBin, Fields], Vsn, Types) ->
|
||||||
|
Code = decode_basic(int, CodeBin),
|
||||||
|
{_, Template} = template(Type, Vsn, Types),
|
||||||
|
case template(Code, Vsn, Types) of
|
||||||
|
{Tag, Template} ->
|
||||||
|
Dyn = is_dyn_template(Tag),
|
||||||
|
decode_from_template(Template, Code, Fields, Dyn, Vsn, Types);
|
||||||
|
_ ->
|
||||||
|
error(badarg)
|
||||||
|
end;
|
||||||
|
decode_typed_(_, _, _, _) ->
|
||||||
|
error(illegal_encoding).
|
||||||
|
|
||||||
decode_tag_and_vsn([TagBin, VsnBin, Fields]) ->
|
decode_tag_and_vsn([TagBin, VsnBin, Fields]) ->
|
||||||
{decode_basic(int, TagBin),
|
{decode_basic(int, TagBin),
|
||||||
decode_basic(int, VsnBin),
|
decode_basic(int, VsnBin),
|
||||||
@ -189,6 +216,17 @@ assert_vsn(V, #{vsn := V} = Types) -> Types;
|
|||||||
assert_vsn(V, #{vsn := Other} ) -> error({version_mismatch, V, Other});
|
assert_vsn(V, #{vsn := Other} ) -> error({version_mismatch, V, Other});
|
||||||
assert_vsn(V, #{} = Types ) -> Types#{vsn => V}.
|
assert_vsn(V, #{} = Types ) -> Types#{vsn => V}.
|
||||||
|
|
||||||
|
-define(ANYINT, 246).
|
||||||
|
-define(NEGINT, 247).
|
||||||
|
-define(INT, 248).
|
||||||
|
-define(BINARY, 249).
|
||||||
|
-define(BOOL, 250).
|
||||||
|
-define(LIST, 251).
|
||||||
|
-define(MAP, 252).
|
||||||
|
-define(TUPLE, 253).
|
||||||
|
-define(ID, 254).
|
||||||
|
-define(LABEL, 255).
|
||||||
|
|
||||||
dynamic_types() ->
|
dynamic_types() ->
|
||||||
#{ vsn => ?VSN
|
#{ vsn => ?VSN
|
||||||
, codes =>
|
, codes =>
|
||||||
@ -203,16 +241,16 @@ dynamic_types() ->
|
|||||||
, 254 => id
|
, 254 => id
|
||||||
, 255 => label}
|
, 255 => label}
|
||||||
, rev =>
|
, rev =>
|
||||||
#{ anyint => 246
|
#{ anyint => ?ANYINT
|
||||||
, negint => 247
|
, negint => ?NEGINT
|
||||||
, int => 248
|
, int => ?INT
|
||||||
, binary => 249
|
, binary => ?BINARY
|
||||||
, bool => 250
|
, bool => ?BOOL
|
||||||
, list => 251
|
, list => ?LIST
|
||||||
, map => 252
|
, map => ?MAP
|
||||||
, tuple => 253
|
, tuple => ?TUPLE
|
||||||
, id => 254
|
, id => ?ID
|
||||||
, label => 255}
|
, label => ?LABEL }
|
||||||
, labels => #{}
|
, labels => #{}
|
||||||
, rev_labels => #{}
|
, rev_labels => #{}
|
||||||
, templates =>
|
, templates =>
|
||||||
@ -230,6 +268,12 @@ dynamic_types() ->
|
|||||||
, options => #{}
|
, options => #{}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
is_dyn_template(T) ->
|
||||||
|
is_dyn_template_(T, dynamic_types()).
|
||||||
|
|
||||||
|
is_dyn_template_(T, #{templates := Ts}) ->
|
||||||
|
is_map_key(T, Ts).
|
||||||
|
|
||||||
registered_types() ->
|
registered_types() ->
|
||||||
registered_types(latest_vsn()).
|
registered_types(latest_vsn()).
|
||||||
|
|
||||||
@ -264,8 +308,9 @@ find_cached_label(Lbl, #{labels := Lbls}) ->
|
|||||||
|
|
||||||
decode_([CodeBin, Flds], Vsn, Types) ->
|
decode_([CodeBin, Flds], Vsn, Types) ->
|
||||||
Code = decode_basic(int, CodeBin),
|
Code = decode_basic(int, CodeBin),
|
||||||
{_Tag, Template} = template(Code, Vsn, Types),
|
{Tag, Template} = template(Code, Vsn, Types),
|
||||||
decode_from_template(Template, Flds, Vsn, Types).
|
Dyn = is_dyn_template(Tag),
|
||||||
|
decode_from_template(Template, Code, Flds, Dyn, Vsn, Types).
|
||||||
|
|
||||||
encode_(Term, Vsn, Types) ->
|
encode_(Term, Vsn, Types) ->
|
||||||
encode_(Term, dyn(emit()), Vsn, Types).
|
encode_(Term, dyn(emit()), Vsn, Types).
|
||||||
@ -351,53 +396,80 @@ auto_template(T) ->
|
|||||||
T < 0 -> {negint, negint};
|
T < 0 -> {negint, negint};
|
||||||
true ->
|
true ->
|
||||||
error({invalid_type, T})
|
error({invalid_type, T})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
decode_from_template(any, Fld, Vsn, Types) ->
|
decode_from_template(any, _, Fld, _, Vsn, Types) ->
|
||||||
decode_(Fld, Vsn, Types);
|
decode_(Fld, Vsn, Types);
|
||||||
decode_from_template(#{items := Items}, Fld, Vsn, Types) when is_list(Fld) ->
|
decode_from_template(#{items := Items}, _, Fld, _Dyn, Vsn, Types) when is_list(Fld) ->
|
||||||
Zipped = lists:zipwith(
|
Zipped = dec_zip_items(Items, Fld, Vsn, Types),
|
||||||
fun({{K, T}, V}) -> {K, T, V};
|
|
||||||
({{opt,K,T}, V}) -> {K, T, V}
|
|
||||||
end, Items, Fld),
|
|
||||||
lists:foldl(
|
lists:foldl(
|
||||||
fun({K, Type, V}, Map) ->
|
fun({K, Type, Code, V}, Map) ->
|
||||||
maps:is_key(K, Map) andalso error(badarg, duplicate_field),
|
maps:is_key(K, Map) andalso error(badarg, duplicate_key),
|
||||||
Map#{K => decode_from_template({any,Type}, V, Vsn, Types)}
|
Map#{K => decode_from_template(Type, Code, V, true, Vsn, Types)}
|
||||||
end, #{}, Zipped);
|
end, #{}, Zipped);
|
||||||
decode_from_template(#{alt := Alts} = T, Fld, Vsn, Types) when is_list(Alts) ->
|
decode_from_template(#{alt := Alts} = T, Code, Fld, _, Vsn, Types) when is_list(Alts) ->
|
||||||
decode_alt(Alts, Fld, T, Vsn, Types);
|
decode_alt(Alts, Code, Fld, T, Vsn, Types);
|
||||||
decode_from_template(#{switch := Alts} = T, Fld, Vsn, Types) when is_map(Alts) ->
|
decode_from_template(#{switch := Alts} = T, Code, Fld, Dyn, Vsn, Types) when is_map(Alts) ->
|
||||||
decode_switch(Alts, Fld, T, Vsn, Types);
|
decode_switch(Alts, Code, Fld, T, Dyn, Vsn, Types);
|
||||||
decode_from_template(list, Flds, Vsn, Types) ->
|
decode_from_template(list, _, Flds, _, Vsn, Types) ->
|
||||||
[decode_(F, Vsn, Types) || F <- Flds];
|
[decode_(F, Vsn, Types) || F <- Flds];
|
||||||
decode_from_template(map, Fld, Vsn, Types) ->
|
decode_from_template(map, _, Fld, _, Vsn, Types) ->
|
||||||
TupleFields = [F || F <- Fld],
|
TupleFields = [F || F <- Fld],
|
||||||
Items = [decode_from_template({any,any}, T, Vsn, Types)
|
Items = [decode_from_template({any,any}, ?TUPLE, T, true, Vsn, Types)
|
||||||
|| T <- TupleFields],
|
|| T <- TupleFields],
|
||||||
maps:from_list(Items);
|
maps:from_list(Items);
|
||||||
decode_from_template(tuple, Fld, Vsn, Types) ->
|
decode_from_template(tuple, _, Fld, _, Vsn, Types) ->
|
||||||
Items = [decode_(F, Vsn, Types) || F <- Fld],
|
Items = [decode_(F, Vsn, Types) || F <- Fld],
|
||||||
list_to_tuple(Items);
|
list_to_tuple(Items);
|
||||||
decode_from_template([Type], Fields, Vsn, Types) ->
|
decode_from_template([Type], _, Fields, _Dyn, Vsn, Types) ->
|
||||||
[decode_from_template(Type, F, Vsn, Types)
|
[decode_typed_(Type, F, Vsn, Types)
|
||||||
|| F <- Fields];
|
|| F <- Fields];
|
||||||
decode_from_template(Type, V, Vsn, Types) when is_list(Type), is_list(V) ->
|
decode_from_template(Type, _, V, Dyn, Vsn, Types) when is_list(Type), is_list(V) ->
|
||||||
decode_fields(Type, V, Vsn, Types);
|
decode_fields(Type, V, Dyn, Vsn, Types);
|
||||||
decode_from_template(Type, V, Vsn, Types) when is_tuple(Type), is_list(V) ->
|
decode_from_template(Type, _, V, _Dyn, Vsn, Types) when is_tuple(Type), is_list(V) ->
|
||||||
Zipped = lists:zip(tuple_to_list(Type), V),
|
Zipped = lists:zip(tuple_to_list(Type), V),
|
||||||
Items = [decode_from_template(T1, V1, Vsn, Types) || {T1, V1} <- Zipped],
|
Items = [decode_typed_(T1, V1, Vsn, Types) || {T1, V1} <- Zipped],
|
||||||
list_to_tuple(Items);
|
list_to_tuple(Items);
|
||||||
decode_from_template(label, [C], _, #{rev_labels := RLbls}) ->
|
decode_from_template(label, _, [C], _, _, #{rev_labels := RLbls}) ->
|
||||||
Code = decode_basic(int, C),
|
Code = decode_basic(int, C),
|
||||||
maps:get(Code, RLbls);
|
maps:get(Code, RLbls);
|
||||||
decode_from_template(Type, Fld, _, Types) when Type == int
|
decode_from_template(Type, Code, Fld, _, Vsn, Types) when Type == int
|
||||||
; Type == negint
|
; Type == negint
|
||||||
; Type == binary
|
; Type == binary
|
||||||
; Type == bool
|
; Type == bool
|
||||||
; Type == id
|
; Type == id
|
||||||
; Type == label ->
|
; Type == label ->
|
||||||
decode_basic(Type, Fld, Types).
|
case template(Code, Vsn, Types) of
|
||||||
|
Type ->
|
||||||
|
decode_basic(Type, Fld, Types);
|
||||||
|
_ ->
|
||||||
|
error(badarg)
|
||||||
|
end.
|
||||||
|
|
||||||
|
dec_zip_items([{K, T}|Is], [{K1, KEnc, VEnc}|Fs], Vsn, Types) ->
|
||||||
|
if K =:= K1 ->
|
||||||
|
[{K, T, KEnc, VEnc} | dec_zip_items(Is, Fs, Vsn, Types)];
|
||||||
|
true ->
|
||||||
|
error(illegal_map)
|
||||||
|
end;
|
||||||
|
dec_zip_items([{K, T}|Is], [[KEnc, VEnc]|Fs], Vsn, Types) ->
|
||||||
|
case decode_(KEnc, Vsn, Types) of
|
||||||
|
K ->
|
||||||
|
[{K, T, KEnc, VEnc} | dec_zip_items(Is, Fs, Vsn, Types)];
|
||||||
|
_ ->
|
||||||
|
error(illegal_map)
|
||||||
|
end;
|
||||||
|
dec_zip_items([{opt, K, T}|Is], [{K, KEnc, VEnc}|Fs], Vsn, Types) ->
|
||||||
|
[{K, T, KEnc, VEnc} | dec_zip_items(Is, Fs, Vsn, Types)];
|
||||||
|
dec_zip_items([{opt, K, T}|Is], [[KEnc,VEnc]|Fs], Vsn, Types) ->
|
||||||
|
case decode_(KEnc, Vsn, Types) of
|
||||||
|
K ->
|
||||||
|
[{K, T, KEnc, VEnc} | dec_zip_items(Is, Fs, Vsn, Types)];
|
||||||
|
OtherK ->
|
||||||
|
dec_zip_items(Is, [{OtherK, KEnc, VEnc}|Fs], Vsn, Types)
|
||||||
|
end;
|
||||||
|
dec_zip_items([], [], _, _) ->
|
||||||
|
[].
|
||||||
|
|
||||||
encode_from_template(any, V, _E, Vsn, Types) ->
|
encode_from_template(any, V, _E, Vsn, Types) ->
|
||||||
encode_(V, dyn(emit()), Vsn, Types);
|
encode_(V, dyn(emit()), Vsn, Types);
|
||||||
@ -475,12 +547,12 @@ encode_from_template(Type, V, E, Vsn, Types) ->
|
|||||||
assert_type(true, _, _) -> ok;
|
assert_type(true, _, _) -> ok;
|
||||||
assert_type(_, Type, V) -> error({illegal, Type, V}).
|
assert_type(_, Type, V) -> error({illegal, Type, V}).
|
||||||
|
|
||||||
decode_alt([A|Alts], Fld, T, Vsn, Types) ->
|
decode_alt([A|Alts], Code, Fld, T, Vsn, Types) ->
|
||||||
try decode_from_template(A, Fld, Vsn, Types)
|
try decode_from_template(A, Code, Fld, true, Vsn, Types)
|
||||||
catch error:_ ->
|
catch error:_ ->
|
||||||
decode_alt(Alts, Fld, T, Vsn, Types)
|
decode_alt(Alts, Code, Fld, T, Vsn, Types)
|
||||||
end;
|
end;
|
||||||
decode_alt([], Fld, T, _, _) ->
|
decode_alt([], _Code, Fld, T, _, _) ->
|
||||||
error({illegal, T, Fld}).
|
error({illegal, T, Fld}).
|
||||||
|
|
||||||
encode_alt(Alts, Term, T, E, Vsn, Types) ->
|
encode_alt(Alts, Term, T, E, Vsn, Types) ->
|
||||||
@ -495,12 +567,12 @@ encode_alt_([A|Alts], Term, T, E, Vsn, Types) ->
|
|||||||
encode_alt_([], Term, T, _, _, _) ->
|
encode_alt_([], Term, T, _, _, _) ->
|
||||||
error({illegal, T, Term}).
|
error({illegal, T, Term}).
|
||||||
|
|
||||||
decode_switch(Alts, Fld, T, Vsn, Types) ->
|
decode_switch(Alts, Code, Fld, T, Dyn, Vsn, Types) ->
|
||||||
[KFld, VFld] = Fld,
|
[[KFld, VFld]] = Fld,
|
||||||
Key = decode_(KFld, Vsn, Types),
|
Key = decode_(KFld, Vsn, Types),
|
||||||
case maps:find(Key, Alts) of
|
case maps:find(Key, Alts) of
|
||||||
{ok, SubType} ->
|
{ok, SubType} ->
|
||||||
SubTerm = decode_from_template(SubType, VFld, Vsn, Types),
|
SubTerm = decode_from_template(SubType, Code, VFld, Dyn, Vsn, Types),
|
||||||
#{Key => SubTerm};
|
#{Key => SubTerm};
|
||||||
error ->
|
error ->
|
||||||
error({illegal, T, Fld})
|
error({illegal, T, Fld})
|
||||||
@ -536,11 +608,11 @@ encode_fields([], [], _, _, _) ->
|
|||||||
[].
|
[].
|
||||||
|
|
||||||
decode_fields([{Tag, Type}|TypesLeft],
|
decode_fields([{Tag, Type}|TypesLeft],
|
||||||
[Field |FieldsLeft], Vsn, Types) ->
|
[Field |FieldsLeft], Dyn, Vsn, Types) ->
|
||||||
|
|
||||||
[ {Tag, decode_from_template(Type, Field, Vsn, Types)}
|
[ {Tag, decode_from_template(Type, 0, Field, Dyn, Vsn, Types)}
|
||||||
| decode_fields(TypesLeft, FieldsLeft, Vsn, Types)];
|
| decode_fields(TypesLeft, FieldsLeft, Dyn, Vsn, Types)];
|
||||||
decode_fields([], [], _, _) ->
|
decode_fields([], [], _, _, _) ->
|
||||||
[].
|
[].
|
||||||
|
|
||||||
|
|
||||||
@ -609,14 +681,20 @@ register_types(Types) when is_map(Types) ->
|
|||||||
register_types(latest_vsn(), Types).
|
register_types(latest_vsn(), Types).
|
||||||
|
|
||||||
register_types(Vsn, Types) ->
|
register_types(Vsn, Types) ->
|
||||||
|
Result = add_types(Types),
|
||||||
|
put_types(Vsn, Result).
|
||||||
|
|
||||||
|
add_types(Types) ->
|
||||||
|
add_types(Types, dynamic_types()).
|
||||||
|
|
||||||
|
add_types(Types, PrevTypes) ->
|
||||||
Codes = maps:get(codes, Types, #{}),
|
Codes = maps:get(codes, Types, #{}),
|
||||||
Rev = rev_codes(Codes),
|
Rev = rev_codes(Codes),
|
||||||
Templates = maps:get(templates, Types, #{}),
|
Templates = maps:get(templates, Types, #{}),
|
||||||
Labels = maps:get(labels, Types, #{}),
|
Labels = maps:get(labels, Types, #{}),
|
||||||
Options = maps:get(options, Types, #{}),
|
Options = maps:get(options, Types, #{}),
|
||||||
#{codes := Codes0, rev := Rev0, labels := Labels0,
|
#{codes := Codes0, rev := Rev0, labels := Labels0,
|
||||||
templates := Templates0, options := Options0} =
|
templates := Templates0, options := Options0} = PrevTypes,
|
||||||
dynamic_types(),
|
|
||||||
Merged = #{ codes => maps:merge(Codes0, Codes)
|
Merged = #{ codes => maps:merge(Codes0, Codes)
|
||||||
, rev => maps:merge(Rev0, Rev)
|
, rev => maps:merge(Rev0, Rev)
|
||||||
, templates => maps:merge(Templates0, Templates)
|
, templates => maps:merge(Templates0, Templates)
|
||||||
@ -624,8 +702,7 @@ register_types(Vsn, Types) ->
|
|||||||
, labels => maps:merge(Labels0, Labels) },
|
, labels => maps:merge(Labels0, Labels) },
|
||||||
assert_sizes(Merged),
|
assert_sizes(Merged),
|
||||||
assert_mappings(Merged),
|
assert_mappings(Merged),
|
||||||
Merged1 = assert_label_cache(Merged),
|
assert_label_cache(Merged).
|
||||||
put_types(Vsn, Merged1).
|
|
||||||
|
|
||||||
latest_vsn() ->
|
latest_vsn() ->
|
||||||
case persistent_term:get(pt_key(), undefined) of
|
case persistent_term:get(pt_key(), undefined) of
|
||||||
@ -817,6 +894,7 @@ user_types_test_() ->
|
|||||||
, ?_test(t_reg_template_vsnd_fun())
|
, ?_test(t_reg_template_vsnd_fun())
|
||||||
, ?_test(t_reg_label_cache())
|
, ?_test(t_reg_label_cache())
|
||||||
, ?_test(t_reg_label_cache2())
|
, ?_test(t_reg_label_cache2())
|
||||||
|
, ?_test(t_reg_map())
|
||||||
, ?_test(t_reg_options())
|
, ?_test(t_reg_options())
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
@ -833,6 +911,45 @@ versioned_types_test_() ->
|
|||||||
[ ?_test(t_new_version())
|
[ ?_test(t_new_version())
|
||||||
].
|
].
|
||||||
|
|
||||||
|
consistency_test_() ->
|
||||||
|
[?_test(t_full_round_trip(Type, Term))
|
||||||
|
|| {Type, Term} <-
|
||||||
|
lists:flatmap(
|
||||||
|
fun({Type, Terms}) ->
|
||||||
|
[{Type,T} || T <- Terms]
|
||||||
|
end, full_round_trip_terms())].
|
||||||
|
|
||||||
|
full_round_trip_terms() ->
|
||||||
|
[
|
||||||
|
{ #{items => [{a, binary}]}, [#{a => <<"foo">>}] }
|
||||||
|
, { #{items => [ {a, int}
|
||||||
|
, {opt, b, int}
|
||||||
|
, {c, int} ]}, [ #{a => 1, b => 2, c => 3}
|
||||||
|
, #{a => 1, c => 3} ] }
|
||||||
|
, { #{alt => [int, label]}, [ 1, a ] }
|
||||||
|
, { #{switch => #{ a => int, b => binary }}, [ #{a => 17}
|
||||||
|
, #{b => <<"foo">>} ]}
|
||||||
|
].
|
||||||
|
|
||||||
|
t_full_round_trip(Type, Term) ->
|
||||||
|
?debugFmt("Type = ~p, Term = ~p", [Type, Term]),
|
||||||
|
Types = dynamic_types(),
|
||||||
|
ETyped = encode_typed(Type, Term, Types),
|
||||||
|
Types1 = gmser_dyn_types:add_type(mytype, 1030, Type, Types),
|
||||||
|
EReg = encode_typed(mytype, Term, Types1),
|
||||||
|
DTyped = decode_typed(Type, ETyped, Types),
|
||||||
|
?debugFmt("DTyped = ~p", [DTyped]),
|
||||||
|
DReg = decode_typed(mytype, EReg, Types1),
|
||||||
|
?assertEqual(Term, DTyped),
|
||||||
|
?assertEqual(Term, DReg),
|
||||||
|
DDynT = decode(ETyped),
|
||||||
|
?assertEqual(Term, DDynT),
|
||||||
|
try decode(EReg)
|
||||||
|
catch
|
||||||
|
error:_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
t_round_trip(T) ->
|
t_round_trip(T) ->
|
||||||
?debugVal(T),
|
?debugVal(T),
|
||||||
?assertMatch({T, T}, {T, decode(encode(T))}).
|
?assertMatch({T, T}, {T, decode(encode(T))}).
|
||||||
@ -928,6 +1045,21 @@ t_reg_label_cache2() ->
|
|||||||
[<<0>>,<<1>>,[<<3,235>>,[[<<49>>],[<<49>>]]]] = Enc,
|
[<<0>>,<<1>>,[<<3,235>>,[[<<49>>],[<<49>>]]]] = Enc,
|
||||||
_Tup = gmser_dyn:decode(Enc).
|
_Tup = gmser_dyn:decode(Enc).
|
||||||
|
|
||||||
|
t_reg_map() ->
|
||||||
|
Types =
|
||||||
|
#{codes => #{1013 => my_map},
|
||||||
|
templates => #{my_map => #{items => [{a, label},
|
||||||
|
{b, int}]}}
|
||||||
|
},
|
||||||
|
register_types(Types),
|
||||||
|
Enc0 = gmser_dyn:encode_typed(my_map, #{a => foo, b => 17}),
|
||||||
|
dbg:tracer(),
|
||||||
|
dbg:tpl(?MODULE,x),
|
||||||
|
dbg:p(all,[c]),
|
||||||
|
#{a := foo, b := 17} = try gmser_dyn:decode(Enc0)
|
||||||
|
after dbg:stop() end,
|
||||||
|
ok.
|
||||||
|
|
||||||
t_reg_options() ->
|
t_reg_options() ->
|
||||||
register_types(set_opts(#{missing_labels => convert})),
|
register_types(set_opts(#{missing_labels => convert})),
|
||||||
[Dyn,Vsn,[Am,<<"random">>]] = gmser_dyn:encode(random),
|
[Dyn,Vsn,[Am,<<"random">>]] = gmser_dyn:encode(random),
|
||||||
|
|||||||
@ -17,6 +17,9 @@
|
|||||||
, is_id/1
|
, is_id/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([ t_id/1
|
||||||
|
]).
|
||||||
|
|
||||||
%% For aec_serialization
|
%% For aec_serialization
|
||||||
-export([ encode/1
|
-export([ encode/1
|
||||||
, decode/1
|
, decode/1
|
||||||
@ -26,11 +29,18 @@
|
|||||||
, val
|
, val
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type tag() :: 'account' | 'name'
|
-type tag() :: 'account'
|
||||||
| 'commitment' | 'contract' | 'channel'
|
| 'associate_chain'
|
||||||
| 'associate_chain' | 'entry' .
|
| 'channel'
|
||||||
|
| 'commitment'
|
||||||
|
| 'contract'
|
||||||
|
| 'contract_source'
|
||||||
|
| 'name'
|
||||||
|
| 'native_token'
|
||||||
|
| 'entry'.
|
||||||
|
|
||||||
-type val() :: <<_:256>>.
|
-type val() :: <<_:256>>.
|
||||||
-opaque(id() :: #id{}).
|
-type id() :: #id{}.
|
||||||
|
|
||||||
-export_type([ id/0
|
-export_type([ id/0
|
||||||
, tag/0
|
, tag/0
|
||||||
@ -94,6 +104,9 @@ decode(<<Tag:?TAG_SIZE/unit:8, Val:?PUB_SIZE/binary>>) ->
|
|||||||
#id{ tag = decode_tag(Tag)
|
#id{ tag = decode_tag(Tag)
|
||||||
, val = Val}.
|
, val = Val}.
|
||||||
|
|
||||||
|
-spec t_id(any()) -> id().
|
||||||
|
t_id(#id{} = Id) -> Id.
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user