Current section
Files
Jump to
Current section
Files
src/swidden_api_handler.erl
-module(swidden_api_handler).
-export([init/2,
terminate/3]).
-include("swidden.hrl").
%% > re:run(<<"XYZ_20001210.CreateUser">>, <<"^([a-zA-Z]+)\_(\\d{4}\\d{2}\\d{2})\.([a-zA-Z]+)$">>, [{capture, all_but_first, binary}]).
%% {match,[<<"XYZ">>,<<"20001210">>,<<"CreateUser">>]}
-define(REGEXP, <<"^([a-zA-Z]+)\_(\\d{4}\\d{2}\\d{2})\.([a-zA-Z]+)$">>).
-define(DEFAULT_HEADERS, #{<<"content-type">> => <<"application/json">>}).
-define(REDIRECT_STATUS_CODE, 307).
init(Req, Opts) ->
HeaderName = proplists:get_value(header_name, Opts),
Services = proplists:get_value(services, Opts),
Interceptor = proplists:get_value(interceptor, Opts),
case cowboy_req:method(Req) of
<<"POST">> ->
case cowboy_req:header(HeaderName, Req) of
undefined ->
%% ヘッダーがみつからない
RawJSON = jsone:encode(#{type => <<"MissingHeaderName">>}, [skip_undefined]),
Req2 = cowboy_req:reply(400, ?DEFAULT_HEADERS, RawJSON, Req),
{ok, Req2, Opts};
HeaderValue ->
%% Service_Version.Operation として分解する
case re:run(HeaderValue, ?REGEXP, [{capture, all_but_first, binary}]) of
{match, [Service, Version, Operation]} ->
case lists:member(Service, Services) of
true ->
Req2 = handle(Service, Version, Operation, Req, Interceptor),
{ok, Req2, Opts};
false when Services == [] ->
Req2 = handle(Service, Version, Operation, Req, Interceptor),
{ok, Req2, Opts};
false ->
Req2 = cowboy_req:reply(400,
?DEFAULT_HEADERS,
jsone:encode(#{error_type => <<"InvalidTarget">>},
[skip_undefined]),
Req),
{ok, Req2, Opts}
end;
_ ->
%% サービスに対応してなかったよ
Req2 = cowboy_req:reply(400,
?DEFAULT_HEADERS,
jsone:encode(#{error_type => <<"MissingService">>},
[skip_undefined]),
Req),
{ok, Req2, Opts}
end
end;
_Other ->
%% POST 以外受け付けていないのでエラーメッセージ
RawJSON = jsone:encode(#{type => <<"UnexpectedMethod">>}, [skip_undefined]),
Req2 = cowboy_req:reply(400, ?DEFAULT_HEADERS, RawJSON, Req),
{ok, Req2, Opts}
end.
read_body(Req, Acc) ->
case cowboy_req:read_body(Req) of
{ok, Data, Req2} ->
{ok, iolist_to_binary([Acc, Data]), Req2};
{more, Data, Req2} ->
read_body(Req2, [Acc, Data])
end.
handle(Service, Version, Operation, Req, Interceptor) ->
%% TODO(nakai): リファクタリング
case cowboy_req:has_body(Req) of
true ->
case read_body(Req, []) of
{ok, <<>>, Req2} ->
case dispatch(Service, Version, Operation, Interceptor) of
200 ->
cowboy_req:reply(200, ?DEFAULT_HEADERS, [], Req2);
{?REDIRECT_STATUS_CODE, Location} ->
cowboy_req:reply(?REDIRECT_STATUS_CODE, #{<<"location">> => Location}, [], Req);
{StatusCode, JSON} ->
RawJSON = jsone:encode(JSON, [skip_undefined]),
cowboy_req:reply(StatusCode, ?DEFAULT_HEADERS, RawJSON, Req2)
end;
{ok, Body, Req2} ->
case validate_json(Service, Version, Operation, Body, Interceptor) of
200 ->
cowboy_req:reply(200, ?DEFAULT_HEADERS, [], Req2);
{?REDIRECT_STATUS_CODE, Location} ->
cowboy_req:reply(?REDIRECT_STATUS_CODE, #{<<"location">> => Location}, [], Req);
{StatusCode, JSON} ->
RawJSON = jsone:encode(JSON, [skip_undefined]),
cowboy_req:reply(StatusCode, ?DEFAULT_HEADERS, RawJSON, Req2)
end
end;
false ->
case dispatch(Service, Version, Operation, Interceptor) of
200 ->
cowboy_req:reply(200, ?DEFAULT_HEADERS, [], Req);
{?REDIRECT_STATUS_CODE, Location} ->
cowboy_req:reply(?REDIRECT_STATUS_CODE, #{<<"location">> => Location}, [], Req);
{StatusCode, JSON} ->
RawJSON = jsone:encode(JSON, [skip_undefined]),
cowboy_req:reply(StatusCode, ?DEFAULT_HEADERS, RawJSON, Req)
end
end.
terminate(normal, _Req, _State) ->
ok;
terminate(Reason, _Req, _State) ->
?debugVal(Reason, 10000),
ok.
dispatch(Service, Version, Operation, Interceptor) ->
case swidden_dispatch:lookup(Service, Version, Operation) of
not_found ->
{400, #{error_type => <<"MissingTarget">>}};
{Module, Function} ->
case code:which(Module) of
non_existing ->
{400, #{error_type => <<"MissingTargetModule">>}};
_ ->
case lists:member({Function, 0}, Module:module_info(exports)) of
true ->
preprocess0(Module, Function, Interceptor);
false ->
{400,
#{
error_type => <<"MissingTargetFunction">>,
error_reason => #{
service => Service,
version => Version,
operation => Operation
}
}}
end
end
end.
validate_json(Service, Version, Operation, RawJSON, Interceptor) ->
case swidden_json_schema:validate_json(Service, Version, Operation, RawJSON) of
{ok, Module, Function, JSON} ->
case code:which(Module) of
non_existing ->
{400, #{error_type => <<"MissingTargetModule">>}};
_ ->
case lists:member({Function, 1}, Module:module_info(exports)) of
true ->
preprocess1(Module, Function, JSON, Interceptor);
false ->
{400,
#{
error_type => <<"MissingTargetFunction">>,
error_reason => #{
service => Service,
version => Version,
operation => Operation
}
}}
end
end;
{error, {data_error, _Reason}} ->
?debugVal(_Reason, 1000),
{400, #{error_type => <<"MalformedJSON">>}};
{error, {database_error, _Key, schema_not_found}} ->
%% TODO(nakai): この部分は外だしする
{400,
#{
error_type => <<"SchemaNotFound">>,
error_reason => #{
service => Service,
version => Version,
operation => Operation
}
}};
{error, Reasons} ->
ErrorReasons = swidden_json_schema:to_json(Reasons),
{400,
#{
error_type => <<"InvalidJSON">>,
error_reason => ErrorReasons
}}
end.
preprocess0(Module, Function, undefined) ->
apply_mfa(Module, Function, []);
preprocess0(Module, Function, Interceptor) ->
case Interceptor:preprocess(Module, Function) of
continue ->
apply_mfa(Module, Function, [], Interceptor);
{stop, Result} ->
response(Result)
end.
preprocess1(Module, Function, JSON, undefined) ->
apply_mfa(Module, Function, [JSON]);
preprocess1(Module, Function, JSON0, Interceptor) ->
case Interceptor:preprocess(Module, Function, JSON0) of
{continue, JSON1} ->
apply_mfa(Module, Function, [JSON1], Interceptor);
{stop, Result} ->
postprocess(Module, Function, Result, Interceptor)
end.
apply_mfa(Module, Function, Args) ->
Result = apply(Module, Function, Args),
response(Result).
apply_mfa(Module, Function, Args, Interceptor) ->
Result = apply(Module, Function, Args),
postprocess(Module, Function, Result, Interceptor).
postprocess(Module, Function, Result0, Interceptor) ->
Result1 = Interceptor:postprocess(Module, Function, Result0),
response(Result1).
response({ok, {redirect, Location}}) ->
{?REDIRECT_STATUS_CODE, Location};
response(ok) ->
200;
response({ok, RespJSON}) ->
{200, RespJSON};
response({error, Type}) ->
{400, #{error_type => Type}};
response({error, Type, Reason}) ->
{400, #{error_type => Type, error_reason => Reason}}.