Current section
Files
Jump to
Current section
Files
src/kraft_ws_jsonrpc.erl
-module(kraft_ws_jsonrpc).
% Callbacks
-export([init/2]).
-ignore_xref(init/2).
-export([handle/2]).
-ignore_xref(handle/2).
-export([info/2]).
-ignore_xref(info/2).
-export([terminate/3]).
-ignore_xref(terminate/3).
%--- Includes ------------------------------------------------------------------
-include_lib("kernel/include/logger.hrl").
%--- Types ---------------------------------------------------------------------
-type state() :: any().
-optional_callbacks([handshake/3]).
-callback handshake(kraft:conn(), kraft_conn:params(), state()) ->
{reply, kraft:status(), kraft:headers(), kraft:body()}
| {ok, state()}.
-callback init(kraft:conn(), state()) -> state().
-callback message(kraft_jsonrpc:message(), state()) ->
{[kraft_jsonrpc:message()], state()}.
-optional_callbacks([info/2]).
-callback info(any(), state()) -> {[kraft_jsonrpc:message()], state()}.
%--- Callbacks -----------------------------------------------------------------
init(Conn, State0) ->
MState = kraft_ws_util:raw_call(?FUNCTION_NAME, [Conn], State0),
% FIXME: Ugly to update inner state here?
{[], State0#{state => MState}}.
handle({text, Data}, State0) ->
{Replies, State1} = handle_messages(kraft_jsonrpc:decode(Data), State0),
{[encode(R) || R <- Replies], State1}.
info(Info, State0) ->
{Replies, State1} = kraft_ws_util:call(info, [Info], State0),
{[encode(R) || R <- Replies], State1}.
terminate(Reason, _Req, State0) ->
kraft_ws_util:raw_call(?FUNCTION_NAME, [Reason], State0).
%--- Internal ------------------------------------------------------------------
handle_messages({batch, Messages}, State0) ->
Unpacked = [unpack(M) || M <- Messages],
{Replies, State3} = lists:foldl(
fun(Message, {Rs, State1}) ->
{R, State2} = handle_message(Message, State1),
{[Rs, R], State2}
end,
{[], State0},
Unpacked
),
{lists:flatten(Replies), State3};
handle_messages({single, Message}, State0) ->
handle_message(unpack(Message), State0).
handle_message({internal_error, _, _} = Error, State0) ->
{[kraft_jsonrpc:format_error(Error)], State0};
handle_message(Message, #{handler := Handler} = State0) ->
try
kraft_ws_util:call(message, [Message], State0)
catch
error:function_clause:ST ->
case {Message, ST} of
{{call, _, _, ID}, [{Handler, message, _, _} | _]} ->
?LOG_ERROR("JSON-RPC unhandled message: ~p", [Message]),
{[error_reply(method_not_found, ID)], State0};
_Else ->
{[], State0}
end
end.
encode(close = Close) -> Close;
encode({close, _IOData} = Close) -> Close;
encode({close, _Code, _IOData} = Close) -> Close;
encode(Messages) -> {text, kraft_jsonrpc:encode(Messages)}.
error_reply(method_not_found, ID) ->
kraft_jsonrpc:format_error({internal_error, method_not_found, ID}).
unpack({call, Method, Params, ID}) ->
{call, attempt_atom(Method), Params, ID};
unpack({notification, Method, Params}) ->
{notification, attempt_atom(Method), Params};
unpack(Message) ->
Message.
attempt_atom(Binary) when is_binary(Binary) ->
try
binary_to_existing_atom(Binary)
catch
error:badarg -> Binary
end.