-module(async_handler).
-behavior(cowboy_rest).
-export([set_result/1]).
-export([init/2]).
-export([allowed_methods/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([resource_exists/2]).
-export([from_json/2]).
-export([to_json/2]).
set_result({ReqID, {{_, 200, _}, _Headers, Result}}) ->
set_task_result(ReqID, Result);
set_result({ReqID, {{_, 204, _}, _Headers, _Result}}) ->
set_task_result(ReqID, succeeded);
set_result({ReqID, {{_, StatusCode, Status}, _Headers, _Result}}) ->
set_task_result(ReqID, #{StatusCode => list_to_binary(Status)});
set_result({ReqID, {error, Reason}}) ->
set_task_result(ReqID, #{error => Reason}).
init(Req, State) ->
{cowboy_rest, Req, State}.
allowed_methods(Req, State) ->
{[
<<"GET">>,
<<"HEAD">>,
<<"POST">>,
<<"OPTIONS">>
], Req, State}.
content_types_accepted(Req, State) ->
{[
{<<"application/json">>, from_json}
],
Req, State}.
content_types_provided(Req, State) ->
{[
{<<"application/json">>, to_json}
], Req, State}.
resource_exists(Req, State) ->
case cowboy_req:binding(task_id, Req) of
undefined ->
%% Collections always exist.
{true, Req, State};
TaskID ->
case get_task(TaskID) of
not_found ->
{false, Req, State};
Task ->
{true, Req, State#{task_id => TaskID, task => Task}}
end
end.
from_json(Req, State) ->
{ok, Body, Req1} = cowboy_req:read_body(Req),
{ok, Task} = thoas:decode(Body),
Result = set_task(Task),
Req2 = cowboy_req:set_resp_body(thoas:encode(Result), Req1),
{true, Req2, State}.
to_json(Req, #{task := Task} = State) ->
{thoas:encode(Task), Req, State};
to_json(Req, State) ->
Collection = get_collection(),
{thoas:encode(Collection), Req, State}.
%%
%% Internal.
%%
table() -> workloads_async.
set_task(Task) ->
{ok, ReqID} = forward(Task),
TaskID = req_id_to_task_id(ReqID),
Row = {TaskID, Task, in_progress},
ets:insert(table(), Row),
row_to_task(Row).
set_task_result(ReqID, Result) ->
TaskID = req_id_to_task_id(ReqID),
ets:update_element(table(), TaskID, {3, Result}).
get_task(TaskID) ->
[Row] = ets:lookup(table(), TaskID),
row_to_task(Row).
get_collection() ->
[ row_to_task(Row) || Row <- ets:tab2list(table()) ].
forward(#{<<"uri">> := URI, <<"payload">> := Payload}) ->
JSONBody = thoas:encode(Payload),
httpc:request(
post,
{URI, [], "application/json", JSONBody},
[],
[{sync, false},
{receiver, {?MODULE, set_result, []}}
]
).
req_id_to_task_id(ReqID) ->
TaskID = base64:encode_to_string(
term_to_binary(ReqID),
#{mode => urlsafe}
),
list_to_binary(TaskID).
row_to_task({TaskID, Task, Result}) ->
#{id => TaskID,
task => Task,
result => Result
}.