-module(distribute@codec@variant). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/distribute/codec/variant.gleam"). -export([new/0, add/6, unit/5, build/1]). -export_type([variant_strategy/1, variant_builder/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type variant_strategy(HSZ) :: {variant_strategy, integer(), binary(), fun((HSZ) -> {ok, bitstring()} | {error, nil}), fun((bitstring()) -> {ok, {HSZ, bitstring()}} | {error, distribute@codec:decode_error()})}. -opaque variant_builder(HTA) :: {variant_builder, list(variant_strategy(HTA))}. -file("src/distribute/codec/variant.gleam", 30). ?DOC(" Create a new builder for an ADT codec.\n"). -spec new() -> variant_builder(any()). new() -> {variant_builder, []}. -file("src/distribute/codec/variant.gleam", 38). ?DOC( " Add a variant with a payload to the codec.\n" " id: unique identifier (0-255). name: for error messages.\n" " inner: codec for the payload. wrap: ADT constructor.\n" " unwrap: extract payload or return Error(Nil) if wrong variant.\n" ). -spec add( variant_builder(HTD), integer(), binary(), distribute@codec:codec(HTF), fun((HTF) -> HTD), fun((HTD) -> {ok, HTF} | {error, nil}) ) -> variant_builder(HTD). add(Builder, Id, Name, Inner, Wrap, Unwrap) -> Strategy = {variant_strategy, Id, Name, fun(Value) -> case Unwrap(Value) of {ok, Payload} -> Bits@1 = case (erlang:element(2, Inner))(Payload) of {ok, Bits} -> Bits; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"distribute/codec/variant"/utf8>>, function => <<"add"/utf8>>, line => 53, value => _assert_fail, start => 1454, 'end' => 1498, pattern_start => 1465, pattern_end => 1473}) end, {ok, gleam@bit_array:append(<>, Bits@1)}; {error, _} -> {error, nil} end end, fun(Data) -> case Data of <> when Tag =:= Id -> case (erlang:element(4, Inner))(Rest) of {ok, {Val, Remaining}} -> {ok, {Wrap(Val), Remaining}}; {error, E} -> {error, E} end; _ -> {error, {tag_mismatch, Name, <<"unknown"/utf8>>}} end end}, {variant_builder, [Strategy | erlang:element(2, Builder)]}. -file("src/distribute/codec/variant.gleam", 76). ?DOC(" Add a variant without a payload (unit/constant).\n"). -spec unit( variant_builder(HTK), integer(), binary(), HTK, fun((HTK) -> boolean()) ) -> variant_builder(HTK). unit(Builder, Id, Name, Value, Match) -> Strategy = {variant_strategy, Id, Name, fun(Val) -> case Match(Val) of true -> {ok, <>}; false -> {error, nil} end end, fun(Data) -> case Data of <> when Tag =:= Id -> {ok, {Value, Rest}}; _ -> {error, {tag_mismatch, Name, <<"unknown"/utf8>>}} end end}, {variant_builder, [Strategy | erlang:element(2, Builder)]}. -file("src/distribute/codec/variant.gleam", 160). -spec find_encoder(list(variant_strategy(HTQ)), HTQ) -> {ok, bitstring()} | {error, nil}. find_encoder(Strategies, Value) -> case Strategies of [] -> {error, nil}; [S | Rest] -> case (erlang:element(4, S))(Value) of {ok, Bits} -> {ok, Bits}; {error, _} -> find_encoder(Rest, Value) end end. -file("src/distribute/codec/variant.gleam", 174). -spec validate_strategies(list(variant_strategy(any()))) -> nil. validate_strategies(Strategies) -> Ids = gleam@list:map(Strategies, fun(S) -> erlang:element(2, S) end), case begin _pipe = gleam@list:unique(Ids), erlang:length(_pipe) end =:= erlang:length(Ids) of true -> nil; false -> erlang:error(#{gleam_error => panic, message => <<"Variant codec: Duplicate IDs detected in builder"/utf8>>, file => <>, module => <<"distribute/codec/variant"/utf8>>, function => <<"validate_strategies"/utf8>>, line => 178}) end. -file("src/distribute/codec/variant.gleam", 110). ?DOC( " Finalize the builder and return a Codec(a).\n" " Panics if duplicate IDs are found.\n" ). -spec build(variant_builder(HTN)) -> distribute@codec:codec(HTN). build(Builder) -> Strategies = lists:reverse(erlang:element(2, Builder)), validate_strategies(Strategies), Encoder = fun(Value) -> case find_encoder(Strategies, Value) of {ok, Bits} -> {ok, Bits}; {error, _} -> {error, {encode_failed, <<"No variant matched for encoding ADT value"/utf8>>}} end end, Decoder_dict = gleam@list:fold( Strategies, maps:new(), fun(Acc, S) -> gleam@dict:insert(Acc, erlang:element(2, S), S) end ), Sized_decoder = fun(Data) -> case Data of <> -> case gleam_stdlib:map_get(Decoder_dict, Tag) of {ok, Strategy} -> (erlang:element(5, Strategy))(Data); {error, _} -> {error, {tag_mismatch, <<"one of "/utf8, (begin _pipe = gleam@list:map( Strategies, fun(S@1) -> erlang:element(3, S@1) end ), _pipe@1 = gleam@list:unique(_pipe), gleam@list:fold( _pipe@1, <<""/utf8>>, fun(Acc@1, N) -> case Acc@1 of <<""/utf8>> -> N; _ -> <<<>/binary, N/binary>> end end ) end)/binary>>, erlang:integer_to_binary(Tag)}} end; _ -> {error, {insufficient_data, <<"missing variant tag"/utf8>>}} end end, {codec, Encoder, distribute@codec:to_decoder(Sized_decoder), Sized_decoder}.