(Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{errors: %{detail: "Internal Server Error"}}

Hello all:
I have a problem in phoenix project. The official example I use is for creating users, project error
This is an error log

[debug] Converted error Protocol.UndefinedError to 500 response
[error] #PID<0.804.0> running StoreApiWeb.Endpoint (connection #PID<0.803.0>, stream id 1) terminated
Server: localhost:4004 (http)
Request: POST /user
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{errors: %{detail: "Internal Server Error"}} of type Map. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Float, Integer, List, NaiveDateTime, Phoenix.HTML.Form, Phoenix.LiveComponent.CID, Phoenix.LiveView.Component, Phoenix.LiveView.Comprehension, Phoenix.LiveView.JS, Phoenix.LiveView.Rendered, Time, Tuple

it is my case

router.ex

defmodule StoreApiWeb.Router do
  use StoreApiWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/api", StoreApiWeb do
    pipe_through :api
  end

  pipeline :browser do
    plug(:accepts, ["html", "json"])
  end

  scope "/user", StoreApiWeb do
    pipe_through :browser

    # 获取所有用户信息
    resources "/", UserController, except: [:new, :edit]
    # 新增用户
    post "/user_create", UserController, :user_create
    post "/sign_in", UserController, :sign_in
    post "/update", UserController, :update
    get "/detail", UserController, :show
  end

  # Enables LiveDashboard only for development
  #
  # If you want to use the LiveDashboard in production, you should put
  # it behind authentication and allow only admins to access it.
  # If your application does not have an admins-only section yet,
  # you can use Plug.BasicAuth to set up some basic authentication
  # as long as you are also using SSL (which you should anyway).
  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through [:fetch_session, :protect_from_forgery]

      live_dashboard "/dashboard", metrics: StoreApiWeb.Telemetry
    end
  end

  # Enables the Swoosh mailbox preview in development.
  #
  # Note that preview only shows emails that were sent by the same
  # node running the Phoenix server.
  if Mix.env() == :dev do
    scope "/dev" do
      pipe_through [:fetch_session, :protect_from_forgery]

      forward "/mailbox", Plug.Swoosh.MailboxPreview
    end
  end
end

user_controller.ex

defmodule StoreApiWeb.UserController do
  use StoreApiWeb, :controller

  alias StoreApi.Accounts
  alias StoreApi.Accounts.User

  action_fallback StoreApiWeb.FallbackController

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.json", users: users)
  end

  def create(conn, %{"user" => user_params}) do
    with {:ok, %User{} = user} <- Accounts.create_user(user_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.user_path(conn, :show, user))
      |> render("show.json", user: user)
    end
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, "show.json", user: user)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = Accounts.get_user!(id)

    with {:ok, %User{} = user} <- Accounts.update_user(user, user_params) do
      render(conn, "show.json", user: user)
    end
  end

  def delete(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)

    with {:ok, %User{}} <- Accounts.delete_user(user) do
      send_resp(conn, :no_content, "")
    end
  end
  def sign_in(conn, %{"name" => name, "tag" => tag}) do
    case Accounts.authenticate_user(name, tag) do
      {:ok, user} ->
        conn
        |> put_status(:ok)
        |> put_view(StoreApiWeb.UserView)
        |> render("sign_in.json", user: user)
      {:error, message} ->
        conn
        |> put_status(:unauthorized)
        |> put_view(StoreApiWeb.ErrorView)
        |> render("401.json", message: message)
      _ ->
        conn
        |> put_status(:syserror)
        |> put_view(StoreApiWeb.ErrorView)
        |> render("common.json", message: "系统错误!")
    end
  end
end

accounts.ex

defmodule StoreApi.Accounts do
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias StoreApi.Repo

  alias StoreApi.Accounts.User

  @doc """
  Returns the list of users.

  ## Examples

      iex> list_users()
      [%User{}, ...]

  """
  def list_users do
    Repo.all(User)
  end

  @doc """
  Gets a single user.

  Raises `Ecto.NoResultsError` if the User does not exist.

  ## Examples

      iex> get_user!(123)
      %User{}

      iex> get_user!(456)
      ** (Ecto.NoResultsError)

  """
  def get_user!(id), do: Repo.get!(User, id)

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end
end

After I start the service, use Postman to access http://localhost:4004/user , an error is reported, but the database has been updated


image

thank you very much indeed :smiley:

I think what is happening is that the the user you’re trying to update is failing and your fallback controller aren’t transforming the error map into a string (That particular error is very common if when there is a data structure that cannot be directly transformed to a safe string).

If you try to add an else to the with clause in update/2 like this:/

with {:ok, user} <- Accounts.update_user(user, user_params) do

else
  {:error, _changeset} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "Invalid params"})
end

You would at least see if that was the case. You could look into a fallback action (or a fallback controller) Phoenix.Controller — Phoenix v1.6.15

I’ll give it a try. Thank you very much

I tried to add code, and this error occurred

Can you please paste only code instead of screenshots. Its difficult to read and impossible to quote from image.

User is created - you are getting error inside with body.

At line 18 you are passing user to user_path - unless user is binary or number - there will be an error. Looks like it is StoreApi.Accounts.User

|>  put_resp_header("location", Routes.user_path(conn, :show, user))

May be you want it to be (just a guess)

|>  put_resp_header("location", Routes.user_path(conn, :show, user.id))

Sorry, what I want to express is that I use Postman to visit http://localhost:4004/user/create , the actual data has been created successfully, and there is data in the database, but the program reports an error
I used the scheme you mentioned, and no error was reported, but the postman did not return any information
routes.ex

  scope "/user", StoreApiWeb do
    pipe_through :browser

    
    resources "/", UserController, except: [:new, :edit]
   
    post "/create", UserController, :create
    post "/sign_in", UserController, :sign_in
    post "/update", UserController, :update
    post "/detail", UserController, :show
    post "/delete", UserController, :delete
  end

user_controller.ex

def create(conn, %{"user" => user_params}) do
    with {:ok, %User{} = user} <- Accounts.create_user(user_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.user_path(conn, :show, user.id))
      |> render("show.json", user: user)
    end
  end

image
When I comment out this line of code, my program runs normally, and I don’t know why

But thank you very much for giving me ideas

1 Like

It’s unusual for a json endpoint send redirect using location.

# redirects to the path 
|> put_resp_header("location", path) 

Generally web pages rendering data will do a redirect. Json API endpoints respond with status codes and body. So that line is not needed in your code if you are serving json.

1 Like

Thank you so much

Can you mark this post as solution - if this is what fixed your original error ?

1 Like

With this solution, I solved my problem