Compare commits

...

4 Commits

Author SHA1 Message Date
Ulf Wiger 795c7f7860 Save options, test cases for missing labels
Gajumaru Serialization Tests / tests (push) Successful in 49m42s
2025-04-23 23:36:03 +02:00
uwiger 0d77ca0388 Fix function_clause bug (#51)
Gajumaru Serialization Tests / tests (push) Successful in 49m16s
Co-authored-by: Ulf Wiger <ulf@wiger.net>
Reviewed-on: #51
2025-04-14 19:10:58 +09:00
uwiger ed204f8526 Merge pull request 'uw-dyn-options' (#50) from uw-dyn-options into master
Gajumaru Serialization Tests / tests (push) Failing after 49m16s
Reviewed-on: #50
2025-04-14 18:59:38 +09:00
Ulf Wiger a949d166f6 Add options for deserialization of missing labels
Gajumaru Serialization Tests / tests (push) Failing after 49m16s
2025-04-14 11:54:48 +02:00
2 changed files with 78 additions and 12 deletions
+18
View File
@@ -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
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.
Note that when caching labels, the same cache mapping needs to be used on the
decoder side.
+60 -12
View File
@@ -17,6 +17,9 @@
%% register a type schema, inspect existing schema
-export([ register_types/1
, registered_types/0
, get_opts/1
, set_opts/1
, set_opts/2
, types_from_list/1
, revert_to_default_types/0
, dynamic_types/0 ]).
@@ -121,6 +124,7 @@ dynamic_types() ->
, id => id
, label => label
}
, options => #{}
}.
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_from_template(label, V, E, _, Types) ->
assert_type(is_atom(V), label, V),
emit(E, label, Types,
case find_cached_label(V, Types) of
error ->
encode_basic(label, V, E, Types);
{ok, Code} when is_integer(Code) ->
[encode_basic(int, Code)]
end);
case find_cached_label(V, Types) of
error ->
encode_basic(label, V, E, Types);
{ok, Code} when is_integer(Code) ->
emit(E, label, Types,
[encode_basic(int, Code)])
end;
encode_from_template(Type, V, E, _, Types) when Type == id
; Type == binary
; Type == bool
@@ -399,15 +403,29 @@ emit(0, _, _, Enc) ->
emit_code(Tag, #{rev := 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
Code when map_get(Code, Codes) == Type ->
decode_basic(Type, V);
decode_basic_(Type, V, Types);
_ ->
error(illegal)
end;
decode_basic(Type, V, _) ->
decode_basic(Type, V).
decode_basic(Type, V, Types) ->
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) ->
binary_to_existing_atom(decode_basic(binary, Fld), utf8);
@@ -443,11 +461,14 @@ register_types(Types) when is_map(Types) ->
Rev = rev_codes(Codes),
Templates = maps:get(templates, 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(),
Merged = #{ codes => maps:merge(Codes0, Codes)
, rev => maps:merge(Rev0, Rev)
, templates => maps:merge(Templates0, Templates)
, options => maps:merge(Options0, Options)
, labels => maps:merge(Labels0, Labels) },
assert_sizes(Merged),
assert_mappings(Merged),
@@ -473,6 +494,15 @@ register_type(Code, Tag, Template) when is_integer(Code), Code >= 0 ->
{_, true} -> error(tag_exists)
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) ->
#{labels := Lbls, rev_labels := RevLbls} = Types = registered_types(),
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_label_cache())
, ?_test(t_reg_label_cache2())
, ?_test(t_reg_options())
]}.
dynamic_types_test_() ->
@@ -630,6 +661,7 @@ dynamic_types_test_() ->
, ?_test(t_typed_map())
, ?_test(t_alts())
, ?_test(t_anyints())
, ?_test(t_missing_labels())
].
t_round_trip(T) ->
@@ -723,6 +755,13 @@ t_reg_label_cache2() ->
[<<0>>,<<1>>,[<<3,235>>,[[<<49>>],[<<49>>]]]] = 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() ->
Term = #{a => 13, {key,1} => [a]},
Enc = encode_typed(#{items => [{a,int},{{key,1},[label]}]}, Term),
@@ -738,4 +777,13 @@ t_anyints() ->
t_round_trip_typed(anyint, 5),
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.