From a949d166f627fcafb40e6113e48e22daea49694d Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 14 Apr 2025 11:54:48 +0200 Subject: [PATCH] Add options for deserialization of missing labels --- README.md | 18 ++++++++++++++++++ src/gmser_dyn.erl | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7f73e6c..d24432f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/gmser_dyn.erl b/src/gmser_dyn.erl index c3b0713..3253a3d 100644 --- a/src/gmser_dyn.erl +++ b/src/gmser_dyn.erl @@ -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) -> @@ -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_(label, Fld, _) -> + decode_basic(label, Fld). decode_basic(label, Fld) -> binary_to_existing_atom(decode_basic(binary, Fld), utf8); @@ -473,6 +491,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