-module(dove). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([connect/3, get_response/2, request/3, 'receive'/2, shutdown/1]). -export_type([request_option/1, connection/1]). -type request_option(HUD) :: {json_decoder, fun((gleam@dynamic:dynamic_()) -> {ok, HUD} | {error, list(gleam@dynamic:decode_error())})}. -opaque connection(HUE) :: {connection, binary(), mug:socket(), bitstring(), list({gleam@erlang:reference_(), gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HUE} | {error, list(gleam@dynamic:decode_error())}))}), list({gleam@erlang:reference_(), {ok, gleam@http@response:response(gleam@option:option(dove@response:body(HUE)))} | {error, dove@error:error()}}), integer()}. -spec connect(binary(), integer(), integer()) -> {ok, connection(any())} | {error, mug:error()}. connect(Host, Port, Timeout) -> gleam@result:then( dove@internal@tcp:connect(Host, Port, Timeout), fun(Socket) -> {ok, {connection, <<<>/binary, (gleam@int:to_string(Port))/binary>>, Socket, <<>>, [], [], Timeout}} end ). -spec get_response(connection(HUW), gleam@erlang:reference_()) -> {connection(HUW), {ok, gleam@http@response:response(gleam@option:option(dove@response:body(HUW)))} | {error, dove@error:error()}}. get_response(Conn, Ref) -> case gleam@list:key_pop(erlang:element(6, Conn), Ref) of {ok, {Response, Other_responses}} -> {erlang:setelement(6, Conn, Other_responses), Response}; {error, nil} -> {Conn, {error, {tcp_error, timeout}}} end. -spec get_decoder(list(request_option(HVD))) -> gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HVD} | {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(HUG), gleam@http@request:request(gleam@option:option(dove@request:body())), list(request_option(HUG)) ) -> {ok, {connection(HUG), 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 => 78}) end, Method@1 end, {Body@3, Headers} = case erlang:element(4, Request) of {some, {json, Body}} -> {begin _pipe = Body, _pipe@1 = gleam_stdlib:identity(_pipe), {some, _pipe@1} end, gleam@list:append( erlang:element(3, Request), [{<<"content-type"/utf8>>, <<"application/json; charset=utf-8"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe@2 = Body, _pipe@3 = gleam@string:length(_pipe@2), gleam@int:to_string(_pipe@3) end}] )}; {some, {plain_text, Body@1}} -> {begin _pipe@4 = Body@1, _pipe@5 = gleam_stdlib:identity(_pipe@4), {some, _pipe@5} end, gleam@list:append( erlang:element(3, Request), [{<<"content-type"/utf8>>, <<"text/plain; charset=utf-8"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe@6 = Body@1, _pipe@7 = gleam@string:length(_pipe@6), gleam@int:to_string(_pipe@7) end}] )}; {some, {octet_stream, Body@2}} -> {{some, Body@2}, gleam@list:append( erlang:element(3, Request), [{<<"content-type"/utf8>>, <<"application/octet-stream"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe@8 = Body@2, _pipe@9 = erlang:byte_size(_pipe@8), gleam@int:to_string(_pipe@9) end}] )}; none -> {none, erlang:element(3, Request)} end, Decoder = get_decoder(Options), gleam@result:then( dove@internal@request:encode( erlang:element(2, Conn), Method@2, erlang:element(8, Request), erlang:element(9, Request), Headers, Body@3 ), fun(Request@1) -> gleam@result:then( begin _pipe@10 = dove@internal@tcp:send( erlang:element(3, Conn), Request@1 ), gleam@result:replace_error( _pipe@10, 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), erlang:element(7, Conn)}, Ref}} end ) end ) end ). -spec receive_packet( mug:socket(), gleam@erlang@process:selector({ok, bitstring()} | {error, mug:error()}), bitstring(), integer(), integer() ) -> {ok, {{integer(), list({binary(), binary()}), gleam@option:option(bitstring())}, bitstring()}} | {error, dove@error:error()}. receive_packet(Socket, Selector, Storage, Start_time, Timeout) -> case dove@internal@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@internal@tcp:'receive'(Socket, Selector, Timeout) of {error, Tcp_error} -> {error, {tcp_error, Tcp_error}}; {ok, Packet} -> receive_packet( Socket, Selector, gleam@bit_array:append(Storage, Packet), Start_time, Timeout ) end end; {error, Error} -> {error, Error} end. -spec receive_internal( connection(HUR), gleam@erlang@process:selector({ok, bitstring()} | {error, mug:error()}), integer() ) -> connection(HUR). receive_internal(Conn, Selector, Timeout) -> case erlang:element(5, Conn) of [{Ref, Decoder} | Rest] -> Data = case erlang:byte_size(erlang:element(4, Conn)) of 0 -> receive_packet( erlang:element(3, Conn), Selector, <<>>, erlang:monotonic_time(), Timeout ); _ -> dove@internal@response:decode(erlang:element(4, Conn)) end, Conn@1 = {connection, erlang:element(2, Conn), erlang:element(3, Conn), case Data of {ok, {_, Rest@1}} -> Rest@1; _ -> <<>> 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_bits( Body, Decoder@1 ) of {ok, Value} -> {ok, {response, Status, Headers, {some, {json_decoded, Value}}}}; {error, Decode_error} -> {ok, {response, Status, Headers, {some, {invalid_or_unexpected_json, Body, Decode_error}}}} end; none -> case gleam@bit_array:to_string( Body ) of {ok, Body@1} -> case gleam@list:key_find( Headers, <<"content-type"/utf8>> ) of {ok, Mime} -> case gleam_stdlib:contains_string( Mime, <<"application/json"/utf8>> ) of true -> {ok, {response, Status, Headers, {some, {headers_say_json, Body@1}}}}; false -> {ok, {response, Status, Headers, {some, {plain_text, Body@1}}}} end; {error, nil} -> {ok, {response, Status, Headers, {some, {plain_text, Body@1}}}} end; {error, nil} -> {ok, {response, Status, Headers, {some, {octet_stream, Body}}}} end end; {Status@1, Headers@1, none} -> {ok, {response, Status@1, Headers@1, none}} end end ), gleam@result:flatten(_pipe@1) end}] ), erlang:element(7, Conn)}, case Rest of [] -> Conn@1; _ -> receive_internal(Conn@1, Selector, Timeout) end; [] -> Conn end. -spec 'receive'(connection(HUN), integer()) -> connection(HUN). 'receive'(Conn, Timeout) -> Selector = dove@internal@tcp:new_selector(), receive_internal(Conn, Selector, Timeout). -spec shutdown(connection(HVA)) -> {ok, list({gleam@erlang:reference_(), {ok, gleam@http@response:response(gleam@option:option(dove@response:body(HVA)))} | {error, dove@error:error()}})} | {error, dove@error:error()}. shutdown(Conn) -> Conn@1 = 'receive'(Conn, erlang:element(7, Conn)), _pipe = mug_ffi:shutdown(erlang:element(3, Conn@1)), _pipe@1 = gleam@result:map(_pipe, fun(_) -> erlang:element(6, Conn@1) end), gleam@result:map_error( _pipe@1, fun(Tcp_error) -> {tcp_error, Tcp_error} end ).