-module(glats). -compile([no_auto_import, nowarn_unused_vars]). -export([publish_message/2, publish/3, request/4, subscribe/3, unsubscribe/2, queue_subscribe/4, server_info/1, active_subscriptions/1, connect/1]). -export_type([connection_error/0, connection_message/0, subscription_message/0, subscription_actor_message/0, connection_state/0, subscription_actor_state/0]). -type connection_error() :: timeout | no_responders | unexpected. -opaque connection_message() :: {subscribe, gleam@erlang@process:subject({ok, integer()} | {error, binary()}), gleam@erlang@process:subject(subscription_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, connection_error()}), binary(), binary(), integer()} | {get_server_info, gleam@erlang@process:subject({ok, glats@protocol:server_info()} | {error, connection_error()})} | {get_active_subscriptions, gleam@erlang@process:subject({ok, integer()} | {error, connection_error()})} | {exited, gleam@erlang@process:exit_message()}. -type subscription_message() :: {received_message, gleam@erlang@process:subject(connection_message()), integer(), glats@message:message()}. -type subscription_actor_message() :: {get_sid, gleam@erlang@process:subject(integer())} | {incoming_message, integer(), 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(subscription_message())}. -spec map_gnat_message(gleam@dynamic:dynamic()) -> subscription_actor_message(). map_gnat_message(Data) -> Sid_ = begin _pipe = Data, (gleam@dynamic:field( erlang:binary_to_atom(<<"sid"/utf8>>), fun gleam@dynamic:int/1 ))(_pipe) end, case Sid_ of {ok, Sid} -> _pipe@1 = Data, _pipe@2 = glats@internal@decoder:decode_msg(_pipe@1), _pipe@3 = gleam@result:map( _pipe@2, fun(_capture) -> {incoming_message, Sid, _capture} end ), gleam@result:unwrap(_pipe@3, {decode_error, Data}); {error, _} -> {decode_error, Data} end. -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(), integer() ) -> {ok, glats@message:message()} | {error, connection_error()}. request(Conn, Subject, Message, Timeout) -> gleam@erlang@process:call( Conn, fun(_capture) -> {request, _capture, Subject, Message, Timeout} end, Timeout + 1000 ). -spec subscribe( gleam@erlang@process:subject(connection_message()), gleam@erlang@process:subject(subscription_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}; {incoming_message, Sid, Msg} -> gleam@otp@actor:send( erlang:element(4, State), {received_message, erlang:element(2, State), Sid, 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(subscription_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 server_info(gleam@erlang@process:subject(connection_message())) -> {ok, glats@protocol:server_info()} | {error, connection_error()}. server_info(Conn) -> gleam@erlang@process:call( Conn, fun(Field@0) -> {get_server_info, Field@0} end, 5000 ). -spec active_subscriptions(gleam@erlang@process:subject(connection_message())) -> {ok, integer()} | {error, connection_error()}. active_subscriptions(Conn) -> gleam@erlang@process:call( Conn, fun(Field@0) -> {get_active_subscriptions, Field@0} 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, connection_error()}), binary(), binary(), any(), connection_state() ) -> gleam@otp@actor:next(connection_state()). handle_request(From, Subject, Message, Timeout, State) -> Opts = [{erlang:binary_to_atom(<<"receive_timeout"/utf8>>), gleam@dynamic:from(Timeout)}], case 'Elixir.Gnat':request(erlang:element(2, State), Subject, Message, Opts) of {ok, Msg} -> _pipe = glats@internal@decoder:decode_msg(Msg), _pipe@1 = gleam@result:map_error(_pipe, fun(_) -> unexpected 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, unexpected} end, gleam@erlang@process:send(From, _pipe@2) end, {continue, State}. -spec start_subscription_actor( gleam@erlang@process:subject(subscription_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 map_gnat_message/1 ) 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(subscription_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_server_info( gleam@erlang@process:subject({ok, glats@protocol:server_info()} | {error, connection_error()}), connection_state() ) -> gleam@otp@actor:next(connection_state()). handle_server_info(From, State) -> _pipe = 'Elixir.Gnat':server_info(erlang:element(2, State)), _pipe@1 = glats@internal@decoder:decode_server_info(_pipe), _pipe@2 = gleam@result:map_error(_pipe@1, fun(_) -> unexpected end), gleam@erlang@process:send(From, _pipe@2), {continue, State}. -spec handle_active_subscriptions( gleam@erlang@process:subject({ok, integer()} | {error, connection_error()}), connection_state() ) -> gleam@otp@actor:next(connection_state()). handle_active_subscriptions(From, State) -> _pipe = 'Elixir.Gnat':active_subscriptions(erlang:element(2, State)), _pipe@1 = gleam@result:map_error(_pipe, fun(_) -> unexpected end), gleam@erlang@process:send(From, _pipe@1), {continue, State}. -spec handle_command(connection_message(), connection_state()) -> gleam@otp@actor:next(connection_state()). handle_command(Message, State) -> case Message of {exited, Em} -> {stop, erlang:element(3, Em)}; {publish, From, Msg} -> handle_publish(From, Msg, State); {request, From@1, Subject, Msg@1, Timeout} -> handle_request(From@1, Subject, Msg@1, Timeout, 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); {get_server_info, From@4} -> handle_server_info(From@4, State); {get_active_subscriptions, From@5} -> handle_active_subscriptions(From@5, 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() -> gleam_erlang_ffi:trap_exits(true), Subject = gleam@erlang@process:new_subject(), Selector = begin _pipe = gleam_erlang_ffi:new_selector(), _pipe@1 = gleam@erlang@process:selecting_trapped_exits( _pipe, fun(Field@0) -> {exited, Field@0} end ), gleam@erlang@process:selecting( _pipe@1, Subject, fun(Msg) -> Msg end ) end, case 'Elixir.Gnat':start_link( begin _pipe@2 = Settings, build_settings(_pipe@2) end ) of {ok, Pid} -> {ready, {connection_state, Pid, Subject}, Selector}; {error, _} -> {failed, <<"starting connection failed"/utf8>>} end end, 5000, fun handle_command/2} ).