How to stop controller action code from being executed

I have a controller code with an action that i need to set permission upon if it fail i want it to stop and send error json to user i had use plug in action but it look like halt didn’t do anything because it was in action code not in another plug.

in my action

 def delete(conn, %{"id" => id,"store_id" => store_id}) do
    Catastore.ValidateStoreOwnerPlug.call(conn, %{store_id: store_id})
    category = Repo.get!(Category, id)
    Repo.delete!(category)

    send_resp(conn, :no_content, "")
  end

in my plug

def call(conn, opts) do

%{current_user: current_user} = conn.assigns

if (current_user.store_id != String.to_integer(opts.store_id)) do
  conn
    |> render(Catastore.ErrorView, "403.json")
    |> halt
end
conn

end

any suggestion?

You could just return the conn in an :ok/:error tuple and branch out if error else run the existing code if true?

but i will have to write the error if/else check for every function i want to reuse. is there any other way that i can reuse plug in an action ?

oh never mind i see, i just have to wrap the code in :ok tuple and render error in the function

I’d do something like:

Action:

  def delete(conn, %{"id" => id,"store_id" => store_id}) do
  with\
    {:ok, conn} <- Catastore.ValidateStoreOwnerPlug.call(conn, %{store_id: store_id}),
    category = Repo.get!(Category, id),
    _ = Repo.delete!(category),

    do: send_resp(conn, :no_content, "")
  end

Plug:

def call(%{current_user: current_user} = conn, opts) do
  if (current_user.store_id != String.to_integer(opts.store_id)) do
    conn
      |> render(Catastore.ErrorView, "403.json")
      |> halt
  else
    {:ok, conn}
  end
end

Or the action could be (using my new block syntax I added to exceptional, not yet in released version):

  def delete(conn, %{"id" => id,"store_id" => store_id}) do
    Exceptional.block do
      {:ok, conn} <- Catastore.ValidateStoreOwnerPlug.call(conn, %{store_id: store_id})
      category = Repo.get!(Category, id)
      Repo.delete!(category)

      send_resp(conn, :no_content, "")
    end
  end
1 Like

currently have to refactor a bit of code but i can’t wait for the released of the Exceptional.block it would be cool to have ability to stop another function by calling another function. :slight_smile: thank you for you answer

My exceptional block is like with except it is purely inside a do, has a couple of options you can pass in, it matches only on <- just like with and matches outright fail if it is in one of the ‘error states’ that exceptional handles too, along with an else fallback as well. It is pretty basic and simple (It is not TCO’able as it normalizes the output of function calls as one example), but quite useful when using the exceptional error handling style. :slight_smile:

1 Like

hope to see it release soon, the syntax would be much cleaner than what i had done currently xD

Plugs have to return just a %Plug.Conn{} and nothing else, otherwise you will get a RuntimeError, so you won’t be able to return {:ok, conn}. I don’t think it’s a good idea to call a plug directly like that anyways, I think what you want to do is put in your controller:

plug Catastore.ValidateStoreOwnerPlug  when action in [:delete]`

That will pipe requests for the :delete action through the plug, so that if the store_ids don’t match up the connection will halt, otherwise it continues as usual. One thing you will need to change though is the way you fetch the store_id parameter in the plug. The second argument to a plug’s call is intended for options about how the plug should work, not data from the request. You will need to use String.to_integer(conn.params["store_id"]) to pull it directly from conn instead.

If there are other actions in your controller that you want to restrict to the current store owner then you can add them where you register the plug: ... when action in [:update, :delete].

If you’re finding yourself needing a call a plug directly then that probably means that whatever the plug is doing should be moved into one of your contexts. Then both the plug and the original caller can both call into the context instead.

2 Likes

If you use it as a plug yep, but I was not using it as a plug, I was calling it straight. :wink:

I prefer to call things straight personally, I often have options I need to give it that are context/connection dependent that would end up making a lot of plugs otherwise.

1 Like