Compare commits
1 Commits
master
...
uw-dyn-opt
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a250828afa |
@ -1,33 +1,22 @@
|
|||||||
-module(gmser_dyn).
|
-module(gmser_dyn).
|
||||||
|
|
||||||
-export([ encode/1 %% (Term) -> rlp()
|
-export([ encode/1
|
||||||
, encode/2 %% (Term, Types) -> rlp()
|
, encode/2
|
||||||
, encode/3 %% (Term, Vsn, Types) -> rlp()
|
, encode_typed/2
|
||||||
, encode_typed/2 %% (Type, Term) -> rlp()
|
, encode_typed/3
|
||||||
, encode_typed/3 %% (Type, Term, Types) -> rlp()
|
, decode/1
|
||||||
, encode_typed/4 %% (Type, Term, Vsn, Types) -> rlp()
|
, decode/2 ]).
|
||||||
, decode/1 %% (RLP) -> Term
|
|
||||||
, decode/2 %% (RLP, Types) -> Term
|
|
||||||
, decode/3 %% (RLP, Vsn, Types) -> Term
|
|
||||||
, decode_typed/2 %% (Type, RLP) -> Term
|
|
||||||
, decode_typed/3 %% (Type, RLP, Types) -> Term
|
|
||||||
, decode_typed/4 ]). %% (Type, RLP, Vsn, Types) -> Term
|
|
||||||
|
|
||||||
-export([ serialize/1 %% (Term) -> Bin
|
-export([ serialize/1
|
||||||
, serialize/2 %% (Term, Types) -> Bin
|
, serialize/2
|
||||||
, serialize/3 %% (Term, Vsn, Types) -> Bin
|
, serialize_typed/2
|
||||||
, serialize_typed/2 %% (Type, Term) -> Bin
|
, serialize_typed/3
|
||||||
, serialize_typed/3 %% (Type, Term, Types) -> Bin
|
, deserialize/1
|
||||||
, serialize_typed/4 %% (Type, Term, Vsn, Types) -> Bin
|
, deserialize/2 ]).
|
||||||
, deserialize/1 %% (Bin) -> Term
|
|
||||||
, deserialize/2 %% (Bin, 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
|
||||||
, registered_types/0
|
, registered_types/0
|
||||||
, registered_types/1
|
|
||||||
, latest_vsn/0
|
|
||||||
, get_opts/1
|
, get_opts/1
|
||||||
, set_opts/1
|
, set_opts/1
|
||||||
, set_opts/2
|
, set_opts/2
|
||||||
@ -37,8 +26,7 @@
|
|||||||
|
|
||||||
%% Register individual types, or cache labels
|
%% Register individual types, or cache labels
|
||||||
-export([ register_type/3
|
-export([ register_type/3
|
||||||
, cache_label/2
|
, cache_label/2 ]).
|
||||||
]).
|
|
||||||
|
|
||||||
-import(gmserialization, [ decode_field/2 ]).
|
-import(gmserialization, [ decode_field/2 ]).
|
||||||
|
|
||||||
@ -51,120 +39,46 @@
|
|||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
serialize(Term) ->
|
serialize(Term) -> rlp_encode(encode(Term)).
|
||||||
Vsn = latest_vsn(),
|
serialize(Term, Types) -> rlp_encode(encode(Term, Types)).
|
||||||
rlp_encode(encode(Term, Vsn, registered_types(Vsn))).
|
serialize_typed(Type, Term) -> rlp_encode(encode_typed(Type, Term)).
|
||||||
|
serialize_typed(Type, Term, Types) -> rlp_encode(encode_typed(Type, Term, Types)).
|
||||||
|
|
||||||
serialize(Term, Types0) ->
|
deserialize(Binary) -> decode(rlp_decode(Binary)).
|
||||||
Types = proper_types(Types0),
|
deserialize(Binary, Types) -> decode(rlp_decode(Binary), Types).
|
||||||
Vsn = types_vsn(Types),
|
|
||||||
rlp_encode(encode(Term, Vsn, Types)).
|
|
||||||
|
|
||||||
serialize(Term, Vsn, Types) ->
|
|
||||||
rlp_encode(encode(Term, Vsn, proper_types(Types, Vsn))).
|
|
||||||
|
|
||||||
serialize_typed(Type, Term) ->
|
|
||||||
Vsn = latest_vsn(),
|
|
||||||
rlp_encode(encode_typed(Type, Term, Vsn, registered_types(Vsn))).
|
|
||||||
|
|
||||||
serialize_typed(Type, Term, Types0) ->
|
|
||||||
Types = proper_types(Types0),
|
|
||||||
Vsn = types_vsn(Types),
|
|
||||||
rlp_encode(encode_typed(Type, Term, Vsn, Types)).
|
|
||||||
|
|
||||||
serialize_typed(Type, Term, Vsn, Types) ->
|
|
||||||
rlp_encode(encode_typed(Type, Term, Vsn, proper_types(Types, Vsn))).
|
|
||||||
|
|
||||||
deserialize(Binary) ->
|
|
||||||
Fields0 = rlp_decode(Binary),
|
|
||||||
case decode_tag_and_vsn(Fields0) of
|
|
||||||
{0, Vsn, Fields} ->
|
|
||||||
decode_(Fields, Vsn, registered_types(Vsn));
|
|
||||||
Other ->
|
|
||||||
error({illegal_serialization, Other})
|
|
||||||
end.
|
|
||||||
|
|
||||||
deserialize(Binary, Types0) ->
|
|
||||||
Types = proper_types(Types0),
|
|
||||||
Vsn0 = types_vsn(Types),
|
|
||||||
Fields0 = rlp_decode(Binary),
|
|
||||||
case decode_tag_and_vsn(Fields0) of
|
|
||||||
{0, Vsn, Fields} when Vsn0 == undefined; Vsn0 == Vsn ->
|
|
||||||
decode_(Fields, Vsn, Types);
|
|
||||||
Other ->
|
|
||||||
error({illegal_serialization, Other})
|
|
||||||
end.
|
|
||||||
|
|
||||||
deserialize(Binary, Vsn, Types0) ->
|
|
||||||
Types = proper_types(Types0),
|
|
||||||
Fields0 = rlp_decode(Binary),
|
|
||||||
case decode_tag_and_vsn(Fields0) of
|
|
||||||
{0, Vsn, Fields} ->
|
|
||||||
decode_(Fields, Vsn, Types);
|
|
||||||
Other ->
|
|
||||||
error({illegal_serialization, Other})
|
|
||||||
end.
|
|
||||||
|
|
||||||
encode(Term) ->
|
encode(Term) ->
|
||||||
Vsn = latest_vsn(),
|
encode(Term, registered_types()).
|
||||||
encode(Term, Vsn, registered_types(Vsn)).
|
|
||||||
|
|
||||||
encode(Term, Types0) ->
|
encode(Term, Types) ->
|
||||||
Types = proper_types(Types0),
|
encode(Term, vsn(Types), Types).
|
||||||
encode(Term, types_vsn(Types), Types).
|
|
||||||
|
|
||||||
encode(Term, Vsn, Types0) ->
|
encode(Term, Vsn, Types) ->
|
||||||
Types = proper_types(Types0, Vsn),
|
|
||||||
[ encode_basic(int, 0)
|
[ encode_basic(int, 0)
|
||||||
, encode_basic(int, Vsn)
|
, encode_basic(int, Vsn)
|
||||||
, encode_(Term, Vsn, Types) ].
|
, encode_(Term, Vsn, Types) ].
|
||||||
|
|
||||||
encode_typed(Type, Term) ->
|
encode_typed(Type, Term) ->
|
||||||
Vsn = latest_vsn(),
|
encode_typed(Type, Term, registered_types()).
|
||||||
encode_typed(Type, Term, Vsn, registered_types(Vsn)).
|
|
||||||
|
|
||||||
encode_typed(Type, Term, Types0) ->
|
encode_typed(Type, Term, Types) ->
|
||||||
Types = proper_types(Types0),
|
encode_typed(Type, Term, vsn(Types), Types).
|
||||||
encode_typed(Type, Term, types_vsn(Types), Types).
|
|
||||||
|
|
||||||
encode_typed(Type, Term, Vsn, Types0) ->
|
encode_typed(Type, Term, Vsn, Types) ->
|
||||||
Types = proper_types(Types0, Vsn),
|
|
||||||
[ encode_basic(int, 0)
|
[ encode_basic(int, 0)
|
||||||
, encode_basic(int, Vsn)
|
, encode_basic(int, Vsn)
|
||||||
, encode_typed_(Type, Term, Vsn, Types) ].
|
, encode_typed_(Type, Term, Vsn, Types) ].
|
||||||
|
|
||||||
decode(Fields) ->
|
decode(Fields) ->
|
||||||
Vsn = latest_vsn(),
|
decode(Fields, registered_types()).
|
||||||
decode(Fields, Vsn, registered_types(Vsn)).
|
|
||||||
|
|
||||||
decode(Fields, Types0) ->
|
decode(Fields0, Types) ->
|
||||||
Types = proper_types(Types0),
|
|
||||||
decode(Fields, types_vsn(Types), Types).
|
|
||||||
|
|
||||||
decode(Fields0, Vsn, Types0) ->
|
|
||||||
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_(Fields, Vsn, Types);
|
decode_(Fields, Vsn, Types);
|
||||||
Other ->
|
Other ->
|
||||||
error({illegal_encoding, Other})
|
error({illegal_serialization, Other})
|
||||||
end.
|
|
||||||
|
|
||||||
decode_typed(Type, Fields) ->
|
|
||||||
Vsn = latest_vsn(),
|
|
||||||
decode_typed(Type, Fields, Vsn, registered_types(Vsn)).
|
|
||||||
|
|
||||||
decode_typed(Type, Fields, Types0) ->
|
|
||||||
Types = proper_types(Types0),
|
|
||||||
decode_typed(Type, Fields, types_vsn(Types), Types).
|
|
||||||
|
|
||||||
decode_typed(Type, Fields0, Vsn, Types0) ->
|
|
||||||
Types = proper_types(Types0, Vsn),
|
|
||||||
case decode_tag_and_vsn(Fields0) of
|
|
||||||
{0, Vsn, Fields} ->
|
|
||||||
decode_from_template(Type, Fields, Vsn, Types);
|
|
||||||
Other ->
|
|
||||||
error({illegal_encoding, Other})
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
decode_tag_and_vsn([TagBin, VsnBin, Fields]) ->
|
decode_tag_and_vsn([TagBin, VsnBin, Fields]) ->
|
||||||
@ -172,23 +86,6 @@ decode_tag_and_vsn([TagBin, VsnBin, Fields]) ->
|
|||||||
decode_basic(int, VsnBin),
|
decode_basic(int, VsnBin),
|
||||||
Fields}.
|
Fields}.
|
||||||
|
|
||||||
proper_types(undefined) ->
|
|
||||||
registered_types(latest_vsn());
|
|
||||||
proper_types(#{} = Types) ->
|
|
||||||
Types.
|
|
||||||
|
|
||||||
proper_types(undefined, Vsn) ->
|
|
||||||
registered_types(Vsn);
|
|
||||||
proper_types(#{} = Types, Vsn) ->
|
|
||||||
assert_vsn(Vsn, Types).
|
|
||||||
|
|
||||||
types_vsn(#{vsn := Vsn}) -> Vsn;
|
|
||||||
types_vsn(_) -> latest_vsn().
|
|
||||||
|
|
||||||
assert_vsn(V, #{vsn := V} = Types) -> Types;
|
|
||||||
assert_vsn(V, #{vsn := Other} ) -> error({version_mismatch, V, Other});
|
|
||||||
assert_vsn(V, #{} = Types ) -> Types#{vsn => V}.
|
|
||||||
|
|
||||||
dynamic_types() ->
|
dynamic_types() ->
|
||||||
#{ vsn => ?VSN
|
#{ vsn => ?VSN
|
||||||
, codes =>
|
, codes =>
|
||||||
@ -230,17 +127,15 @@ dynamic_types() ->
|
|||||||
, options => #{}
|
, options => #{}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
registered_types() ->
|
vsn(Types) ->
|
||||||
registered_types(latest_vsn()).
|
maps:get(vsn, Types, ?VSN).
|
||||||
|
|
||||||
registered_types(Vsn) ->
|
registered_types() ->
|
||||||
case persistent_term:get(pt_key(), undefined) of
|
case persistent_term:get({?MODULE, types}, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
dynamic_types();
|
dynamic_types();
|
||||||
#{latest_vsn := _, types := #{Vsn := Types}} ->
|
Types when is_map(Types) ->
|
||||||
Types;
|
Types
|
||||||
#{latest_vsn := _, types := _} ->
|
|
||||||
dynamic_types()
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
template(TagOrCode, Vsn, Types) ->
|
template(TagOrCode, Vsn, Types) ->
|
||||||
@ -318,8 +213,6 @@ encode_maybe_template(#{items := _} = Type, Term, Vsn, Types) ->
|
|||||||
end;
|
end;
|
||||||
encode_maybe_template(#{alt := _} = Type, Term, Vsn, Types) ->
|
encode_maybe_template(#{alt := _} = Type, Term, Vsn, Types) ->
|
||||||
encode_from_template(Type, Term, Vsn, emit(dyn()), Types);
|
encode_from_template(Type, Term, Vsn, emit(dyn()), Types);
|
||||||
encode_maybe_template(#{switch := _} = Type, Term, Vsn, Types) ->
|
|
||||||
encode_from_template(Type, Term, Vsn, emit(dyn()), Types);
|
|
||||||
encode_maybe_template(Pat, Term, Vsn, Types) when is_list(Pat);
|
encode_maybe_template(Pat, Term, Vsn, Types) when is_list(Pat);
|
||||||
is_tuple(Pat) ->
|
is_tuple(Pat) ->
|
||||||
encode_from_template(Pat, Term, emit(dyn()), Vsn, Types);
|
encode_from_template(Pat, Term, emit(dyn()), Vsn, Types);
|
||||||
@ -356,19 +249,14 @@ auto_template(T) ->
|
|||||||
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, Vsn, Types) when is_list(Fld) ->
|
||||||
Zipped = lists:zipwith(
|
Zipped = lists:zip(Items, Fld),
|
||||||
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}, V}, Map) ->
|
||||||
maps:is_key(K, Map) andalso error(badarg, duplicate_field),
|
maps:is_key(K, Map) andalso error(badarg, duplicate_field),
|
||||||
Map#{K => decode_from_template({any,Type}, V, Vsn, Types)}
|
Map#{K => decode_from_template({any,Type}, V, 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, Fld, Vsn, Types) when is_list(Alts) ->
|
||||||
decode_alt(Alts, Fld, T, Vsn, Types);
|
decode_alt(Alts, Fld, T, Vsn, Types);
|
||||||
decode_from_template(#{switch := Alts} = T, Fld, Vsn, Types) when is_map(Alts) ->
|
|
||||||
decode_switch(Alts, Fld, T, 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) ->
|
||||||
@ -408,28 +296,15 @@ encode_from_template(list, L, E, Vsn, Types) when is_list(L) ->
|
|||||||
encode_from_template(#{items := Items}, M, E, Vsn, Types) ->
|
encode_from_template(#{items := Items}, M, E, Vsn, Types) ->
|
||||||
assert_type(is_map(M), map, M),
|
assert_type(is_map(M), map, M),
|
||||||
Emit = noemit(E),
|
Emit = noemit(E),
|
||||||
Encode = fun(K, Type, V) ->
|
|
||||||
[encode_from_template(any, K, Emit, Vsn, Types),
|
|
||||||
encode_from_template(Type, V, Emit, Vsn, Types)]
|
|
||||||
end,
|
|
||||||
emit(E, map, Types,
|
emit(E, map, Types,
|
||||||
lists:foldr(
|
lists:map(
|
||||||
fun({K, Type}, Acc) ->
|
fun({K, Type}) ->
|
||||||
V = maps:get(K, M),
|
V = maps:get(K, M),
|
||||||
[Encode(K, Type, V) | Acc];
|
[encode_from_template(any, K, Emit, Vsn, Types),
|
||||||
({opt, K, Type}, Acc) ->
|
encode_from_template(Type, V, Emit, Vsn, Types)]
|
||||||
case maps:find(K, M) of
|
end, Items));
|
||||||
{ok, V} ->
|
|
||||||
[Encode(K, Type, V) | Acc];
|
|
||||||
error ->
|
|
||||||
Acc
|
|
||||||
end
|
|
||||||
end, [], Items));
|
|
||||||
encode_from_template(#{alt := Alts} = T, Term, E, Vsn, Types) when is_list(Alts) ->
|
encode_from_template(#{alt := Alts} = T, Term, E, Vsn, Types) when is_list(Alts) ->
|
||||||
encode_alt(Alts, Term, T, E, Vsn, Types);
|
encode_alt(Alts, Term, T, E, Vsn, Types);
|
||||||
encode_from_template(#{switch := Alts} = T, Term, E, Vsn, Types) when is_map(Alts),
|
|
||||||
is_map(Term) ->
|
|
||||||
encode_switch(Alts, Term, T, E, Vsn, Types);
|
|
||||||
encode_from_template(map, M, E, Vsn, Types) ->
|
encode_from_template(map, M, E, Vsn, Types) ->
|
||||||
assert_type(is_map(M), map, M),
|
assert_type(is_map(M), map, M),
|
||||||
Emit = emit(E),
|
Emit = emit(E),
|
||||||
@ -455,13 +330,13 @@ encode_from_template(Type, List, E, Vsn, Types) when is_list(Type), is_list(List
|
|||||||
encode_fields(Type, List, E, Vsn, Types);
|
encode_fields(Type, List, E, Vsn, Types);
|
||||||
encode_from_template(label, V, E, _, Types) ->
|
encode_from_template(label, V, E, _, Types) ->
|
||||||
assert_type(is_atom(V), label, V),
|
assert_type(is_atom(V), label, V),
|
||||||
case find_cached_label(V, Types) of
|
emit(E, label, Types,
|
||||||
error ->
|
case find_cached_label(V, Types) of
|
||||||
encode_basic(label, V, E, Types);
|
error ->
|
||||||
{ok, Code} when is_integer(Code) ->
|
encode_basic(label, V, E, Types);
|
||||||
emit(E, label, Types,
|
{ok, Code} when is_integer(Code) ->
|
||||||
[encode_basic(int, Code)])
|
[encode_basic(int, Code)]
|
||||||
end;
|
end);
|
||||||
encode_from_template(Type, V, E, _, Types) when Type == id
|
encode_from_template(Type, V, E, _, Types) when Type == id
|
||||||
; Type == binary
|
; Type == binary
|
||||||
; Type == bool
|
; Type == bool
|
||||||
@ -495,30 +370,6 @@ 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) ->
|
|
||||||
[KFld, VFld] = Fld,
|
|
||||||
Key = decode_(KFld, Vsn, Types),
|
|
||||||
case maps:find(Key, Alts) of
|
|
||||||
{ok, SubType} ->
|
|
||||||
SubTerm = decode_from_template(SubType, VFld, Vsn, Types),
|
|
||||||
#{Key => SubTerm};
|
|
||||||
error ->
|
|
||||||
error({illegal, T, Fld})
|
|
||||||
end.
|
|
||||||
|
|
||||||
encode_switch(Alts, Term, T, E, Vsn, Types) ->
|
|
||||||
assert_type(map_size(Term) == 1, singleton_map, Term),
|
|
||||||
[{Key, Subterm}] = maps:to_list(Term),
|
|
||||||
case maps:find(Key, Alts) of
|
|
||||||
{ok, SubType} ->
|
|
||||||
Enc = encode_from_template(SubType, Subterm, E, Vsn, Types),
|
|
||||||
emit(E, map, Types,
|
|
||||||
[[encode_from_template(any, Key, E, Vsn, Types),
|
|
||||||
Enc]]);
|
|
||||||
error ->
|
|
||||||
error({illegal, T, Term})
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Basically, dynamically encoding a statically defined object
|
%% Basically, dynamically encoding a statically defined object
|
||||||
encode_fields([{Field, Type}|TypesLeft],
|
encode_fields([{Field, Type}|TypesLeft],
|
||||||
[{Field, Val}|FieldsLeft], E, Vsn, Types) ->
|
[{Field, Val}|FieldsLeft], E, Vsn, Types) ->
|
||||||
@ -606,68 +457,29 @@ rlp_encode(Fields) ->
|
|||||||
%% Type registration and validation code
|
%% Type registration and validation code
|
||||||
|
|
||||||
register_types(Types) when is_map(Types) ->
|
register_types(Types) when is_map(Types) ->
|
||||||
register_types(latest_vsn(), Types).
|
|
||||||
|
|
||||||
register_types(Vsn, Types) ->
|
|
||||||
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, #{}),
|
#{codes := Codes0, rev := Rev0, labels := Labels0, templates := Templates0} =
|
||||||
#{codes := Codes0, rev := Rev0, labels := Labels0,
|
|
||||||
templates := Templates0, options := Options0} =
|
|
||||||
dynamic_types(),
|
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)
|
||||||
, options => maps:merge(Options0, Options)
|
|
||||||
, 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),
|
Merged1 = assert_label_cache(Merged),
|
||||||
put_types(Vsn, Merged1).
|
put_types(Merged1).
|
||||||
|
|
||||||
latest_vsn() ->
|
|
||||||
case persistent_term:get(pt_key(), undefined) of
|
|
||||||
undefined -> ?VSN;
|
|
||||||
#{latest_vsn := V} ->
|
|
||||||
V
|
|
||||||
end.
|
|
||||||
|
|
||||||
pt_key() -> {?MODULE, types}.
|
|
||||||
|
|
||||||
put_types(Types) ->
|
put_types(Types) ->
|
||||||
put_types(types_vsn(Types), Types).
|
persistent_term:put({?MODULE, types}, Types).
|
||||||
|
|
||||||
put_types(V, Types) ->
|
|
||||||
K = pt_key(),
|
|
||||||
Old = case persistent_term:get(K, undefined) of
|
|
||||||
undefined -> default_types_pt();
|
|
||||||
Existing -> Existing
|
|
||||||
end,
|
|
||||||
put_types_(K, V, Types, Old).
|
|
||||||
|
|
||||||
put_types_(K, V, Types, #{latest_vsn := V0, types := Types0} = Old) ->
|
|
||||||
New = case V > V0 of
|
|
||||||
true ->
|
|
||||||
Old#{latest_vsn := V,
|
|
||||||
types := Types0#{V => Types#{vsn => V}}};
|
|
||||||
false ->
|
|
||||||
Old#{types := Types0#{V => Types#{vsn => V}}}
|
|
||||||
end,
|
|
||||||
persistent_term:put(K, New).
|
|
||||||
|
|
||||||
types_from_list(L) ->
|
types_from_list(L) ->
|
||||||
types_from_list(L, registered_types()).
|
lists:foldl(fun elem_to_type/2, dynamic_types(), L).
|
||||||
|
|
||||||
types_from_list(L, Types) ->
|
register_type(Code, Tag, Template) when is_integer(Code), Code >= 0 ->
|
||||||
gmser_dyn_types:from_list(L, Types).
|
#{codes := Codes, rev := Rev, templates := Temps} = Types = registered_types(),
|
||||||
|
|
||||||
register_type(Code, Tag, Template) ->
|
|
||||||
register_type(latest_vsn(), Code, Tag, Template).
|
|
||||||
|
|
||||||
register_type(Vsn, Code, Tag, Template) when is_integer(Code), Code >= 0 ->
|
|
||||||
#{codes := Codes, rev := Rev, templates := Temps} = Types = registered_types(Vsn),
|
|
||||||
case {is_map_key(Code, Codes), is_map_key(Tag, Rev)} of
|
case {is_map_key(Code, Codes), is_map_key(Tag, Rev)} of
|
||||||
{false, false} ->
|
{false, false} ->
|
||||||
New = Types#{ codes := Codes#{Code => Tag}
|
New = Types#{ codes := Codes#{Code => Tag}
|
||||||
@ -700,11 +512,37 @@ cache_label(Code, Label) when is_integer(Code), Code >= 0, is_atom(Label) ->
|
|||||||
{_,true} -> error(code_exists)
|
{_,true} -> error(code_exists)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
revert_to_default_types() ->
|
elem_to_type({Tag, Code, Template}, Acc) when is_atom(Tag), is_integer(Code) ->
|
||||||
persistent_term:put(pt_key(), default_types_pt()).
|
#{codes := Codes, rev := Rev, templates := Temps} = Acc,
|
||||||
|
case {is_map_key(Tag, Rev), is_map_key(Code, Codes)} of
|
||||||
|
{false, false} ->
|
||||||
|
Acc#{ codes := Codes#{Code => Tag}
|
||||||
|
, rev := Rev#{Tag => Code}
|
||||||
|
, templates => Temps#{Tag => Template}
|
||||||
|
};
|
||||||
|
{true, _} -> error({duplicate_tag, Tag});
|
||||||
|
{_, true} -> error({duplicate_code, Code})
|
||||||
|
end;
|
||||||
|
elem_to_type({labels, Lbls}, Acc) ->
|
||||||
|
lists:foldl(fun add_label/2, Acc, Lbls);
|
||||||
|
elem_to_type(Elem, _) ->
|
||||||
|
error({invalid_type_list_element, Elem}).
|
||||||
|
|
||||||
default_types_pt() ->
|
add_label({L, Code}, #{labels := Lbls, rev_labels := RevLbls} = Acc)
|
||||||
#{latest_vsn => ?VSN, types => #{?VSN => dynamic_types()}}.
|
when is_atom(L), is_integer(Code), Code > 0 ->
|
||||||
|
case {is_map_key(L, Lbls), is_map_key(Code, RevLbls)} of
|
||||||
|
{false, false} ->
|
||||||
|
Acc#{labels := Lbls#{L => Code},
|
||||||
|
rev_labels := RevLbls#{Code => L}};
|
||||||
|
{true, _} -> error({duplicate_label, L});
|
||||||
|
{_, true} -> error({duplicate_label_code, Code})
|
||||||
|
end;
|
||||||
|
add_label(Elem, _) ->
|
||||||
|
error({invalid_label_elem, Elem}).
|
||||||
|
|
||||||
|
|
||||||
|
revert_to_default_types() ->
|
||||||
|
persistent_term:put({?MODULE, types}, dynamic_types()).
|
||||||
|
|
||||||
assert_sizes(#{codes := Codes, rev := Rev, templates := Ts} = Types) ->
|
assert_sizes(#{codes := Codes, rev := Rev, templates := Ts} = Types) ->
|
||||||
assert_sizes(map_size(Codes), map_size(Rev), map_size(Ts), Types).
|
assert_sizes(map_size(Codes), map_size(Rev), map_size(Ts), Types).
|
||||||
@ -787,11 +625,6 @@ round_trip_test_() ->
|
|||||||
T <- t_sample_types()
|
T <- t_sample_types()
|
||||||
].
|
].
|
||||||
|
|
||||||
ser_round_trip_test_() ->
|
|
||||||
[?_test(t_ser_round_trip(T)) ||
|
|
||||||
T <- t_sample_types()
|
|
||||||
].
|
|
||||||
|
|
||||||
t_sample_types() ->
|
t_sample_types() ->
|
||||||
[ 5
|
[ 5
|
||||||
, -5
|
, -5
|
||||||
@ -817,31 +650,19 @@ 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_options())
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
dynamic_types_test_() ->
|
dynamic_types_test_() ->
|
||||||
[ ?_test(revert_to_default_types())
|
[ ?_test(revert_to_default_types())
|
||||||
, ?_test(t_typed_map())
|
, ?_test(t_typed_map())
|
||||||
, ?_test(t_alts())
|
, ?_test(t_alts())
|
||||||
, ?_test(t_switch())
|
|
||||||
, ?_test(t_anyints())
|
, ?_test(t_anyints())
|
||||||
, ?_test(t_missing_labels())
|
|
||||||
].
|
|
||||||
|
|
||||||
versioned_types_test_() ->
|
|
||||||
[ ?_test(t_new_version())
|
|
||||||
].
|
].
|
||||||
|
|
||||||
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))}).
|
||||||
|
|
||||||
t_ser_round_trip(T) ->
|
|
||||||
Data = serialize(T),
|
|
||||||
?debugFmt("Data (~p) = ~p~n", [T, Data]),
|
|
||||||
?assertMatch({T, T}, {T, deserialize(Data)}).
|
|
||||||
|
|
||||||
t_round_trip_typed(Type, T) ->
|
t_round_trip_typed(Type, T) ->
|
||||||
?debugVal(T),
|
?debugVal(T),
|
||||||
?assertMatch({T, T}, {T, decode(encode_typed(Type, T))}).
|
?assertMatch({T, T}, {T, decode(encode_typed(Type, T))}).
|
||||||
@ -894,6 +715,7 @@ t_reg_template_vsnd_fun() ->
|
|||||||
{3,4} = decode(E),
|
{3,4} = decode(E),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
t_reg_label_cache() ->
|
t_reg_label_cache() ->
|
||||||
Enc0 = gmser_dyn:encode('1'),
|
Enc0 = gmser_dyn:encode('1'),
|
||||||
?debugFmt("Enc0 (no cache): ~w", [Enc0]),
|
?debugFmt("Enc0 (no cache): ~w", [Enc0]),
|
||||||
@ -916,7 +738,7 @@ t_reg_label_cache() ->
|
|||||||
?assertNotEqual(Enc0a, Enc1a).
|
?assertNotEqual(Enc0a, Enc1a).
|
||||||
|
|
||||||
t_reg_label_cache2() ->
|
t_reg_label_cache2() ->
|
||||||
TFromL = types_from_list(
|
TFromL = gmser_dyn:types_from_list(
|
||||||
[ {lbl_tup2, 1003, {label, label}}
|
[ {lbl_tup2, 1003, {label, label}}
|
||||||
, {labels,
|
, {labels,
|
||||||
[{'1', 49}]}
|
[{'1', 49}]}
|
||||||
@ -928,69 +750,19 @@ 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_options() ->
|
|
||||||
register_types(set_opts(#{missing_labels => convert})),
|
|
||||||
[Dyn,Vsn,[Am,<<"random">>]] = gmser_dyn:encode(random),
|
|
||||||
EncNewAm = [Dyn,Vsn,[Am,<<"foo12345">>]],
|
|
||||||
<<"foo12345">> = gmser_dyn:decode(EncNewAm),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_typed_map() ->
|
t_typed_map() ->
|
||||||
Term = #{a => 13, {key,1} => [a]},
|
Term = #{a => 13, {key,1} => [a]},
|
||||||
Items = [{a,int},{{key,1},[label]}],
|
Enc = encode_typed(#{items => [{a,int},{{key,1},[label]}]}, Term),
|
||||||
OptItems = [{opt, b, int} | Items],
|
?assertEqual(Term, decode(Enc)).
|
||||||
Enc = encode_typed(#{items => Items}, Term),
|
|
||||||
?assertEqual(Term, decode(Enc)),
|
|
||||||
?assertEqual(Enc, encode_typed(#{items => Items}, Term)),
|
|
||||||
?assertEqual(Enc, encode_typed(#{items => OptItems}, Term)),
|
|
||||||
Term1 = Term#{b => 4},
|
|
||||||
Enc1 = encode_typed(#{items => OptItems}, Term1),
|
|
||||||
?assertEqual(Term1, decode(Enc1)),
|
|
||||||
?assertEqual(Enc, encode_typed(#{items => Items}, Term1)).
|
|
||||||
|
|
||||||
t_alts() ->
|
t_alts() ->
|
||||||
t_round_trip_typed(#{alt => [negint, int]}, -4),
|
t_round_trip_typed(#{alt => [negint, int]}, -4),
|
||||||
t_round_trip_typed(#{alt => [negint, int]}, 4),
|
t_round_trip_typed(#{alt => [negint, int]}, 4),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_switch() ->
|
|
||||||
T = #{switch => #{a => int, b => binary}},
|
|
||||||
t_round_trip_typed(T, #{a => 17}),
|
|
||||||
t_round_trip_typed(T, #{b => <<"foo">>}),
|
|
||||||
?assertError({illegal,int,<<"foo">>}, encode_typed(T, #{a => <<"foo">>})),
|
|
||||||
MMap = #{a => 17, b => <<"foo">>},
|
|
||||||
?assertError({illegal, singleton_map, MMap}, encode_typed(T, MMap)).
|
|
||||||
|
|
||||||
t_anyints() ->
|
t_anyints() ->
|
||||||
t_round_trip_typed(anyint, -5),
|
t_round_trip_typed(anyint, -5),
|
||||||
t_round_trip_typed(anyint, 5),
|
t_round_trip_typed(anyint, 5),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_missing_labels() ->
|
|
||||||
[Dyn,Vsn,[Am,<<"random">>]] = gmser_dyn:encode(random),
|
|
||||||
EncNewAm = [Dyn,Vsn,[Am,<<"flurbee">>]],
|
|
||||||
?assertError(badarg, gmser_dyn:decode(EncNewAm)),
|
|
||||||
?assertError(badarg, gmser_dyn:decode(EncNewAm, set_opts(#{missing_labels => fail}))),
|
|
||||||
<<"flurbee">> = gmser_dyn:decode(EncNewAm, set_opts(#{missing_labels => convert})),
|
|
||||||
true = is_atom(gmser_dyn:decode(EncNewAm, set_opts(#{missing_labels => create}))),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_new_version() ->
|
|
||||||
V = latest_vsn(),
|
|
||||||
Types0 = registered_types(V),
|
|
||||||
V1 = V+1,
|
|
||||||
Types1 = types_from_list([{vsn, V1},
|
|
||||||
{msg1, 300, {int, int}}], Types0),
|
|
||||||
T2 = {3,5},
|
|
||||||
Enc21 = encode_typed(msg1, T2, Types1),
|
|
||||||
T2 = decode(Enc21, Types1),
|
|
||||||
V2 = V1+1,
|
|
||||||
Types2 = types_from_list([{vsn, V2},
|
|
||||||
{modify, {msg1, {int, int, int}}}], Types1),
|
|
||||||
Enc21 = encode_typed(msg1, T2, Types1),
|
|
||||||
?assertError({illegal,{int,int,int},T2}, encode_typed(msg1, T2, Types2)),
|
|
||||||
T3 = {3,5,7},
|
|
||||||
Enc32 = encode_typed(msg1, T3, Types2),
|
|
||||||
T3 = decode(Enc32, Types2).
|
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
-module(gmser_dyn_types).
|
|
||||||
|
|
||||||
-export([ add_type/4
|
|
||||||
, from_list/2
|
|
||||||
, expand/1 ]).
|
|
||||||
-export([ next_code/1 ]).
|
|
||||||
|
|
||||||
next_code(#{codes := Codes}) ->
|
|
||||||
lists:max(maps:keys(Codes)) + 1.
|
|
||||||
|
|
||||||
add_type(Tag, Code, Template, Types) ->
|
|
||||||
elem_to_type({Tag, Code, Template}, Types).
|
|
||||||
|
|
||||||
from_list(L, Types) ->
|
|
||||||
lists:foldl(fun elem_to_type/2, Types, L).
|
|
||||||
|
|
||||||
expand(#{vsn := V, templates := Templates0} = Types) ->
|
|
||||||
Templates =
|
|
||||||
maps:map(
|
|
||||||
fun(_, F) when is_function(F, 0) ->
|
|
||||||
F();
|
|
||||||
(_, F) when is_function(F, 1) ->
|
|
||||||
F(V);
|
|
||||||
(_, T) ->
|
|
||||||
T
|
|
||||||
end, Templates0),
|
|
||||||
Types#{templates := Templates}.
|
|
||||||
|
|
||||||
elem_to_type({Tag, Code, Template}, Acc) when is_atom(Tag), is_integer(Code) ->
|
|
||||||
#{codes := Codes, rev := Rev, templates := Temps} = Acc,
|
|
||||||
case {is_map_key(Tag, Rev), is_map_key(Code, Codes)} of
|
|
||||||
{false, false} ->
|
|
||||||
Acc#{ codes := Codes#{Code => Tag}
|
|
||||||
, rev := Rev#{Tag => Code}
|
|
||||||
, templates => Temps#{Tag => Template}
|
|
||||||
};
|
|
||||||
{true, _} -> error({duplicate_tag, Tag});
|
|
||||||
{_, true} -> error({duplicate_code, Code})
|
|
||||||
end;
|
|
||||||
elem_to_type({modify, {Tag, Template}}, Acc) ->
|
|
||||||
#{codes := _, rev := Rev, templates := Templates} = Acc,
|
|
||||||
_ = maps:get(Tag, Rev),
|
|
||||||
Templates1 = Templates#{Tag := Template},
|
|
||||||
Acc#{templates := Templates1};
|
|
||||||
elem_to_type({labels, Lbls}, Acc) ->
|
|
||||||
lists:foldl(fun add_label/2, Acc, Lbls);
|
|
||||||
elem_to_type({vsn, V}, Acc) ->
|
|
||||||
Acc#{vsn => V};
|
|
||||||
elem_to_type(Elem, _) ->
|
|
||||||
error({invalid_type, Elem}).
|
|
||||||
|
|
||||||
add_label({L, Code}, #{labels := Lbls, rev_labels := RevLbls} = Acc)
|
|
||||||
when is_atom(L), is_integer(Code), Code > 0 ->
|
|
||||||
case {is_map_key(L, Lbls), is_map_key(Code, RevLbls)} of
|
|
||||||
{false, false} ->
|
|
||||||
Acc#{labels := Lbls#{L => Code},
|
|
||||||
rev_labels := RevLbls#{Code => L}};
|
|
||||||
{true, _} -> error({duplicate_label, L});
|
|
||||||
{_, true} -> error({duplicate_label_code, Code})
|
|
||||||
end;
|
|
||||||
add_label(Elem, _) ->
|
|
||||||
error({invalid_label, Elem}).
|
|
Loading…
x
Reference in New Issue
Block a user