-module(lightspeed@protocol).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/protocol.gleam").
-export([hello/0, ref/1, is_current_hello/1, encode/1, decode/1, decode_error_to_string/1]).
-export_type([frame/0, decode_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(" Versioned protocol frame model.\n").
-type frame() :: {hello, binary(), integer()} |
{event, binary(), binary(), binary()} |
{diff, binary(), binary()} |
{ack, binary()} |
{failure, binary(), binary()}.
-type decode_error() :: empty_frame |
{unknown_frame_tag, binary()} |
{bad_field_count, binary(), integer(), integer()} |
{invalid_version, binary()} |
{unsupported_version, integer()} |
{unsupported_protocol, binary()} |
invalid_escape_sequence.
-file("src/lightspeed/protocol.gleam", 32).
?DOC(" Construct a protocol hello frame.\n").
-spec hello() -> frame().
hello() ->
{hello, <<"lightspeed"/utf8>>, 1}.
-file("src/lightspeed/protocol.gleam", 37).
?DOC(" Return the frame reference when one exists.\n").
-spec ref(frame()) -> binary().
ref(Frame) ->
case Frame of
{hello, _, _} ->
<<""/utf8>>;
{event, Ref, _, _} ->
Ref;
{diff, Ref@1, _} ->
Ref@1;
{ack, Ref@2} ->
Ref@2;
{failure, Ref@3, _} ->
Ref@3
end.
-file("src/lightspeed/protocol.gleam", 48).
?DOC(" True when the frame is part of the current protocol.\n").
-spec is_current_hello(frame()) -> boolean().
is_current_hello(Frame) ->
case Frame of
{hello, Protocol, Version} ->
(Protocol =:= <<"lightspeed"/utf8>>) andalso (Version =:= 1);
_ ->
false
end.
-file("src/lightspeed/protocol.gleam", 189).
-spec escape_chars(list(binary()), binary()) -> binary().
escape_chars(Chars, Acc) ->
case Chars of
[] ->
Acc;
[Char | Rest] ->
case Char of
<<"\\"/utf8>> ->
escape_chars(Rest, <<Acc/binary, "\\\\"/utf8>>);
<<"|"/utf8>> ->
escape_chars(Rest, <<Acc/binary, "\\|"/utf8>>);
_ ->
escape_chars(Rest, <<Acc/binary, Char/binary>>)
end
end.
-file("src/lightspeed/protocol.gleam", 185).
-spec escape_field(binary()) -> binary().
escape_field(Value) ->
escape_chars(gleam@string:to_graphemes(Value), <<""/utf8>>).
-file("src/lightspeed/protocol.gleam", 178).
-spec join_fields_loop(list(binary()), binary()) -> binary().
join_fields_loop(Fields, Acc) ->
case Fields of
[] ->
Acc;
[Field | Rest] ->
join_fields_loop(
Rest,
<<<<Acc/binary, "|"/utf8>>/binary,
(escape_field(Field))/binary>>
)
end.
-file("src/lightspeed/protocol.gleam", 171).
-spec join_fields(list(binary())) -> binary().
join_fields(Fields) ->
case Fields of
[] ->
<<""/utf8>>;
[Field | Rest] ->
join_fields_loop(Rest, escape_field(Field))
end.
-file("src/lightspeed/protocol.gleam", 57).
?DOC(" Encode a frame into a transport-safe textual format.\n").
-spec encode(frame()) -> binary().
encode(Frame) ->
case Frame of
{hello, Protocol, Version} ->
join_fields(
[<<"hello"/utf8>>, Protocol, erlang:integer_to_binary(Version)]
);
{event, Ref, Name, Payload} ->
join_fields([<<"event"/utf8>>, Ref, Name, Payload]);
{diff, Ref@1, Html} ->
join_fields([<<"diff"/utf8>>, Ref@1, Html]);
{ack, Ref@2} ->
join_fields([<<"ack"/utf8>>, Ref@2]);
{failure, Ref@3, Reason} ->
join_fields([<<"failure"/utf8>>, Ref@3, Reason])
end.
-file("src/lightspeed/protocol.gleam", 163).
-spec bad_field_count(binary(), integer(), list(binary())) -> {ok, frame()} |
{error, decode_error()}.
bad_field_count(Tag, Expected, Fields) ->
{error, {bad_field_count, Tag, Expected, erlang:length(Fields)}}.
-file("src/lightspeed/protocol.gleam", 156).
-spec decode_failure(list(binary())) -> {ok, frame()} | {error, decode_error()}.
decode_failure(Fields) ->
case Fields of
[<<"failure"/utf8>>, Ref, Reason] ->
{ok, {failure, Ref, Reason}};
_ ->
bad_field_count(<<"failure"/utf8>>, 3, Fields)
end.
-file("src/lightspeed/protocol.gleam", 149).
-spec decode_ack(list(binary())) -> {ok, frame()} | {error, decode_error()}.
decode_ack(Fields) ->
case Fields of
[<<"ack"/utf8>>, Ref] ->
{ok, {ack, Ref}};
_ ->
bad_field_count(<<"ack"/utf8>>, 2, Fields)
end.
-file("src/lightspeed/protocol.gleam", 142).
-spec decode_diff(list(binary())) -> {ok, frame()} | {error, decode_error()}.
decode_diff(Fields) ->
case Fields of
[<<"diff"/utf8>>, Ref, Html] ->
{ok, {diff, Ref, Html}};
_ ->
bad_field_count(<<"diff"/utf8>>, 3, Fields)
end.
-file("src/lightspeed/protocol.gleam", 134).
-spec decode_event(list(binary())) -> {ok, frame()} | {error, decode_error()}.
decode_event(Fields) ->
case Fields of
[<<"event"/utf8>>, Ref, Name, Payload] ->
{ok, {event, Ref, Name, Payload}};
_ ->
bad_field_count(<<"event"/utf8>>, 4, Fields)
end.
-file("src/lightspeed/protocol.gleam", 115).
-spec decode_hello(list(binary())) -> {ok, frame()} | {error, decode_error()}.
decode_hello(Fields) ->
case Fields of
[<<"hello"/utf8>>, Protocol, Version_text] ->
case gleam_stdlib:parse_int(Version_text) of
{error, _} ->
{error, {invalid_version, Version_text}};
{ok, Version} ->
case Protocol =:= <<"lightspeed"/utf8>> of
false ->
{error, {unsupported_protocol, Protocol}};
true ->
case Version =:= 1 of
true ->
{ok, {hello, Protocol, Version}};
false ->
{error, {unsupported_version, Version}}
end
end
end;
_ ->
bad_field_count(<<"hello"/utf8>>, 3, Fields)
end.
-file("src/lightspeed/protocol.gleam", 100).
-spec decode_fields(list(binary())) -> {ok, frame()} | {error, decode_error()}.
decode_fields(Fields) ->
case Fields of
[] ->
{error, empty_frame};
[Tag | _] ->
case Tag of
<<"hello"/utf8>> ->
decode_hello(Fields);
<<"event"/utf8>> ->
decode_event(Fields);
<<"diff"/utf8>> ->
decode_diff(Fields);
<<"ack"/utf8>> ->
decode_ack(Fields);
<<"failure"/utf8>> ->
decode_failure(Fields);
_ ->
{error, {unknown_frame_tag, Tag}}
end
end.
-file("src/lightspeed/protocol.gleam", 205).
-spec split_chars(list(binary()), binary(), list(binary()), boolean()) -> {ok,
list(binary())} |
{error, decode_error()}.
split_chars(Chars, Current, Fields_rev, Escaped) ->
case Chars of
[] ->
case Escaped of
true ->
{error, invalid_escape_sequence};
false ->
{ok, lists:reverse([Current | Fields_rev])}
end;
[Char | Rest] ->
case Escaped of
true ->
split_chars(
Rest,
<<Current/binary, Char/binary>>,
Fields_rev,
false
);
false ->
case Char of
<<"\\"/utf8>> ->
split_chars(Rest, Current, Fields_rev, true);
<<"|"/utf8>> ->
split_chars(
Rest,
<<""/utf8>>,
[Current | Fields_rev],
false
);
_ ->
split_chars(
Rest,
<<Current/binary, Char/binary>>,
Fields_rev,
false
)
end
end
end.
-file("src/lightspeed/protocol.gleam", 201).
-spec split_fields(binary()) -> {ok, list(binary())} | {error, decode_error()}.
split_fields(Payload) ->
split_chars(gleam@string:to_graphemes(Payload), <<""/utf8>>, [], false).
-file("src/lightspeed/protocol.gleam", 69).
?DOC(" Decode a textual frame payload.\n").
-spec decode(binary()) -> {ok, frame()} | {error, decode_error()}.
decode(Payload) ->
case Payload of
<<""/utf8>> ->
{error, empty_frame};
_ ->
case split_fields(Payload) of
{error, Error} ->
{error, Error};
{ok, Fields} ->
decode_fields(Fields)
end
end.
-file("src/lightspeed/protocol.gleam", 81).
?DOC(" Convert decode errors to stable strings for logs and adapter errors.\n").
-spec decode_error_to_string(decode_error()) -> binary().
decode_error_to_string(Error) ->
case Error of
empty_frame ->
<<"empty_frame"/utf8>>;
{unknown_frame_tag, Tag} ->
<<"unknown_frame_tag:"/utf8, Tag/binary>>;
{bad_field_count, Tag@1, Expected, Actual} ->
<<<<<<<<<<"bad_field_count:"/utf8, Tag@1/binary>>/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(Expected))/binary>>/binary,
":"/utf8>>/binary,
(erlang:integer_to_binary(Actual))/binary>>;
{invalid_version, Version} ->
<<"invalid_version:"/utf8, Version/binary>>;
{unsupported_version, Version@1} ->
<<"unsupported_version:"/utf8,
(erlang:integer_to_binary(Version@1))/binary>>;
{unsupported_protocol, Protocol} ->
<<"unsupported_protocol:"/utf8, Protocol/binary>>;
invalid_escape_sequence ->
<<"invalid_escape_sequence"/utf8>>
end.