%%% @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 , content_types_accepted/2 , announce_req/2 ] }]). -export([ rest_init/2 , resource_exists/2 , handle_get/2 , handle_put/2 , handle_patch/2 , delete_resource/2 ]). -type options() :: #{ path => string() , model => atom() , verbose => boolean() }. -type state() :: #{ opts => options() , id => binary() , entity => sumo:user_doc() , module => module() }. -export_type([state/0, options/0]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Cowboy Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @doc Announces the Req and moves on. %% It extracts the :id binding from the Req and leaves it in %% the id key in the state. %% @see cowboy_rest:rest_init/2 -spec rest_init(cowboy_req:req(), options()) -> {ok, cowboy_req:req(), state()}. rest_init(Req, Opts) -> Req1 = announce_req(Req, Opts), #{model := Model} = Opts, Module = sumo_config:get_prop_value(Model, module), {Id, Req2} = cowboy_req:binding(id, Req1), {ok, Req2, #{opts => Opts, id => Id, module => Module}}. %% @doc Verifies if there is an entity with the given id. %% The provided id must be the value for the id field in %% SumoDb. If the entity is found, it's kept in the %% state. %% @see cowboy_rest:resource_exists/2 %% @see sumo:find/2 -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. %% @doc Renders the found entity. %% @see resource_exists/2 -spec handle_get(cowboy_req:req(), state()) -> {iodata(), cowboy_req:req(), state()}. handle_get(Req, State) -> #{entity := Entity, module := Module} = State, ResBody = sr_json:encode(Module:to_json(Entity)), {ResBody, Req, State}. %% @doc Updates the found entity. %% To parse the body, it uses update/2 from the %% model provided in the options. %% @see resource_exists/2 -spec handle_patch(cowboy_req:req(), state()) -> {{true, binary()} | false | halt, cowboy_req:req(), state()}. handle_patch(Req, #{entity := Entity} = State) -> #{module := Module} = State, try {ok, Body, Req1} = cowboy_req:body(Req), Json = sr_json:decode(Body), persist(Module:update(Entity, Json), Req1, State) catch _:badjson -> Req3 = cowboy_req:set_resp_body( sr_json:error(<<"Malformed JSON request">>), Req), {false, Req3, State} end. %% @doc Updates the entity if found, otherwise it creates a new one. %% To parse the body, it uses either update/2 or %% from_json/2 (if defined) or from_json/1 %% from the model provided in the options. %% @see resource_exists/2 -spec handle_put(cowboy_req:req(), state()) -> {{true, binary()} | false | halt, cowboy_req:req(), state()}. handle_put(Req, #{entity := Entity} = State) -> #{module := Module} = State, try {ok, Body, Req1} = cowboy_req:body(Req), Json = sr_json:decode(Body), persist(Module:update(Entity, Json), Req1, State) catch _:badjson -> Req3 = cowboy_req:set_resp_body( sr_json:error(<<"Malformed JSON request">>), Req), {false, Req3, State} end; handle_put(Req, #{id := Id} = State) -> #{module := Module} = State, try {ok, Body, Req1} = cowboy_req:body(Req), Json = sr_json:decode(Body), persist(from_json(Module, Id, Json), Req1, State) catch _:badjson -> Req3 = cowboy_req:set_resp_body( sr_json:error(<<"Malformed JSON request">>), Req), {false, Req3, State} end. %% @doc Deletes the found entity. %% @see resource_exists/2 -spec delete_resource(cowboy_req:req(), state()) -> {boolean() | halt, cowboy_req:req(), state()}. delete_resource(Req, State) -> #{opts := #{model := Model}, id := Id} = State, Result = sumo:delete(Model, Id), {Result, Req, State}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Auxiliary Functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% from_json(Module, Id, Json) -> try Module:from_json(Id, Json) catch _:undef -> Module:from_json(Json) end. persist({error, Reason}, Req, State) -> Req1 = cowboy_req:set_resp_body(Reason, Req), {false, Req1, State}; persist({ok, Entity}, Req1, State) -> #{opts := #{model := Model}, module := Module} = State, PersistedEntity = sumo:persist(Model, Entity), ResBody = sr_json:encode(Module:to_json(PersistedEntity)), Req2 = cowboy_req:set_resp_body(ResBody, Req1), {true, Req2, State}.