-module(dove). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([connect/3, get_response/2, request/4, 'receive'/2]). -export_type([request_option/1, request_body/0, response_body/1, connection/1]). -type request_option(HNC) :: {body, request_body()} | {headers, list({binary(), binary()})} | {query_params, list({binary(), binary()})} | {response_decoder, fun((gleam@dynamic:dynamic_()) -> {ok, HNC} | {error, list(gleam@dynamic:decode_error())})}. -type request_body() :: {json, binary()} | {plain_text, binary()}. -type response_body(HND) :: empty | {raw, binary()} | {json_decoded, HND} | {invalid_or_unexpected_json, binary(), gleam@json:decode_error()}. -opaque connection(HNE) :: {connection, binary(), mug:socket(), binary(), list({gleam@erlang:reference_(), gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HNE} | {error, list(gleam@dynamic:decode_error())}))}), list({gleam@erlang:reference_(), {ok, gleam@http@response:response(response_body(HNE))} | {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, gleam@http@response:response(response_body(HNU))} | {error, dove@error:error()}. get_response(Conn, Ref) -> gleam@result:flatten( begin _pipe = gleam@list:key_find(erlang:element(6, Conn), Ref), gleam@result:replace_error(_pipe, {tcp_error, timeout}) end ). -spec get_query_params(list(request_option(any()))) -> list({binary(), binary()}). get_query_params(Options) -> case gleam@list:find_map(Options, fun(Opt) -> case Opt of {query_params, Params} -> {ok, Params}; _ -> {error, nil} end end) of {ok, Params@1} -> Params@1; {error, nil} -> [] end. -spec get_decoder(list(request_option(HOC))) -> gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HOC} | {error, list(gleam@dynamic:decode_error())})). get_decoder(Options) -> case gleam@list:find_map(Options, fun(Opt) -> case Opt of {response_decoder, Decoder} -> {ok, Decoder}; _ -> {error, nil} end end) of {ok, Decoder@1} -> {some, Decoder@1}; {error, nil} -> none end. -spec get_headers(list(request_option(any()))) -> list({binary(), binary()}). get_headers(Options) -> case gleam@list:find_map(Options, fun(Opt) -> case Opt of {headers, Headers} -> {ok, Headers}; _ -> {error, nil} end end) of {ok, Headers@1} -> Headers@1; {error, nil} -> [] end. -spec get_body(list(request_option(any()))) -> gleam@option:option(request_body()). get_body(Options) -> case gleam@list:find_map(Options, fun(Opt) -> case Opt of {body, Body} -> {ok, Body}; _ -> {error, nil} end end) of {ok, Body@1} -> {some, Body@1}; {error, nil} -> none end. -spec request( connection(HNG), gleam@http:method(), binary(), list(request_option(HNG)) ) -> {ok, {connection(HNG), gleam@erlang:reference_()}} | {error, dove@error:error()}. request(Conn, Method, Path, Options) -> Method@3 = case Method of {other, Method@1} -> Method@1; _ -> _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>>}], Method ), {ok, Method@2} = 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 => 74}) end, Method@2 end, Query_params = get_query_params(Options), Headers = get_headers(Options), Body = get_body(Options), Decoder = get_decoder(Options), {Body@3, Headers@1} = case Body of {some, {json, Body@1}} -> {{some, Body@1}, gleam@list:append( Headers, [{<<"content-type"/utf8>>, <<"application/json"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe = Body@1, _pipe@1 = gleam@string:length(_pipe), gleam@int:to_string(_pipe@1) end}] )}; {some, {plain_text, Body@2}} -> {{some, Body@2}, gleam@list:append( Headers, [{<<"content-type"/utf8>>, <<"text/plain"/utf8>>}, {<<"content-length"/utf8>>, begin _pipe@2 = Body@2, _pipe@3 = gleam@string:length(_pipe@2), gleam@int:to_string(_pipe@3) end}] )}; none -> {none, Headers} end, gleam@result:then( dove@request:encode( erlang:element(2, Conn), Method@3, Path, Query_params, Headers@1, Body@3 ), fun(Request) -> gleam@result:then( begin _pipe@4 = dove@tcp:send(erlang:element(3, Conn), Request), 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 ). -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).