Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 795c7f7860 | |||
| 0d77ca0388 | |||
| ed204f8526 | |||
| a949d166f6 |
@@ -58,6 +58,24 @@ Labels correspond to (existing) atoms in Erlang.
|
|||||||
Decoding of a label results in a call to `binary_to_existing_atom/2`, so will
|
Decoding of a label results in a call to `binary_to_existing_atom/2`, so will
|
||||||
fail if the corresponding atom does not already exist.
|
fail if the corresponding atom does not already exist.
|
||||||
|
|
||||||
|
This behavior can be modified using the option `#{missing_labels => fail | create | convert}`,
|
||||||
|
where `fail` is the default, as described above, `convert` means that missing atoms are
|
||||||
|
converted to binaries, and `create` means that the atom is created dynamically.
|
||||||
|
|
||||||
|
The option can be passed e.g.:
|
||||||
|
```erlang
|
||||||
|
gmser_dyn:deserialize(Binary, set_opts(#{missing_labels => convert}))
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
```erlang
|
||||||
|
gmser_dyn:deserialize(Binary, set_opts(#{missing_labels => convert}, Types))
|
||||||
|
```
|
||||||
|
|
||||||
|
By calling `gmser_dyn:register_types/1`, after having added options to the type map,
|
||||||
|
the options can be made to take effect automatically.
|
||||||
|
|
||||||
|
|
||||||
It's possible to cache labels for more compact encoding.
|
It's possible to cache labels for more compact encoding.
|
||||||
Note that when caching labels, the same cache mapping needs to be used on the
|
Note that when caching labels, the same cache mapping needs to be used on the
|
||||||
decoder side.
|
decoder side.
|
||||||
|
|||||||
+60
-12
@@ -17,6 +17,9 @@
|
|||||||
%% 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
|
||||||
|
, get_opts/1
|
||||||
|
, set_opts/1
|
||||||
|
, set_opts/2
|
||||||
, types_from_list/1
|
, types_from_list/1
|
||||||
, revert_to_default_types/0
|
, revert_to_default_types/0
|
||||||
, dynamic_types/0 ]).
|
, dynamic_types/0 ]).
|
||||||
@@ -121,6 +124,7 @@ dynamic_types() ->
|
|||||||
, id => id
|
, id => id
|
||||||
, label => label
|
, label => label
|
||||||
}
|
}
|
||||||
|
, options => #{}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
vsn(Types) ->
|
vsn(Types) ->
|
||||||
@@ -326,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),
|
||||||
emit(E, label, Types,
|
case find_cached_label(V, Types) of
|
||||||
case find_cached_label(V, Types) of
|
error ->
|
||||||
error ->
|
encode_basic(label, V, E, Types);
|
||||||
encode_basic(label, V, E, Types);
|
{ok, Code} when is_integer(Code) ->
|
||||||
{ok, Code} when is_integer(Code) ->
|
emit(E, label, Types,
|
||||||
[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
|
||||||
@@ -399,15 +403,29 @@ emit(0, _, _, Enc) ->
|
|||||||
emit_code(Tag, #{rev := Tags}) ->
|
emit_code(Tag, #{rev := Tags}) ->
|
||||||
encode_basic(int, maps:get(Tag, Tags)).
|
encode_basic(int, maps:get(Tag, Tags)).
|
||||||
|
|
||||||
decode_basic(Type, [Tag,V], #{codes := Codes}) ->
|
decode_basic(Type, [Tag,V], #{codes := Codes} = Types) ->
|
||||||
case decode_basic(int, Tag) of
|
case decode_basic(int, Tag) of
|
||||||
Code when map_get(Code, Codes) == Type ->
|
Code when map_get(Code, Codes) == Type ->
|
||||||
decode_basic(Type, V);
|
decode_basic_(Type, V, Types);
|
||||||
_ ->
|
_ ->
|
||||||
error(illegal)
|
error(illegal)
|
||||||
end;
|
end;
|
||||||
decode_basic(Type, V, _) ->
|
decode_basic(Type, V, Types) ->
|
||||||
decode_basic(Type, V).
|
decode_basic_(Type, V, Types).
|
||||||
|
|
||||||
|
decode_basic_(label, Fld, #{options := #{missing_labels := Opt}}) ->
|
||||||
|
Bin = decode_basic(binary, Fld),
|
||||||
|
case Opt of
|
||||||
|
create -> binary_to_atom(Bin, utf8);
|
||||||
|
fail -> binary_to_existing_atom(Bin, utf8);
|
||||||
|
convert ->
|
||||||
|
try binary_to_existing_atom(Bin, utf8)
|
||||||
|
catch
|
||||||
|
error:_ -> Bin
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
decode_basic_(Type, Fld, _) ->
|
||||||
|
decode_basic(Type, Fld).
|
||||||
|
|
||||||
decode_basic(label, Fld) ->
|
decode_basic(label, Fld) ->
|
||||||
binary_to_existing_atom(decode_basic(binary, Fld), utf8);
|
binary_to_existing_atom(decode_basic(binary, Fld), utf8);
|
||||||
@@ -443,11 +461,14 @@ register_types(Types) when is_map(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, #{}),
|
||||||
#{codes := Codes0, rev := Rev0, labels := Labels0, templates := Templates0} =
|
Options = maps:get(options, Types, #{}),
|
||||||
|
#{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),
|
||||||
@@ -473,6 +494,15 @@ register_type(Code, Tag, Template) when is_integer(Code), Code >= 0 ->
|
|||||||
{_, true} -> error(tag_exists)
|
{_, true} -> error(tag_exists)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
set_opts(Opts) ->
|
||||||
|
set_opts(Opts, registered_types()).
|
||||||
|
|
||||||
|
set_opts(Opts, Types) ->
|
||||||
|
Types#{options => Opts}.
|
||||||
|
|
||||||
|
get_opts(#{options := Opts}) ->
|
||||||
|
Opts.
|
||||||
|
|
||||||
cache_label(Code, Label) when is_integer(Code), Code >= 0, is_atom(Label) ->
|
cache_label(Code, Label) when is_integer(Code), Code >= 0, is_atom(Label) ->
|
||||||
#{labels := Lbls, rev_labels := RevLbls} = Types = registered_types(),
|
#{labels := Lbls, rev_labels := RevLbls} = Types = registered_types(),
|
||||||
case {is_map_key(Label, Lbls), is_map_key(Code, RevLbls)} of
|
case {is_map_key(Label, Lbls), is_map_key(Code, RevLbls)} of
|
||||||
@@ -623,6 +653,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_options())
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
dynamic_types_test_() ->
|
dynamic_types_test_() ->
|
||||||
@@ -630,6 +661,7 @@ dynamic_types_test_() ->
|
|||||||
, ?_test(t_typed_map())
|
, ?_test(t_typed_map())
|
||||||
, ?_test(t_alts())
|
, ?_test(t_alts())
|
||||||
, ?_test(t_anyints())
|
, ?_test(t_anyints())
|
||||||
|
, ?_test(t_missing_labels())
|
||||||
].
|
].
|
||||||
|
|
||||||
t_round_trip(T) ->
|
t_round_trip(T) ->
|
||||||
@@ -723,6 +755,13 @@ 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]},
|
||||||
Enc = encode_typed(#{items => [{a,int},{{key,1},[label]}]}, Term),
|
Enc = encode_typed(#{items => [{a,int},{{key,1},[label]}]}, Term),
|
||||||
@@ -738,4 +777,13 @@ t_anyints() ->
|
|||||||
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.
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|||||||
Reference in New Issue
Block a user