dimitarvp

dimitarvp

I’m looking into returning JSON errors from an API app. I know the specs dictate you must always return HTML on certain error codes but I want to circumvent that because I’ll have complete control both on the calling side and the service itself. Plus the service will send logs and telemetry and I want errors to be machine-parseable.

I tried a few things after searching the forum but I couldn’t manage to make any of them work.

Then I stumbled upon Plug.ErrorHandler — Plug v1.20.2. Call me stupid but I have no clue how to use it?

So as a start I want all 404s and 500s to be wrapped with a JSON key errors in an object, returned alongside a 404 and 500 status.

Is this possible using Plug.ErrorHandler? If so, how exactly?

Showing Posts 1 to 7

Exadra37

Exadra37

Please bear in mind that all what I am about to suggest is from the top of my mind, therefore not tested, and may be wrong, but hey it doesn’t hurt for you to give it a try :slight_smile:

  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    send_resp(conn, conn.status, "Something went wrong")
  end

If I am not mistake the above will override the call to the Error view at lib/yourapp_web/views/error_view.ex.

I think that this will return it in json:

# You may need to use the Jason library if returning only the map doesn't work.
# You also want to put the `application/json` header in the `conn`
send_resp(conn, conn.status,%{error: conn.status, message:  "Something went wrong"})

Maybe you can simplify the above to use in your router:

# file: lib/yourapp_web/router.ex
  pipeline :api do
    plug :accepts, ["json"],
    plug :handle_errors
  end

  # you may need to pattern match in `conn.status` to make this work
  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    # You may need to use the Jason library if returning only the map doesn't work.
    # You also want to put the `application/json` header in the `conn`
    send_resp(conn, conn.status,%{error: conn.status, message:  "Something went wrong"})
  end

EDIT: On a second though I think this isn’t reflecting the intended use from the Plug.ErrorHandler

An Alternative that you can try is to change the Error view to always return a JSON object:

defmodule YourAppWeb.ErrorView do
  use YourAppWeb, :view

  require Logger

  # If you want to customize a particular status code
  # for a certain format, you may uncomment below.
  def render(template, assigns) do
    _json_error(template, assigns)
  end

  # By default, Phoenix returns the status message from
  # the template name. For example, "404.html" becomes
  # "Not Found".
  def template_not_found(template, assigns) do
    _json_error(template, assigns)
  end

  defp _json_error(template, assigns) do

    Logger.info("Error template: #{template}")
    # render("500.html", assigns)
    
    # I am not sure, but I think you can just return a Map, otherwise just try to use the Jason library.
    %{
      error: 400, # You need to extract it from assigns or template name
      maessage: "Whoops!"
    }

  end
  
end
dimitarvp

dimitarvp OP

I still don’t understand where to put this copy-pasted module code in my Phoenix project, and how do I register it to be used – in the router, the endpoint, somewhere else?

The docs kind of assume certain knowledge that I found I don’t have.

Exadra37

Exadra37

I update my previous post while you made this one to make it clear, but here it his again:

# file: lib/yourapp_web/router.ex
  pipeline :api do
    plug :accepts, ["json"],
    plug :handle_errors
  end

  # you may need to pattern match in `conn.status` to make this work
  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    # You may need to use the Jason library if returning only the map doesn't work.
    # You also want to put the `application/json` header in the `conn`
    send_resp(conn, conn.status,%{error: conn.status, message:  "Something went wrong"})
  end

This is a guess from me, not actually tried it.

EDIT: On a second though I think this isn’t reflecting the intended use from the Plug.ErrorHandler

dimitarvp

dimitarvp OP

Nope, that didn’t work. :expressionless:

Hitting a non-existent endpoint with curl:

HTTP/1.1 404 Not Found
cache-control: max-age=0, private, must-revalidate
content-length: 1333
content-type: text/markdown; charset=utf-8
date: Mon, 12 Apr 2021 16:33:02 GMT
server: Cowboy

# Phoenix.Router.NoRouteError at POST /does_not_exist

Exception:

    ** (Phoenix.Router.NoRouteError) no route found for POST /does_not_exist (MyAppWeb.Router)
# <snip>

And it’s returning markdown text? Why?

I am still trying to understand how do I just intercept ALL errors and wrap them in keys in a resulting JSON object.

Exadra37

Exadra37

Sorry for not having given you a good solution :frowning:

But did you try the Error view approach? I think that one should work :thinking:

Because you are in development mode. Go to config/dev.exs and set debug_errors to false.

dimitarvp

dimitarvp OP

No idea what to do exactly there either. :003:

It’s been an absolutely awful day and I’d love complete code examples. At least today. If not, I’ll try formulating a solution on a fresh head tomorrow. :smiley:

Exadra37

Exadra37

I showed it to you in my first post:

This file can be found in your app web views folder.

Basically ti’s overriding the render/2 function called to render the error templates for each error status code that is defined in the templates folder.

It also customizes the template_not_found/2 to return the Json error.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews