Current section
Files
Jump to
Current section
Files
src/glats.erl
-module(glats).
-compile([no_auto_import, nowarn_unused_vars]).
-export([publish_message/2, publish/3, request/3, subscribe/3, unsubscribe/2, queue_subscribe/4, connect/1]).
-export_type([server_error/0, connection_message/0, subscription_actor_message/0, connection_state/0, subscription_actor_state/0]).
-type server_error() :: timeout | no_responders | unknown.
-opaque connection_message() :: {subscribe,
gleam@erlang@process:subject({ok, integer()} | {error, binary()}),
gleam@erlang@process:subject(glats@message:message()),
binary(),
gleam@option:option(binary())} |
{unsubscribe,
gleam@erlang@process:subject({ok, nil} | {error, binary()}),
integer()} |
{publish,
gleam@erlang@process:subject({ok, nil} | {error, binary()}),
glats@message:message()} |
{request,
gleam@erlang@process:subject({ok, glats@message:message()} |
{error, server_error()}),
binary(),
binary()}.
-type subscription_actor_message() :: {get_sid,
gleam@erlang@process:subject(integer())} |
{received_message, glats@message:message()} |
{decode_error, gleam@dynamic:dynamic()} |
subscriber_exited.
-type connection_state() :: {connection_state,
gleam@erlang@process:pid_(),
gleam@erlang@process:subject(connection_message())}.
-type subscription_actor_state() :: {subscription_actor_state,
gleam@erlang@process:subject(connection_message()),
integer(),
gleam@erlang@process:subject(glats@message:message())}.
-spec publish_message(
gleam@erlang@process:subject(connection_message()),
glats@message:message()
) -> {ok, nil} | {error, binary()}.
publish_message(Conn, Message) ->
gleam@erlang@process:call(
Conn,
fun(_capture) -> {publish, _capture, Message} end,
5000
).
-spec publish(
gleam@erlang@process:subject(connection_message()),
binary(),
binary()
) -> {ok, nil} | {error, binary()}.
publish(Conn, Subject, Message) ->
publish_message(Conn, {message, Subject, gleam@map:new(), none, Message}).
-spec request(
gleam@erlang@process:subject(connection_message()),
binary(),
binary()
) -> {ok, glats@message:message()} | {error, server_error()}.
request(Conn, Subject, Message) ->
gleam@erlang@process:call(
Conn,
fun(_capture) -> {request, _capture, Subject, Message} end,
5000
).
-spec subscribe(
gleam@erlang@process:subject(connection_message()),
gleam@erlang@process:subject(glats@message:message()),
binary()
) -> {ok, integer()} | {error, binary()}.
subscribe(Conn, Subscriber, Subject) ->
gleam@erlang@process:call(
Conn,
fun(_capture) -> {subscribe, _capture, Subscriber, Subject, none} end,
5000
).
-spec unsubscribe(gleam@erlang@process:subject(connection_message()), integer()) -> {ok,
nil} |
{error, binary()}.
unsubscribe(Conn, Sid) ->
gleam@erlang@process:call(
Conn,
fun(_capture) -> {unsubscribe, _capture, Sid} end,
5000
).
-spec subscription_loop(
subscription_actor_message(),
subscription_actor_state()
) -> gleam@otp@actor:next(subscription_actor_state()).
subscription_loop(Message, State) ->
case Message of
{get_sid, From} ->
gleam@otp@actor:send(From, erlang:element(3, State)),
{continue, State};
{received_message, Msg} ->
gleam@otp@actor:send(erlang:element(4, State), Msg),
{continue, State};
{decode_error, Data} ->
gleam@io:debug(Data),
{continue, State};
subscriber_exited ->
gleam@io:println(<<"subscriber exited"/utf8>>),
unsubscribe(erlang:element(2, State), erlang:element(3, State)),
{stop, normal}
end.
-spec queue_subscribe(
gleam@erlang@process:subject(connection_message()),
gleam@erlang@process:subject(glats@message:message()),
binary(),
binary()
) -> {ok, integer()} | {error, binary()}.
queue_subscribe(Conn, Subscriber, Subject, Group) ->
gleam@erlang@process:call(
Conn,
fun(_capture) ->
{subscribe, _capture, Subscriber, Subject, {some, Group}}
end,
5000
).
-spec add_opt_to_list(
list({binary(), gleam@dynamic:dynamic()}),
binary(),
any()
) -> list({binary(), gleam@dynamic:dynamic()}).
add_opt_to_list(Old, Key, Value) ->
_pipe = Old,
gleam@list:append(_pipe, [{Key, gleam@dynamic:from(Value)}]).
-spec take_host(
list({binary(), gleam@dynamic:dynamic()}),
glats@settings:settings()
) -> list({binary(), gleam@dynamic:dynamic()}).
take_host(Old, Settings) ->
case erlang:element(2, Settings) of
{some, Host} ->
_pipe = Old,
add_opt_to_list(_pipe, <<"host"/utf8>>, Host);
none ->
Old
end.
-spec take_port(
list({binary(), gleam@dynamic:dynamic()}),
glats@settings:settings()
) -> list({binary(), gleam@dynamic:dynamic()}).
take_port(Old, Settings) ->
case erlang:element(3, Settings) of
{some, Port} ->
_pipe = Old,
add_opt_to_list(_pipe, <<"port"/utf8>>, Port);
none ->
Old
end.
-spec take_tls(
list({binary(), gleam@dynamic:dynamic()}),
glats@settings:settings()
) -> list({binary(), gleam@dynamic:dynamic()}).
take_tls(Old, Settings) ->
case erlang:element(4, Settings) of
{some, Tls} ->
_pipe = Old,
add_opt_to_list(_pipe, <<"tls"/utf8>>, Tls);
none ->
Old
end.
-spec take_ssl_opts(
list({binary(), gleam@dynamic:dynamic()}),
glats@settings:settings()
) -> list({binary(), gleam@dynamic:dynamic()}).
take_ssl_opts(Old, Settings) ->
case erlang:element(5, Settings) of
{some, Ssl_opts} ->
_pipe = Old,
add_opt_to_list(_pipe, <<"ssl_opts"/utf8>>, Ssl_opts);
none ->
Old
end.
-spec build_settings(glats@settings:settings()) -> gleam@map:map_(binary(), gleam@dynamic:dynamic()).
build_settings(Settings) ->
_pipe = [],
_pipe@1 = take_host(_pipe, Settings),
_pipe@2 = take_port(_pipe@1, Settings),
_pipe@3 = take_tls(_pipe@2, Settings),
_pipe@4 = take_ssl_opts(_pipe@3, Settings),
gleam@map:from_list(_pipe@4).
-spec handle_publish(
gleam@erlang@process:subject({ok, nil} | {error, binary()}),
glats@message:message(),
connection_state()
) -> gleam@otp@actor:next(connection_state()).
handle_publish(From, Message, State) ->
case begin
_pipe = 'Elixir.Gnat':pub(
erlang:element(2, State),
erlang:element(2, Message),
erlang:element(5, Message),
[]
),
erlang:atom_to_binary(_pipe)
end of
<<"ok"/utf8>> ->
gleam@erlang@process:send(From, {ok, nil});
_ ->
gleam@erlang@process:send(
From,
{error, <<"unknown publish error"/utf8>>}
)
end,
{continue, State}.
-spec handle_request(
gleam@erlang@process:subject({ok, glats@message:message()} |
{error, server_error()}),
binary(),
binary(),
connection_state()
) -> gleam@otp@actor:next(connection_state()).
handle_request(From, Subject, Message, State) ->
case 'Elixir.Gnat':request(erlang:element(2, State), Subject, Message, []) of
{ok, Msg} ->
_pipe = glats@internal@decoder:decode_msg(Msg),
_pipe@1 = gleam@result:map_error(_pipe, fun(_) -> unknown end),
gleam@erlang@process:send(From, _pipe@1);
{error, Err} ->
_pipe@2 = case erlang:atom_to_binary(Err) of
<<"timeout"/utf8>> ->
{error, timeout};
<<"no_responders"/utf8>> ->
{error, no_responders};
_ ->
{error, unknown}
end,
gleam@erlang@process:send(From, _pipe@2)
end,
{continue, State}.
-spec start_subscription_actor(
gleam@erlang@process:subject(glats@message:message()),
binary(),
gleam@option:option(binary()),
connection_state()
) -> {ok, gleam@erlang@process:subject(subscription_actor_message())} |
{error, gleam@otp@actor:start_error()}.
start_subscription_actor(Subscriber, Subject, Queue_group, State) ->
gleam@otp@actor:start_spec(
{spec,
fun() ->
Monitor = gleam@erlang@process:monitor_process(
begin
_pipe = Subscriber,
gleam@erlang@process:subject_owner(_pipe)
end
),
Selector = begin
_pipe@1 = gleam_erlang_ffi:new_selector(),
_pipe@2 = gleam@erlang@process:selecting_process_down(
_pipe@1,
Monitor,
fun(_) -> subscriber_exited end
),
gleam@erlang@process:selecting_record2(
_pipe@2,
erlang:binary_to_atom(<<"msg"/utf8>>),
fun(Data) ->
_pipe@3 = Data,
_pipe@4 = glats@internal@decoder:decode_msg(_pipe@3),
_pipe@5 = gleam@result:map(
_pipe@4,
fun(Field@0) -> {received_message, Field@0} end
),
gleam@result:unwrap(_pipe@5, {decode_error, Data})
end
)
end,
Opts = case Queue_group of
{some, Group} ->
[{erlang:binary_to_atom(<<"queue_group"/utf8>>), Group}];
none ->
[]
end,
case 'Elixir.Gnat':sub(
erlang:element(2, State),
erlang:self(),
Subject,
Opts
) of
{ok, Sid} ->
{ready,
{subscription_actor_state,
erlang:element(3, State),
Sid,
Subscriber},
Selector};
{error, Err} ->
{failed, Err}
end
end,
5000,
fun subscription_loop/2}
).
-spec handle_subscribe(
gleam@erlang@process:subject({ok, integer()} | {error, binary()}),
gleam@erlang@process:subject(glats@message:message()),
binary(),
gleam@option:option(binary()),
connection_state()
) -> gleam@otp@actor:next(connection_state()).
handle_subscribe(From, Subscriber, Subject, Queue_group, State) ->
case start_subscription_actor(Subscriber, Subject, Queue_group, State) of
{ok, Actor} ->
case gleam@erlang@process:try_call(
Actor,
fun(Field@0) -> {get_sid, Field@0} end,
1000
) of
{ok, Sid} ->
gleam@erlang@process:send(From, {ok, Sid});
{error, _} ->
gleam@erlang@process:send(
From,
{error, <<"subscribe failed"/utf8>>}
)
end;
{error, _} ->
gleam@erlang@process:send(
From,
{error, <<"subscribe failed"/utf8>>}
)
end,
{continue, State}.
-spec handle_unsubscribe(
gleam@erlang@process:subject({ok, nil} | {error, binary()}),
integer(),
connection_state()
) -> gleam@otp@actor:next(connection_state()).
handle_unsubscribe(From, Sid, State) ->
case begin
_pipe = 'Elixir.Gnat':unsub(erlang:element(2, State), Sid, []),
erlang:atom_to_binary(_pipe)
end of
<<"ok"/utf8>> ->
gleam@erlang@process:send(From, {ok, nil});
_ ->
gleam@erlang@process:send(
From,
{error, <<"unknown unsubscribe error"/utf8>>}
)
end,
{continue, State}.
-spec handle_command(connection_message(), connection_state()) -> gleam@otp@actor:next(connection_state()).
handle_command(Message, State) ->
case Message of
{publish, From, Msg} ->
handle_publish(From, Msg, State);
{request, From@1, Subject, Msg@1} ->
handle_request(From@1, Subject, Msg@1, State);
{subscribe, From@2, Subscriber, Subject@1, Queue_group} ->
handle_subscribe(From@2, Subscriber, Subject@1, Queue_group, State);
{unsubscribe, From@3, Sid} ->
handle_unsubscribe(From@3, Sid, State);
_ ->
{continue, State}
end.
-spec connect(glats@settings:settings()) -> {ok,
gleam@erlang@process:subject(connection_message())} |
{error, gleam@otp@actor:start_error()}.
connect(Settings) ->
gleam@otp@actor:start_spec(
{spec,
fun() ->
Subject = gleam@erlang@process:new_subject(),
Selector = gleam_erlang_ffi:new_selector(),
case 'Elixir.Gnat':start_link(
begin
_pipe = Settings,
build_settings(_pipe)
end
) of
{ok, Pid} ->
{ready, {connection_state, Pid, Subject}, Selector};
{error, _} ->
{failed, <<"starting connection failed"/utf8>>}
end
end,
5000,
fun handle_command/2}
).