Packages
Typed distributed messaging for Gleam on the BEAM.
Current section
Files
Jump to
Current section
Files
src/distribute@codec.erl
-module(distribute@codec).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/distribute/codec.gleam").
-export([encode/2, decode/2, decode_sized/2, int_sized_decoder/0, int_decoder/0, int_encoder/0, int/0, to_decoder/1, string_encoder/0, string_sized_decoder/0, string_decoder/0, check_32bit_length/2, float_encoder/0, float_sized_decoder/0, float_decoder/0, bool_encoder/0, bool_sized_decoder/0, bool_decoder/0, bitarray_encoder/0, bitarray_sized_decoder/0, bitarray_decoder/0, list_sized_decoder/1, list_decoder/1, list/1, list_encoder/1, nil_encoder/0, nil_sized_decoder/0, nil_decoder/0, subject_encoder/0, subject_sized_decoder/0, subject_decoder/0, string/0, float/0, bool/0, bitarray/0, nil/0, subject/0, map/3, decode_error_to_string/1, encode_error_to_string/1]).
-export_type([encode_error/0, decode_error/0, codec/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 encode_error() :: {invalid_value, binary()} |
{encode_failed, binary()} |
{value_too_large, binary()}.
-type decode_error() :: {invalid_binary, binary()} |
{type_mismatch, binary()} |
{decode_failed, binary()} |
{insufficient_data, binary()} |
decode_timeout |
{tag_mismatch, binary(), binary()} |
{version_mismatch, integer(), integer()} |
{payload_too_large, integer()} |
{list_too_long, integer(), integer()}.
-type codec(FIV) :: {codec,
fun((FIV) -> {ok, bitstring()} | {error, encode_error()}),
fun((bitstring()) -> {ok, FIV} | {error, decode_error()}),
fun((bitstring()) -> {ok, {FIV, bitstring()}} | {error, decode_error()})}.
-file("src/distribute/codec.gleam", 81).
-spec encode(fun((FJF) -> {ok, bitstring()} | {error, encode_error()}), FJF) -> {ok,
bitstring()} |
{error, encode_error()}.
encode(Encoder, Value) ->
Encoder(Value).
-file("src/distribute/codec.gleam", 85).
-spec decode(
fun((bitstring()) -> {ok, FJJ} | {error, decode_error()}),
bitstring()
) -> {ok, FJJ} | {error, decode_error()}.
decode(Decoder, Data) ->
Decoder(Data).
-file("src/distribute/codec.gleam", 89).
-spec decode_sized(
fun((bitstring()) -> {ok, {FJN, bitstring()}} | {error, decode_error()}),
bitstring()
) -> {ok, {FJN, bitstring()}} | {error, decode_error()}.
decode_sized(Decoder, Data) ->
Decoder(Data).
-file("src/distribute/codec.gleam", 227).
-spec int_sized_decoder() -> fun((bitstring()) -> {ok, {integer(), bitstring()}} |
{error, decode_error()}).
int_sized_decoder() ->
fun(Data) -> case Data of
<<I:64/signed, Rest/binary>> ->
{ok, {I, Rest}};
_ ->
{error, {invalid_binary, <<"int64"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 236).
-spec int_decoder() -> fun((bitstring()) -> {ok, integer()} |
{error, decode_error()}).
int_decoder() ->
to_decoder(int_sized_decoder()).
-file("src/distribute/codec.gleam", 215).
-spec int_encoder() -> fun((integer()) -> {ok, bitstring()} |
{error, encode_error()}).
int_encoder() ->
fun(I) ->
case (I > 9223372036854775807) orelse (I < -9223372036854775808) of
true ->
{error,
{value_too_large,
<<<<"int "/utf8, (erlang:integer_to_binary(I))/binary>>/binary,
" out of signed 64-bit range"/utf8>>}};
false ->
{ok, <<I:64>>}
end
end.
-file("src/distribute/codec.gleam", 534).
-spec int() -> codec(integer()).
int() ->
{codec, int_encoder(), int_decoder(), int_sized_decoder()}.
-file("src/distribute/codec.gleam", 106).
?DOC(
" Turn a `SizedDecoder` into a top-level `Decoder` with strict parsing.\n"
"\n"
" The frame must consume the binary completely. Any unconsumed\n"
" trailing bytes are a protocol violation (data smuggling, double\n"
" payloads, framing bug) and surface as `Error(InvalidBinary(\"trailing\n"
" bytes ...\"))`. Earlier drafts silently dropped the leftover.\n"
"\n"
" Composite codecs that need to chain frames within one binary (lists,\n"
" tuples) call the underlying `SizedDecoder` directly, so they keep\n"
" working as before. Only the top-level entry point is strict.\n"
).
-spec to_decoder(
fun((bitstring()) -> {ok, {FJR, bitstring()}} | {error, decode_error()})
) -> fun((bitstring()) -> {ok, FJR} | {error, decode_error()}).
to_decoder(Sized) ->
fun(Data) -> case Sized(Data) of
{ok, {Value, <<>>}} ->
{ok, Value};
{ok, {_, Rest}} ->
Leftover = erlang:byte_size(Rest),
{error,
{invalid_binary,
<<<<"trailing bytes ("/utf8,
(erlang:integer_to_binary(Leftover))/binary>>/binary,
") at top level"/utf8>>}};
{error, E} ->
{error, E}
end end.
-file("src/distribute/codec.gleam", 125).
-spec string_encoder() -> fun((binary()) -> {ok, bitstring()} |
{error, encode_error()}).
string_encoder() ->
fun(S) ->
Bytes = gleam_stdlib:identity(S),
Len = erlang:byte_size(Bytes),
case Len > 4294967295 of
true ->
{error,
{value_too_large,
<<<<"string of "/utf8,
(erlang:integer_to_binary(Len))/binary>>/binary,
" bytes exceeds 32-bit length prefix"/utf8>>}};
false ->
{ok, <<Len:32, Bytes/bitstring>>}
end
end.
-file("src/distribute/codec.gleam", 141).
-spec string_sized_decoder() -> fun((bitstring()) -> {ok,
{binary(), bitstring()}} |
{error, decode_error()}).
string_sized_decoder() ->
fun(Data) -> case Data of
<<Len:32, Rest/binary>> ->
Rest_size = erlang:byte_size(Rest),
case Rest_size >= Len of
false ->
{error, {insufficient_data, <<"string"/utf8>>}};
true ->
gleam@result:'try'(
begin
_pipe = gleam_stdlib:bit_array_slice(
Rest,
0,
Len
),
_pipe@1 = gleam@result:replace_error(
_pipe,
{insufficient_data, <<"string slice"/utf8>>}
),
gleam@result:map(
_pipe@1,
fun distribute_ffi_utils:binary_copy/1
)
end,
fun(Str_bytes) ->
gleam@result:'try'(
begin
_pipe@2 = gleam@bit_array:to_string(
Str_bytes
),
gleam@result:replace_error(
_pipe@2,
{invalid_binary,
<<"invalid UTF-8"/utf8>>}
)
end,
fun(S) ->
Remaining = case gleam_stdlib:bit_array_slice(
Rest,
Len,
Rest_size - Len
) of
{ok, R} ->
R;
{error, _} ->
<<>>
end,
{ok, {S, Remaining}}
end
)
end
)
end;
_ ->
{error,
{invalid_binary, <<"missing string length prefix"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 171).
-spec string_decoder() -> fun((bitstring()) -> {ok, binary()} |
{error, decode_error()}).
string_decoder() ->
to_decoder(string_sized_decoder()).
-file("src/distribute/codec.gleam", 195).
?DOC(false).
-spec check_32bit_length(integer(), binary()) -> {ok, nil} |
{error, encode_error()}.
check_32bit_length(Len, What) ->
case Len > 4294967295 of
true ->
{error,
{value_too_large,
<<<<<<What/binary, " of "/utf8>>/binary,
(erlang:integer_to_binary(Len))/binary>>/binary,
" bytes exceeds 32-bit length prefix"/utf8>>}};
false ->
{ok, nil}
end.
-file("src/distribute/codec.gleam", 211).
?DOC(
" Runtime cap for the element count a single `list_decoder` invocation will\n"
" materialise. Bounds in-process memory and CPU cost of hostile frames\n"
" independently of network payload size.\n"
).
-spec decoded_list_elements_cap() -> integer().
decoded_list_elements_cap() ->
erlang:element(6, distribute@config:get()).
-file("src/distribute/codec.gleam", 244).
-spec float_encoder() -> fun((float()) -> {ok, bitstring()} |
{error, encode_error()}).
float_encoder() ->
fun(F) -> {ok, <<F/float>>} end.
-file("src/distribute/codec.gleam", 248).
-spec float_sized_decoder() -> fun((bitstring()) -> {ok, {float(), bitstring()}} |
{error, decode_error()}).
float_sized_decoder() ->
fun(Data) -> case Data of
<<F/float, Rest/binary>> ->
{ok, {F, Rest}};
_ ->
{error, {invalid_binary, <<"float64"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 257).
-spec float_decoder() -> fun((bitstring()) -> {ok, float()} |
{error, decode_error()}).
float_decoder() ->
to_decoder(float_sized_decoder()).
-file("src/distribute/codec.gleam", 265).
-spec bool_encoder() -> fun((boolean()) -> {ok, bitstring()} |
{error, encode_error()}).
bool_encoder() ->
fun(B) -> case B of
true ->
{ok, <<1>>};
false ->
{ok, <<0>>}
end end.
-file("src/distribute/codec.gleam", 274).
-spec bool_sized_decoder() -> fun((bitstring()) -> {ok,
{boolean(), bitstring()}} |
{error, decode_error()}).
bool_sized_decoder() ->
fun(Data) -> case Data of
<<1, Rest/binary>> ->
{ok, {true, Rest}};
<<0, Rest@1/binary>> ->
{ok, {false, Rest@1}};
_ ->
{error, {invalid_binary, <<"bool"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 284).
-spec bool_decoder() -> fun((bitstring()) -> {ok, boolean()} |
{error, decode_error()}).
bool_decoder() ->
to_decoder(bool_sized_decoder()).
-file("src/distribute/codec.gleam", 292).
-spec bitarray_encoder() -> fun((bitstring()) -> {ok, bitstring()} |
{error, encode_error()}).
bitarray_encoder() ->
fun(Bytes) ->
Len = erlang:byte_size(Bytes),
case Len > 4294967295 of
true ->
{error,
{value_too_large,
<<<<"bitarray of "/utf8,
(erlang:integer_to_binary(Len))/binary>>/binary,
" bytes exceeds 32-bit length prefix"/utf8>>}};
false ->
{ok, <<Len:32, Bytes/bitstring>>}
end
end.
-file("src/distribute/codec.gleam", 307).
-spec bitarray_sized_decoder() -> fun((bitstring()) -> {ok,
{bitstring(), bitstring()}} |
{error, decode_error()}).
bitarray_sized_decoder() ->
fun(Data) -> case Data of
<<Len:32, Rest/binary>> ->
Rest_size = erlang:byte_size(Rest),
case Rest_size >= Len of
false ->
{error, {insufficient_data, <<"bitarray"/utf8>>}};
true ->
gleam@result:'try'(
begin
_pipe = gleam_stdlib:bit_array_slice(
Rest,
0,
Len
),
_pipe@1 = gleam@result:replace_error(
_pipe,
{insufficient_data,
<<"bitarray slice"/utf8>>}
),
gleam@result:map(
_pipe@1,
fun distribute_ffi_utils:binary_copy/1
)
end,
fun(Bytes) ->
Remaining = case gleam_stdlib:bit_array_slice(
Rest,
Len,
Rest_size - Len
) of
{ok, R} ->
R;
{error, _} ->
<<>>
end,
{ok, {Bytes, Remaining}}
end
)
end;
_ ->
{error, {invalid_binary, <<"bitarray length prefix"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 333).
-spec bitarray_decoder() -> fun((bitstring()) -> {ok, bitstring()} |
{error, decode_error()}).
bitarray_decoder() ->
to_decoder(bitarray_sized_decoder()).
-file("src/distribute/codec.gleam", 404).
-spec decode_list_elements(
bitstring(),
fun((bitstring()) -> {ok, {FLE, bitstring()}} | {error, decode_error()}),
integer(),
list(FLE)
) -> {ok, {list(FLE), bitstring()}} | {error, decode_error()}.
decode_list_elements(Data, Dec, Remaining, Acc) ->
case Remaining of
0 ->
{ok, {lists:reverse(Acc), Data}};
_ ->
case Dec(Data) of
{ok, {Element, Rest}} ->
decode_list_elements(
Rest,
Dec,
Remaining - 1,
[Element | Acc]
);
{error, E} ->
{error, E}
end
end.
-file("src/distribute/codec.gleam", 370).
-spec list_sized_decoder(
fun((bitstring()) -> {ok, {FKP, bitstring()}} | {error, decode_error()})
) -> fun((bitstring()) -> {ok, {list(FKP), bitstring()}} |
{error, decode_error()}).
list_sized_decoder(Element_decoder) ->
fun(Data) ->
Cap = decoded_list_elements_cap(),
case Data of
<<Count:32, Rest/binary>> ->
case Count > Cap of
true ->
{error, {list_too_long, Count, Cap}};
false ->
decode_list_elements(Rest, Element_decoder, Count, [])
end;
_ ->
{error, {invalid_binary, <<"list length prefix"/utf8>>}}
end
end.
-file("src/distribute/codec.gleam", 386).
-spec list_decoder(
fun((bitstring()) -> {ok, {FKT, bitstring()}} | {error, decode_error()})
) -> fun((bitstring()) -> {ok, list(FKT)} | {error, decode_error()}).
list_decoder(Element_decoder) ->
to_decoder(list_sized_decoder(Element_decoder)).
-file("src/distribute/codec.gleam", 574).
-spec list(codec(FMD)) -> codec(list(FMD)).
list(Element) ->
{codec,
list_encoder(erlang:element(2, Element)),
list_decoder(erlang:element(4, Element)),
list_sized_decoder(erlang:element(4, Element))}.
-file("src/distribute/codec.gleam", 390).
-spec encode_list_elements(
list(FKX),
fun((FKX) -> {ok, bitstring()} | {error, encode_error()}),
list(bitstring())
) -> {ok, list(bitstring())} | {error, encode_error()}.
encode_list_elements(Items, Enc, Acc) ->
case Items of
[] ->
{ok, lists:reverse(Acc)};
[Head | Tail] ->
gleam@result:'try'(
Enc(Head),
fun(Bytes) -> encode_list_elements(Tail, Enc, [Bytes | Acc]) end
)
end.
-file("src/distribute/codec.gleam", 346).
-spec list_encoder(fun((FKL) -> {ok, bitstring()} | {error, encode_error()})) -> fun((list(FKL)) -> {ok,
bitstring()} |
{error, encode_error()}).
list_encoder(Element_encoder) ->
fun(Items) ->
Cap = decoded_list_elements_cap(),
Len = erlang:length(Items),
case Len > Cap of
true ->
{error,
{value_too_large,
<<<<<<"list of "/utf8,
(erlang:integer_to_binary(Len))/binary>>/binary,
" elements exceeds decoder cap of "/utf8>>/binary,
(erlang:integer_to_binary(Cap))/binary>>}};
false ->
gleam@result:'try'(
encode_list_elements(Items, Element_encoder, []),
fun(Chunks) ->
{ok,
gleam_stdlib:bit_array_concat([<<Len:32>> | Chunks])}
end
)
end
end.
-file("src/distribute/codec.gleam", 426).
-spec nil_encoder() -> fun((nil) -> {ok, bitstring()} | {error, encode_error()}).
nil_encoder() ->
fun(_) -> {ok, <<>>} end.
-file("src/distribute/codec.gleam", 430).
-spec nil_sized_decoder() -> fun((bitstring()) -> {ok, {nil, bitstring()}} |
{error, decode_error()}).
nil_sized_decoder() ->
fun(Data) -> {ok, {nil, Data}} end.
-file("src/distribute/codec.gleam", 439).
?DOC(
" Strict top-level decoder for `Nil`: the wire form is zero bytes, and\n"
" **any** trailing data is a protocol violation (either a bug in the\n"
" sender or smuggled payload). Earlier drafts accepted the binary\n"
" regardless, defeating the strict-top-level contract that\n"
" `to_decoder` enforces for every other primitive.\n"
).
-spec nil_decoder() -> fun((bitstring()) -> {ok, nil} | {error, decode_error()}).
nil_decoder() ->
fun(Data) -> case Data of
<<>> ->
{ok, nil};
_ ->
{error,
{invalid_binary,
<<<<"trailing bytes ("/utf8,
(erlang:integer_to_binary(
erlang:byte_size(Data)
))/binary>>/binary,
") for nil top-level decode"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 470).
?DOC(
" Encode a `Subject(BitArray)` via `term_to_binary`. The PID\n"
" inside carries node info, so it routes back cross-node.\n"
"\n"
" Mirrors the 32-bit-prefix validation in `string_encoder` /\n"
" `bitarray_encoder` / `list_encoder`. A Subject larger than 4 GiB\n"
" is absurd in practice (it would imply a 4 GiB tag), but the bound\n"
" is enforced for symmetry with the rest of the codec surface.\n"
).
-spec subject_encoder() -> fun((gleam@erlang@process:subject(bitstring())) -> {ok,
bitstring()} |
{error, encode_error()}).
subject_encoder() ->
fun(Sub) ->
Bytes = distribute_ffi_utils:encode_subject(Sub),
Len = erlang:byte_size(Bytes),
case Len > 4294967295 of
true ->
{error,
{value_too_large,
<<<<"subject encoded form of "/utf8,
(erlang:integer_to_binary(Len))/binary>>/binary,
" bytes exceeds 32-bit length prefix"/utf8>>}};
false ->
{ok, <<Len:32, Bytes/bitstring>>}
end
end.
-file("src/distribute/codec.gleam", 486).
-spec subject_sized_decoder() -> fun((bitstring()) -> {ok,
{gleam@erlang@process:subject(bitstring()), bitstring()}} |
{error, decode_error()}).
subject_sized_decoder() ->
fun(Data) -> case Data of
<<Len:32, Rest/binary>> ->
Rest_size = erlang:byte_size(Rest),
case Rest_size >= Len of
false ->
{error, {insufficient_data, <<"subject"/utf8>>}};
true ->
case gleam_stdlib:bit_array_slice(Rest, 0, Len) of
{ok, Subject_bytes} ->
case distribute_ffi_utils:decode_subject_safe(
Subject_bytes
) of
{ok, Sub} ->
Remaining = case gleam_stdlib:bit_array_slice(
Rest,
Len,
Rest_size - Len
) of
{ok, R} ->
R;
{error, _} ->
<<>>
end,
{ok, {Sub, Remaining}};
{error, nil} ->
{error,
{decode_failed,
<<"invalid subject binary"/utf8>>}}
end;
{error, _} ->
{error,
{insufficient_data,
<<"subject slice"/utf8>>}}
end
end;
_ ->
{error, {invalid_binary, <<"subject length prefix"/utf8>>}}
end end.
-file("src/distribute/codec.gleam", 518).
-spec subject_decoder() -> fun((bitstring()) -> {ok,
gleam@erlang@process:subject(bitstring())} |
{error, decode_error()}).
subject_decoder() ->
to_decoder(subject_sized_decoder()).
-file("src/distribute/codec.gleam", 526).
-spec string() -> codec(binary()).
string() ->
{codec, string_encoder(), string_decoder(), string_sized_decoder()}.
-file("src/distribute/codec.gleam", 542).
-spec float() -> codec(float()).
float() ->
{codec, float_encoder(), float_decoder(), float_sized_decoder()}.
-file("src/distribute/codec.gleam", 550).
-spec bool() -> codec(boolean()).
bool() ->
{codec, bool_encoder(), bool_decoder(), bool_sized_decoder()}.
-file("src/distribute/codec.gleam", 558).
-spec bitarray() -> codec(bitstring()).
bitarray() ->
{codec, bitarray_encoder(), bitarray_decoder(), bitarray_sized_decoder()}.
-file("src/distribute/codec.gleam", 566).
-spec nil() -> codec(nil).
nil() ->
{codec, nil_encoder(), nil_decoder(), nil_sized_decoder()}.
-file("src/distribute/codec.gleam", 583).
?DOC(" Subject codec, for the request/response pattern.\n").
-spec subject() -> codec(gleam@erlang@process:subject(bitstring())).
subject() ->
{codec, subject_encoder(), subject_decoder(), subject_sized_decoder()}.
-file("src/distribute/codec.gleam", 592).
?DOC(" Transform a codec. wrap runs after decoding, unwrap before encoding.\n").
-spec map(codec(FMJ), fun((FMJ) -> FML), fun((FML) -> FMJ)) -> codec(FML).
map(C, Wrap, Unwrap) ->
{codec,
fun(Value) -> (erlang:element(2, C))(Unwrap(Value)) end,
fun(Data) -> case (erlang:element(3, C))(Data) of
{ok, Val} ->
{ok, Wrap(Val)};
{error, E} ->
{error, E}
end end,
fun(Data@1) -> case (erlang:element(4, C))(Data@1) of
{ok, {Val@1, Rest}} ->
{ok, {Wrap(Val@1), Rest}};
{error, E@1} ->
{error, E@1}
end end}.
-file("src/distribute/codec.gleam", 614).
-spec decode_error_to_string(decode_error()) -> binary().
decode_error_to_string(Error) ->
case Error of
{invalid_binary, Msg} ->
<<"Invalid binary: "/utf8, Msg/binary>>;
{type_mismatch, Msg@1} ->
<<"Type mismatch: "/utf8, Msg@1/binary>>;
{decode_failed, Msg@2} ->
<<"Decode failed: "/utf8, Msg@2/binary>>;
{insufficient_data, Msg@3} ->
<<"Insufficient data: "/utf8, Msg@3/binary>>;
decode_timeout ->
<<"Decode timeout"/utf8>>;
{tag_mismatch, Expected, Got} ->
<<<<<<<<"Tag mismatch: expected '"/utf8, Expected/binary>>/binary,
"', got '"/utf8>>/binary,
Got/binary>>/binary,
"'"/utf8>>;
{version_mismatch, Expected@1, Got@1} ->
<<<<<<"Version mismatch: expected "/utf8,
(erlang:integer_to_binary(Expected@1))/binary>>/binary,
", got "/utf8>>/binary,
(erlang:integer_to_binary(Got@1))/binary>>;
{payload_too_large, Size} ->
<<<<"Payload too large: "/utf8,
(erlang:integer_to_binary(Size))/binary>>/binary,
" bytes"/utf8>>;
{list_too_long, Count, Cap} ->
<<<<<<"List too long: declared "/utf8,
(erlang:integer_to_binary(Count))/binary>>/binary,
" elements, cap is "/utf8>>/binary,
(erlang:integer_to_binary(Cap))/binary>>
end.
-file("src/distribute/codec.gleam", 638).
-spec encode_error_to_string(encode_error()) -> binary().
encode_error_to_string(Error) ->
case Error of
{invalid_value, Msg} ->
<<"Invalid value: "/utf8, Msg/binary>>;
{encode_failed, Msg@1} ->
<<"Encode failed: "/utf8, Msg@1/binary>>;
{value_too_large, Msg@2} ->
<<"Value too large: "/utf8, Msg@2/binary>>
end.