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]).
-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,
<<<<Host/binary, ":"/utf8>>/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_decoder(list(request_option(HNY))) -> gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok,
HNY} |
{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).