-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create sumo_single_entity_handler #4
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
%%% @doc Base GET|PUT|DELETE /[entity]s/:id implementation | ||
-module(sr_single_entity_handler). | ||
|
||
-include_lib("mixer/include/mixer.hrl"). | ||
-mixin([{ sr_entities_handler | ||
, [ init/3 | ||
, allowed_methods/2 | ||
, content_types_provided/2 | ||
, announce_req/2 | ||
, handle_exception/3 | ||
] | ||
}]). | ||
|
||
-export([ rest_init/2 | ||
, resource_exists/2 | ||
, content_types_accepted/2 | ||
, handle_get/2 | ||
, handle_put/2 | ||
, delete_resource/2 | ||
]). | ||
|
||
-type options() :: #{ path => string() | ||
, model => module() | ||
, verbose => boolean() | ||
}. | ||
-type state() :: #{ opts => options() | ||
, id => binary() | ||
, entity => sumo:user_doc() | ||
}. | ||
-export_type([state/0, options/0]). | ||
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
%%% Cowboy Callbacks | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
|
||
-spec rest_init(cowboy_req:req(), options()) -> | ||
{ok, cowboy_req:req(), state()}. | ||
rest_init(Req, Opts) -> | ||
Req1 = announce_req(Req, Opts), | ||
{Id, Req2} = cowboy_req:binding(id, Req1), | ||
{ok, Req2, #{opts => Opts, id => Id}}. | ||
|
||
-spec resource_exists(cowboy_req:req(), state()) -> | ||
{boolean(), cowboy_req:req(), state()}. | ||
resource_exists(Req, State) -> | ||
#{opts := #{model := Model}, id := Id} = State, | ||
case sumo:find(Model, Id) of | ||
notfound -> {false, Req, State}; | ||
Entity -> {true, Req, State#{entity => Entity}} | ||
end. | ||
|
||
%% @todo Use swagger's 'consumes' to auto-generate this if possible | ||
%% @see https://github.com/inaka/sumo_rest/issues/7 | ||
-spec content_types_accepted(cowboy_req:req(), state()) -> | ||
{[{{binary(), binary(), '*'}, atom()}], cowboy_req:req(), state()}. | ||
content_types_accepted(Req, State) -> | ||
{[{{<<"application">>, <<"json">>, '*'}, handle_put}], Req, State}. | ||
|
||
-spec handle_get(cowboy_req:req(), state()) -> | ||
{iodata(), cowboy_req:req(), state()}. | ||
handle_get(Req, State) -> | ||
#{opts := #{model := Model}, entity := Entity} = State, | ||
ResBody = sr_json:encode(Model:to_json(Entity)), | ||
{ResBody, Req, State}. | ||
|
||
-spec handle_put(cowboy_req:req(), state()) -> | ||
{{true, binary()} | false | halt, cowboy_req:req(), state()}. | ||
handle_put(Req, #{entity := Entity} = State) -> | ||
#{opts := #{model := Model}} = State, | ||
try | ||
{ok, Body, Req1} = cowboy_req:body(Req), | ||
Json = sr_json:decode(Body), | ||
handle_put(Model:update(Entity, Json), Req1, State) | ||
catch | ||
_:badjson -> | ||
Req3 = cowboy_req:set_resp_body(<<"Malformed JSON request">>, Req), | ||
{false, Req3, State}; | ||
_:Exception -> handle_exception(Exception, Req, State) | ||
end; | ||
handle_put(Req, #{id := Id} = State) -> | ||
#{opts := #{model := Model}} = State, | ||
try | ||
{ok, Body, Req1} = cowboy_req:body(Req), | ||
Json = sr_json:decode(Body), | ||
handle_put(from_json(Model, Id, Json), Req1, State) | ||
catch | ||
_:badjson -> | ||
Req3 = cowboy_req:set_resp_body(<<"Malformed JSON request">>, Req), | ||
{false, Req3, State}; | ||
_:Exception -> handle_exception(Exception, Req, State) | ||
end. | ||
|
||
-spec delete_resource(cowboy_req:req(), state()) -> | ||
{boolean() | halt, cowboy_req:req(), state()}. | ||
delete_resource(Req, State) -> | ||
#{opts := #{model := Model}, id := Id} = State, | ||
try | ||
Result = sumo:delete(Model, Id), | ||
{Result, Req, State} | ||
catch | ||
_:Exception -> handle_exception(Exception, Req, State) | ||
end. | ||
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
%%% Auxiliary Functions | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
from_json(Model, Id, Json) -> | ||
case erlang:function_exported(Model, from_json, 2) of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "problem" with incredibly useful and generic code that does things automagically is that it is often hard to debug when things go wrong. E.g. if the user programmer fails to export the proper functions, all they will get is a stacktrace. If exporting a certain function is a requirement and a source of a possibly foreseeable error, should it not be handled? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Specifically, should it not check it from_json/1 is exported and if not, emit a readable error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's checked at compile time, since it's a callback in the |
||
true -> Model:from_json(Id, Json); | ||
false -> Model:from_json(Json) | ||
end. | ||
|
||
handle_put({error, Reason}, Req, State) -> | ||
Req1 = cowboy_req:set_resp_body(Reason, Req), | ||
{false, Req1, State}; | ||
handle_put({ok, Entity}, Req1, State) -> | ||
#{opts := #{model := Model}} = State, | ||
PersistedEntity = sumo:persist(Model, Entity), | ||
ResBody = sr_json:encode(Model:to_json(PersistedEntity)), | ||
Req2 = cowboy_req:set_resp_body(ResBody, Req1), | ||
{true, Req2, State}. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
{ | ||
incl_mods, | ||
[ sr_entities_handler | ||
, sr_single_entity_handler | ||
, sr_json | ||
, sumo_rest_doc | ||
] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although it's true that at this point not much can go wrong, the other handle functions are wrapped in a try catch for unforseen exceptions and to return a proper 500 error, should this one do the same or is it overengineering?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our experience dictates that it's impossible to get this function to throw an exception. The main difference is the lack of access to databases or repositories here.