%% @doc Library module containing the jsonrpc API logic
-module(grisp_connect_api).
-export([handle_msg/1]).
-include_lib("kernel/include/logger.hrl").
%--- Macros --------------------------------------------------------------------
-define(method_get, get).
-define(method_post, post).
%--- API -----------------------------------------------------------------------
% @doc Handles requests and notifications from grisp.io.
-spec handle_msg(Msg) ->
ok | {reply, Result :: term(), ReqRef :: binary() | integer()}
| {error, Code :: integer() | atom(), Message :: binary() | undefined, ErData :: term(), ReqRef :: binary() | integer()}
when Msg :: {request, Method :: jarl:method(), Params :: map() | list(), ReqRef :: binary() | integer()}
| {notification, jarl:method(), Params :: map() | list()}.
handle_msg({notification, M, Params}) ->
handle_notification(M, Params);
handle_msg({request, M, Params, ID}) ->
try
handle_request(M, Params, ID)
catch
_:R ->
?LOG_ERROR("Error while processing request ~s: ~p",
[jarl_utils:format_method(M), R]),
ReasonBinary = iolist_to_binary(io_lib:format("~p", [R])),
{error, internal_error, ReasonBinary, undefined, ID}
end.
%--- Internal Funcitons --------------------------------------------------------
maybe_put(Map, []) -> Map;
maybe_put(Map, [{Key, Fun, Filter} | Rest]) ->
maybe_put(maybe_put(Map, Key, Fun, Filter), Rest).
maybe_put(Map, Key, Fun, Filter) ->
try Fun() of
V when is_map(V) -> Map#{Key => maps:with(Filter, V)};
_ -> Map
catch
_:_ -> Map
end.
handle_notification([log, sync], Params) ->
grisp_connect_log:sync(Params);
handle_notification(Method, Params) ->
?LOG_ERROR("Received unexpected notification ~p: ~p", [Method, Params]),
ok.
handle_request([?method_get], #{type := <<"system_info">>} = _Params, ID) ->
ClusterInfo = grisp_connect_cluster:system_info(),
UpdateInfo = grisp_connect_updater:system_info(),
Info = maybe_put(maps:merge(ClusterInfo, UpdateInfo), [
{software, fun grisp_info:software/0,
[id, relname, relvsn, profiles, toolchain_rev, rtems_ver, otp_ver]},
{hardware, fun grisp_info:hardware/0,
[platform, version, serial, batch]}
]),
{reply, Info, ID};
handle_request([?method_post], #{type := <<"start_update">>} = Params, ID) ->
try
URL = maps:get(url, Params),
case grisp_connect_updater:start_update(URL) of
{error, grisp_updater_unavailable} ->
{error, grisp_updater_unavailable, undefined, undefined, ID};
{error, already_updating} ->
{error, already_updating, undefined, undefined, ID};
{error, boot_system_not_validated} ->
{error, boot_system_not_validated, undefined, undefined, ID};
{error, Reason} ->
ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
{error, internal_error, ReasonBinary, undefined, ID};
ok ->
{reply, ok, ID}
end
catch
error:{badkey, _} ->
{error, internal_error, <<"Invalid params">>, undefined, ID}
end;
handle_request([?method_post], #{type := <<"validate">>}, ID) ->
case grisp_connect_updater:validate() of
{error, grisp_updater_unavailable} ->
{error, grisp_updater_unavailable, undefined, undefined, ID};
{error, {validate_from_unbooted, PartitionIndex}} ->
{error, validate_from_unbooted, undefined, PartitionIndex, ID};
{error, Reason} ->
ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
{error, internal_error, ReasonBinary, undefined, ID};
ok ->
{reply, ok, ID}
end;
handle_request([?method_post], #{type := <<"reboot">>}, ID) ->
grisp_connect_client:reboot(),
{reply, ok, ID};
handle_request([?method_post], #{type := <<"cancel">>}, ID) ->
case grisp_connect_updater:cancel() of
{error, grisp_updater_unavailable} ->
{error, grisp_updater_unavailable, undefined, undefined, ID};
ok ->
{reply, ok, ID}
end;
handle_request([log, get], Params, ID) ->
{reply, grisp_connect_log:get(Params), ID};
handle_request([cluster, join], Params, ID) ->
#{cookie := CookieBin, ca := CAPemBin, nodename := NodeNameBin,
fingerprint := FingerprintB64, hostname := HostnameBin,
address := AddressBin} = Params,
Node = binary_to_atom(NodeNameBin),
{ok, Address} = inet:parse_ipv4_address(binary_to_list(AddressBin)),
Opts = #{
hostname => HostnameBin,
address => Address,
cookie => binary_to_atom(CookieBin),
ca => CAPemBin,
fingerprint => base64:decode(FingerprintB64),
monitor => maps:get(monitor, Params, false)
},
case grisp_connect_cluster:join(Node, Opts) of
error -> {reply, false, ID};
IsConnected -> {reply, IsConnected, ID}
end;
handle_request([cluster, leave], Params, ID) ->
#{nodename := NodeNameBin} = Params,
Node = binary_to_atom(NodeNameBin),
{reply, grisp_connect_cluster:leave(Node), ID};
handle_request([cluster, list], #{}, ID) ->
{reply, grisp_connect_cluster:list(), ID};
handle_request(Method, Params, ID) ->
?LOG_ERROR("Received unexpected request ~p: ~p", [Method, Params]),
{error, method_not_found, undefined, undefined, ID}.