Current section

Files

Jump to
dove src dove.erl
Raw

src/dove.erl

-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,
<<<<Host/binary, ":"/utf8>>/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
).