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, 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(HSM) :: {json_decoder,
fun((gleam@dynamic:dynamic_()) -> {ok, HSM} |
{error, list(gleam@dynamic:decode_error())})}.
-type request_body() :: {json, binary()} |
{binary, bitstring()} |
empty_request_body |
{plain_text, binary()}.
-type response_body(HSN) :: {raw, bitstring()} |
{string, binary()} |
{json_decoded, HSN} |
empty_response_body |
{invalid_or_unexpected_json, bitstring(), gleam@json:decode_error()}.
-opaque connection(HSO) :: {connection,
binary(),
mug:socket(),
bitstring(),
list({gleam@erlang:reference_(),
gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok, HSO} |
{error, list(gleam@dynamic:decode_error())}))}),
list({gleam@erlang:reference_(),
{ok, gleam@http@response:response(response_body(HSO))} |
{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,
<<<<Host/binary, ":"/utf8>>/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(HTL), gleam@erlang:reference_()) -> {connection(HTL),
{ok, gleam@http@response:response(response_body(HTL))} |
{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(HTS))) -> gleam@option:option(fun((gleam@dynamic:dynamic_()) -> {ok,
HTS} |
{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(HSQ),
gleam@http@request:request(request_body()),
list(request_option(HSQ))
) -> {ok, {connection(HSQ), 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 => 88})
end,
Method@1
end,
Decoder = get_decoder(Options),
{Body@3, Headers} = case erlang:element(4, Request) of
{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}]
)};
{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}]
)};
{binary, 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}]
)};
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@3
),
fun(Request@1) ->
gleam@result:then(
begin
_pipe@10 = dove@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@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(HTG),
gleam@erlang@process:selector({ok, bitstring()} | {error, mug:error()}),
integer()
) -> connection(HTG).
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 Decoder of
{some, Decoder@1} ->
case gleam@json:decode_bits(
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 ->
case gleam@bit_array:to_string(
Body
) of
{ok, Body@1} ->
{ok,
{response,
Status,
Headers,
{string,
Body@1}}};
{error, nil} ->
{ok,
{response,
Status,
Headers,
{raw,
Body}}}
end
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(HSW), integer()) -> connection(HSW).
'receive'(Conn, Timeout) ->
Selector = dove@tcp:new_selector(),
receive_internal(Conn, Selector, Timeout).
-spec shutdown(connection(HTP)) -> {ok,
list({gleam@erlang:reference_(),
{ok, gleam@http@response:response(response_body(HTP))} |
{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
).