Return json error message to api client and halt processing of remaining code in the route

I am building a REST api using Elixir and Plug.

I am attempting to validate some variables and send specific error messages back to the client application.
In the process I get the error below:

07:56:36.926 [error] #PID<0.401.0> running Momo.Router (connection #PID<0.400.0>, stream id 1) terminated
Server: 5.153.40.138:7064 (http)
Request: GET /hello
** (exit) an exception was raised:
    ** (RuntimeError) expected dispatch/2 to return a Plug.Conn, all plugs must receive a connection (conn) and return a connection
        (momo) lib/momo/router.ex:1: Momo.Router.plug_builder_call/2
        (momo) lib/plug/debugger.ex:122: Momo.Router.call/2
        (plug) lib/plug/adapters/cowboy2/handler.ex:12: Plug.Adapters.Cowboy2.Handler.init/2
        (cowboy) /opt/apps/elixir_projects/momo/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy) /opt/apps/elixir_projects/momo/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.execute/3
        (cowboy) /opt/apps/elixir_projects/momo/deps/cowboy/src/cowboy_stream_h.erl:252: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Please find my code below:

post "/process_request" do
        {:ok, body, conn} = read_body(conn)
        IO.puts body
        body = Poison.decode!(body)
        
        
        unless body |> Map.has_key?("amount") do
            IO.puts "no amount"
            send_resp(conn, 500, "Error")
            halt(conn)
        end
        
        amount = body["amount"]
        service_id =  body["service_id"]
        customer_number = body["customer_number"]
        exttrid = body["exttrid"]
        req_type = body["req_type"]
        return_url = body["return_url"]
        ts = body["ts"]
        
        Operations.process_req(customer_number, exttrid, narration, amount, req_type, service_id, return_url)
        ##send_resp(conn, 201, "Request received")
    end

Can anyone assist me?
Thanks.

:wave:

Where do you expect the execution to stop in your code snippet?

Hi,
Sorry for responding late.

I want the execution to stop at the point I have halt(conn).
I do not want the remaining code to be executed.

Thanks.

You need to return the result of halt(conn).

Roughly like this:

post "/process_request" do
  {:ok, body, conn} = read_body(conn)
  IO.puts body
  body = Poison.decode!(body)

  if body |> Map.has_key?("amount") do
    amount = body["amount"]
    service_id =  body["service_id"]
    customer_number = body["customer_number"]
    exttrid = body["exttrid"]
    req_type = body["req_type"]
    return_url = body["return_url"]
    ts = body["ts"]

    Operations.process_req(customer_number, exttrid, narration, amount, req_type, service_id, return_url)
    ##send_resp(conn, 201, "Request received")
  else
    IO.puts "no amount"
    conn
    |> send_resp(500, "Error")
    |> halt()
  end
end

Notice, that I use the pipe-operator (|>) to pipe the conn, as it wouldn’t store the 500 error in it otherwise. Elixir is immutable.

I thought about that solution but the issue is that I have to do validation for each of the parameters and stop execution if any of them fails.
Two things I have to check for each of the parameters listed in the route are:

  1. checking to ensure that each of those keys is present in the request from the client
  2. validating the content of the data in those parameters

Please advise me on the best way to go about this.

Thanks.

Then you should probably use the with/1 special-form.

post "/process_request" do
  {:ok, body, conn} = read_body(conn) # Where comes this `conn` from?
  
  with {:ok, parsed} <- Poison.decode(body)
    {:ok, param1} <- validate_param1(parsed)
    {:ok, param2} <- validate_param2(parsed)
  do
    process_request(param1, param2) # I hope this returns a `conn`!
  else
    {:error, reason} ->
      conn
      |> send_resp(500, "Error #{inspect reason}")
      |> halt(conn)
  end
end
1 Like

Great suggestion. Will try that out now.
I’m using Plugs.

Thanks.

Hello NobbZ,

Thanks a lot for your suggestion.
It worked exactly for me.

Thanks a lot.

final solution below:

{:ok, body, conn} = read_body(conn)

{:ok, parsed} = Poison.decode(body)


with {:ok, amount} <- parse_amount(parsed["amount"]),
   {:ok, exttrid} <- parse_id(parsed["id"]),
   {:ok, cust_no} <- parse_cust(parsed["cust_no"]),
   {:ok, narration} <- parse_narration(parsed["narration"])
do
  save(cust_no, id, amount, narrationo)
else
  {:error, reason} ->
    conn
    |> send_resp(200, Poison.encode!(reason))
    |> halt
end
1 Like