-module(dove). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([connect/3, build_request/5, build_empty_body_request/4, get_response/2, request/3, 'receive'/2, shutdown/1]). -export_type([request_option/1, request_body/0, response_body/1, connection/1]). -type request_option(HPB) :: {json_decoder, fun((gleam@dynamic:dynamic_()) -> {ok, HPB} | {error, list(gleam@dynamic:decode_error())})}. -type request_body() :: {json, binary()} | empty_request_body | {plain_text, binary()}. -type response_body(HPC) :: {raw, binary()} | {json_decoded, HPC} | empty_response_body | {invalid_or_unexpected_json, binary(), gleam@json:decode_error()}. -opaque connection(HPD) :: {connection, binary(), mug:socket(), bitstring(), list({gleam@erlang:reference_(), gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HPD} | {error, list(gleam@dynamic:decode_error())}))}), list({gleam@erlang:reference_(), {ok, gleam@http@response:response(response_body(HPD))} | {error, dove@error:error()}}), integer()}. -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, <<>>, [], [], Timeout}} end ). -spec build_request( gleam@http:method(), list({binary(), binary()}), binary(), list({binary(), binary()}), request_body() ) -> gleam@http@request:request(request_body()). build_request(Method, Headers, Path, Query, Body) -> _pipe = gleam@http@request:new(), _pipe@1 = gleam@http@request:set_scheme(_pipe, http), _pipe@2 = gleam@http@request:set_method(_pipe@1, Method), _pipe@3 = gleam@http@request:set_path(_pipe@2, Path), _pipe@4 = gleam@http@request:set_query(_pipe@3, Query), _pipe@5 = gleam@http@request:set_body(_pipe@4, Body), gleam@list:fold( Headers, _pipe@5, fun(Request, Header) -> gleam@http@request:set_header( Request, erlang:element(1, Header), erlang:element(2, Header) ) end ). -spec build_empty_body_request( gleam@http:method(), list({binary(), binary()}), binary(), list({binary(), binary()}) ) -> gleam@http@request:request(request_body()). build_empty_body_request(Method, Headers, Path, Query) -> _pipe = gleam@http@request:new(), _pipe@1 = gleam@http@request:set_scheme(_pipe, http), _pipe@2 = gleam@http@request:set_method(_pipe@1, Method), _pipe@3 = gleam@http@request:set_path(_pipe@2, Path), _pipe@4 = gleam@http@request:set_query(_pipe@3, Query), _pipe@5 = gleam@http@request:set_body(_pipe@4, empty_request_body), gleam@list:fold( Headers, _pipe@5, fun(Request, Header) -> gleam@http@request:set_header( Request, erlang:element(1, Header), erlang:element(2, Header) ) end ). -spec get_response(connection(HQA), gleam@erlang:reference_()) -> {ok, {connection(HQA), {ok, gleam@http@response:response(response_body(HQA))} | {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 get_decoder(list(request_option(HQH))) -> gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HQH} | {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(HPF), gleam@http@request:request(request_body()), list(request_option(HPF)) ) -> {ok, {connection(HPF), 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 => 86}) end, Method@1 end, Decoder = get_decoder(Options), {Body@2, Headers} = case erlang:element(4, Request) of {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}] )}; {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}] )}; empty_request_body -> {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), 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@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} -> receive_packet( Socket, Selector, gleam@bit_array:append(Storage, Packet), Start_time, Timeout ) end end; {error, Error} -> {error, Error} end. -spec receive_internal( connection(HPV), gleam@erlang@process:selector({ok, bitstring()} | {error, mug:error()}), integer() ) -> connection(HPV). 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@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 gleam@bit_array:to_string( Body ) of {ok, Body@1} -> case Decoder of {some, Decoder@1} -> case gleam@json:decode( Body@1, Decoder@1 ) of {ok, Value} -> {ok, {response, Status, Headers, {json_decoded, Value}}}; {error, Decode_error} -> {ok, {response, Status, Headers, {invalid_or_unexpected_json, Body@1, Decode_error}}} end; none -> {ok, {response, Status, Headers, {raw, Body@1}}} end; {error, nil} -> {error, is_not_string} end; {Status@1, Headers@1, none} -> {ok, {response, Status@1, Headers@1, empty_response_body}} 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(HPL), integer()) -> connection(HPL). 'receive'(Conn, Timeout) -> Selector = dove@tcp:new_selector(), receive_internal(Conn, Selector, Timeout). -spec shutdown(connection(HQE)) -> {ok, list({gleam@erlang:reference_(), {ok, gleam@http@response:response(response_body(HQE))} | {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 ).