Packages

HTTP Erlang JSON APIs - library for easy API management

Current section

Files

Jump to
heja src heja_api.erl
Raw

src/heja_api.erl

%% Description: heja_api
%%
-module(heja_api).
-behaviour(gen_server).
%% Management API
-export([start_link/0]).
%% API
-export([new/3, del/1,
add_handler/4, del_handler/3, get_handler/3, get_ref/2,
serve/2, deserve/1, status/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
%% Management API -------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% API ------------------------------------------------------------------------
new(Name, Version, Opts) ->
gen_server:call(?MODULE, {new, Name, Version, Opts}).
del(Ref) ->
gen_server:call(?MODULE, {del, Ref}).
status() ->
gen_server:call(?MODULE, status).
add_handler(Ref, Method, Path, Fun) ->
gen_server:call(?MODULE, {add_handler, Ref, Method, Path, Fun}).
del_handler(Ref, Method, Path) ->
gen_server:call(?MODULE, {del_handler, Ref, Method, Path}).
get_handler(Ref, Method, Path) ->
gen_server:call(?MODULE, {get_handler, Ref, Method, Path}).
get_ref(Name, Version) ->
gen_server:call(?MODULE, {get_ref, Name, Version}).
serve(Ref, Port) ->
gen_server:call(?MODULE, {serve, Ref, Port}).
deserve(Ref) ->
gen_server:call(?MODULE, {deserve, Ref}).
%% gen_server callbacks -------------------------------------------------------
init([]) ->
process_flag(trap_exit, true),
{ok, #{}}.
handle_call({new, Name, Version, Opts}, _, State) ->
case api_exist(Name, Version, State) of
true ->
{reply, {error, api_exist}, State};
false ->
Ref = make_ref(),
Api = #{name=>Name,
version=>Version,
options=>Opts,
dispatch_list=>[],
router=>heja_router:new()},
{reply, {ok, Ref}, State#{Ref=>Api}}
end;
handle_call({del, Ref}, _, State) ->
case maps:find(Ref, State) of
{ok, #{server_pid:=Pid}} ->
catch elli:stop(Pid);
_ ->
ok
end,
{reply, ok, maps:remove(Ref, State)};
handle_call({add_handler, Ref, Method, Path, Fun}, _, State) ->
case maps:find(Ref, State) of
{ok, #{dispatch_list:=DL} = Api} ->
NewDL = lists:ukeysort(1, [{{Method, Path}, Fun}|DL]),
NewRouter = heja_router:from_list(NewDL),
{reply, ok, State#{Ref:=Api#{router:=NewRouter,
dispatch_list:=NewDL}}};
error ->
{reply, {error, not_found}, State}
end;
handle_call({del_handler, Ref, Method, Path}, _, State) ->
case maps:find(Ref, State) of
{ok, #{dispatch_list:=DL} = Api} ->
NewDL = lists:ukeysort(1, lists:keydelete({Method, Path}, 1, DL)),
NewRouter = heja_router:from_list(NewDL),
{reply, ok, State#{Ref:=Api#{router:=NewRouter,
dispatch_list:=NewDL}}};
error ->
{reply, {error, not_found}, State}
end;
handle_call({get_handler, Ref, Method, Path}, _, State) ->
case maps:find(Ref, State) of
{ok, #{router:=Router}} ->
case heja_router:get(Path, Method, Router) of
{ok, Fun, Bindings} ->
{reply, {ok, Fun, Bindings}, State};
{error, Error} ->
{reply, {error, Error}, State}
end;
error ->
{reply, {error, not_found}, State}
end;
handle_call({get_ref, Name, Version}, _, State) ->
{reply, get_ref(Name, Version, State), State};
handle_call({serve, Ref, Port}, _, State) ->
case maps:find(Ref, State) of
{ok, #{server_port:=Port}} ->
{reply, {error, already_serving_on_port}, State};
{ok, Api} ->
Options = [{callback, heja_dispatch},
{callback_args, [Ref]},
{port, Port}],
{ok, Pid} = elli:start_link(Options),
{reply, ok, State#{Ref:=Api#{server_pid=>Pid, server_port=>Port}}};
error ->
{reply, {error, not_found}, State}
end;
handle_call({deserve, Ref}, _, State) ->
case maps:find(Ref, State) of
{ok, #{server_pid:=Pid} = Api} ->
elli:stop(Pid),
NewApi = maps:without([server_pid, server_port], Api),
{reply, ok, State#{Ref:=NewApi}};
{ok, _} ->
{reply, ok, State};
error ->
{reply, ok, State}
end;
handle_call(status, _, State) ->
F = fun(_, Api, M) ->
#{name:=N, version:=V, dispatch_list:=DL} = Api,
Port = maps:get(server_port, Api, undefined),
Status = case maps:get(server_pid, Api, undefined) of
undefined ->
not_running;
Pid ->
case is_process_alive(Pid) of
true ->
{running, Port};
false ->
not_running
end
end,
NewDL = lists:map(fun({{Me, Pt}, _}) -> {Me, Pt} end, DL),
M#{{N, V}=>#{dispatch=>NewDL, status=>Status}}
end,
{reply, maps:fold(F, #{}, State), State};
handle_call(_, _, State) ->
{noreply, State}.
handle_cast(_, State) ->
{noreply, State}.
handle_info({'EXIT', Pid, Reason}, State) ->
io:format("EXIT (~p): ~p~n", [Pid, Reason]),
{noreply, State}.
%% Internal -------------------------------------------------------------------
api_exist(Name, Version, State) ->
element(1, get_ref(Name, Version, State)) == ok.
get_ref(Name, Version, State) ->
F = fun(_, #{name:=N, version:=V}) ->
N == Name andalso V == Version;
(_, _) ->
false
end,
case maps:keys(maps:filter(F, State)) of
[] ->
{error, not_found};
[Ref] ->
{ok, Ref}
end.