-module(dove). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([connect/3, get_response/2, shutdown/1, request/3, 'receive'/2]). -export_type([request_option/1, request_body/0, response_body/1, connection/1]). -type request_option(HNA) :: {json_decoder, fun((gleam@dynamic:dynamic_()) -> {ok, HNA} | {error, list(gleam@dynamic:decode_error())})}. -type request_body() :: {json, binary()} | {plain_text, binary()}. -type response_body(HNB) :: empty | {raw, binary()} | {json_decoded, HNB} | {invalid_or_unexpected_json, binary(), gleam@json:decode_error()}. -opaque connection(HNC) :: {connection, binary(), mug:socket(), binary(), list({gleam@erlang:reference_(), gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HNC} | {error, list(gleam@dynamic:decode_error())}))}), list({gleam@erlang:reference_(), {ok, gleam@http@response:response(response_body(HNC))} | {error, dove@error:error()}})}. -spec connect(binary(), integer(), integer()) -> {ok, connection(any())} | {error, mug:error()}. connect(Host, Port, Timeout) -> gleam@result:then( dove@tcp:connect(Host, Port, Timeout), fun(Socket) -> {ok, {connection, <<<>/binary, (gleam@int:to_string(Port))/binary>>, Socket, <<""/utf8>>, [], []}} end ). -spec get_response(connection(HNU), gleam@erlang:reference_()) -> {ok, {connection(HNU), {ok, gleam@http@response:response(response_body(HNU))} | {error, dove@error:error()}}} | {error, any()}. get_response(Conn, Ref) -> case gleam@list:key_pop(erlang:element(6, Conn), Ref) of {ok, {Response, Other_responses}} -> {ok, {erlang:setelement(6, Conn, Other_responses), Response}}; {error, nil} -> {ok, {Conn, {error, {tcp_error, timeout}}}} end. -spec shutdown(connection(HNY)) -> {ok, list({gleam@erlang:reference_(), {ok, gleam@http@response:response(response_body(HNY))} | {error, dove@error:error()}})} | {error, dove@error:error()}. shutdown(Conn) -> _pipe = mug_ffi:shutdown(erlang:element(3, Conn)), _pipe@1 = gleam@result:map(_pipe, fun(_) -> erlang:element(6, Conn) end), gleam@result:map_error( _pipe@1, fun(Tcp_error) -> {tcp_error, Tcp_error} end ). -spec get_decoder(list(request_option(HOB))) -> gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HOB} | {error, list(gleam@dynamic:decode_error())})). get_decoder(Options) -> case gleam@list:find_map(Options, fun(Opt) -> case Opt of {json_decoder, Decoder} -> {ok, Decoder}; _ -> {error, nil} end end) of {ok, Decoder@1} -> {some, Decoder@1}; {error, nil} -> none end. -spec request( connection(HNE), gleam@http@request:request(gleam@option:option(request_body())), list(request_option(HNE)) ) -> {ok, {connection(HNE), gleam@erlang:reference_()}} | {error, dove@error:error()}. request(Conn, Request, Options) -> gleam@bool:guard( erlang:element(5, Request) =:= https, {error, https_not_supported_yet}, fun() -> Method@2 = case erlang:element(2, Request) of {other, Method} -> Method; _ -> _assert_subject = gleam@list:key_find( [{get, <<"GET"/utf8>>}, {put, <<"PUT"/utf8>>}, {post, <<"POST"/utf8>>}, {head, <<"HEAD"/utf8>>}, {patch, <<"PATCH"/utf8>>}, {trace, <<"TRACE"/utf8>>}, {delete, <<"DELETE"/utf8>>}, {connect, <<"CONNECT"/utf8>>}, {options, <<"OPTIONS"/utf8>>}], erlang:element(2, Request) ), {ok, Method@1} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"dove"/utf8>>, function => <<"request"/utf8>>, line => 77}) end, Method@1 end, Decoder = get_decoder(Options), {Body@2, Headers} = case erlang:element(4, Request) of {some, {json, Body}} -> {{some, Body}, gleam@list:append( erlang:element(3, Request), [{<<"content-type"/utf8>>, <<"application/json"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe = Body, _pipe@1 = gleam@string:length(_pipe), gleam@int:to_string(_pipe@1) end}] )}; {some, {plain_text, Body@1}} -> {{some, Body@1}, gleam@list:append( erlang:element(3, Request), [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe@2 = Body@1, _pipe@3 = gleam@string:length(_pipe@2), gleam@int:to_string(_pipe@3) end}] )}; none -> {none, erlang:element(3, Request)} end, gleam@result:then( dove@request:encode( erlang:element(2, Conn), Method@2, erlang:element(8, Request), erlang:element(9, Request), Headers, Body@2 ), fun(Request@1) -> gleam@result:then( begin _pipe@4 = dove@tcp:send( erlang:element(3, Conn), Request@1 ), gleam@result:replace_error( _pipe@4, unable_to_send_request ) end, fun(_) -> Ref = erlang:make_ref(), {ok, {{connection, erlang:element(2, Conn), erlang:element(3, Conn), erlang:element(4, Conn), gleam@list:append( erlang:element(5, Conn), [{Ref, Decoder}] ), erlang:element(6, Conn)}, Ref}} end ) end ) end ). -spec receive_packet( mug:socket(), gleam@erlang@process:selector({ok, bitstring()} | {error, mug:error()}), binary(), integer(), integer() ) -> {ok, {{integer(), list({binary(), binary()}), gleam@option:option(binary())}, binary()}} | {error, dove@error:error()}. receive_packet(Socket, Selector, Storage, Start_time, Timeout) -> case dove@response:decode(Storage) of {ok, Value} -> {ok, Value}; {error, more_needed} -> case (erlang:monotonic_time() - Start_time) >= (Timeout * 1000000) of true -> {error, {tcp_error, timeout}}; false -> case dove@tcp:'receive'(Socket, Selector, Timeout) of {error, Tcp_error} -> {error, {tcp_error, Tcp_error}}; {ok, Packet} -> case gleam@bit_array:to_string(Packet) of {ok, Packet@1} -> receive_packet( Socket, Selector, gleam@string:append(Storage, Packet@1), Start_time, Timeout ); {error, nil} -> {error, is_not_string} end end end; {error, Error} -> {error, Error} end. -spec receive_internal( connection(HNP), gleam@erlang@process:selector({ok, bitstring()} | {error, mug:error()}), integer() ) -> connection(HNP). receive_internal(Conn, Selector, Timeout) -> case erlang:element(5, Conn) of [{Ref, Decoder} | Rest] -> Data = case gleam@string:length(erlang:element(4, Conn)) of 0 -> receive_packet( erlang:element(3, Conn), Selector, <<""/utf8>>, erlang:monotonic_time(), Timeout ); _ -> dove@response:decode(erlang:element(4, Conn)) end, Conn@1 = {connection, erlang:element(2, Conn), erlang:element(3, Conn), case Data of {ok, {_, Rest@1}} -> gleam@string:append(erlang:element(4, Conn), Rest@1); _ -> erlang:element(4, Conn) end, Rest, gleam@list:append( erlang:element(6, Conn), [{Ref, begin _pipe = Data, _pipe@1 = gleam@result:map( _pipe, fun(Response) -> case erlang:element(1, Response) of {Status, Headers, {some, Body}} -> case Decoder of {some, Decoder@1} -> case gleam@json:decode( Body, Decoder@1 ) of {ok, Value} -> {ok, {response, Status, Headers, {json_decoded, Value}}}; {error, Decode_error} -> {ok, {response, Status, Headers, {invalid_or_unexpected_json, Body, Decode_error}}} end; none -> {ok, {response, Status, Headers, {raw, Body}}} end; {Status@1, Headers@1, none} -> {ok, {response, Status@1, Headers@1, empty}} end end ), gleam@result:flatten(_pipe@1) end}] )}, case Rest of [] -> Conn@1; _ -> receive_internal(Conn@1, Selector, Timeout) end end. -spec 'receive'(connection(HNL), integer()) -> connection(HNL). 'receive'(Conn, Timeout) -> Selector = dove@tcp:new_selector(), receive_internal(Conn, Selector, Timeout).