0

I'm getting this error when a request hits my router at OPTIONS "/products"

error

14:45:33.433 [error] #PID<0.339.0> running Api.Router terminated
Server: 192.168.20.3:4000 (http)
Request: OPTIONS /products
** (exit) an exception was raised:
    ** (Poison.EncodeError) unable to encode value: {:brand, {"can't be blank", [validation: :required]}}
        (poison) lib/poison/encoder.ex:383: Poison.Encoder.Any.encode/2
        (poison) lib/poison/encoder.ex:259: anonymous fn/3 in Poison.Encoder.List.encode/3
        (poison) lib/poison/encoder.ex:260: Poison.Encoder.List."-encode/3-lists^foldr/2-1-"/3
        (poison) lib/poison/encoder.ex:260: Poison.Encoder.List.encode/3
        (poison) lib/poison/encoder.ex:227: anonymous fn/4 in Poison.Encoder.Map.encode/3
        (poison) lib/poison/encoder.ex:228: Poison.Encoder.Map."-encode/3-lists^foldl/2-0-"/3
        (poison) lib/poison/encoder.ex:228: Poison.Encoder.Map.encode/3
        (poison) lib/poison/encoder.ex:227: anonymous fn/4 in Poison.Encoder.Map.encode/3

Image showing the error caught on the frontend that occurred during the network request:

enter image description here

Judging by the image above it looks like the product JSON object does get sent so for some reason it isn't correctly mapping the http post body to the elixir changeset.

log of conn:

Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> %{}
%Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{},
 before_send: [], body_params: %{},
 cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false,
 host: "192.168.20.3", method: "OPTIONS", owner: #PID<0.339.0>, params: %{},
 path_info: ["products"], path_params: %{}, peer: {{192, 168, 20, 3}, 63793},
 port: 4000,
 private: %{plug_route: #Function<1.14347947/1 in Api.Router.do_match/4>},
 query_params: %{}, query_string: "", remote_ip: {192, 168, 20, 3},
 req_cookies: %Plug.Conn.Unfetched{aspect: :cookies},
 req_headers: [{"host", "192.168.20.3:4000"}, {"connection", "keep-alive"},
  {"access-control-request-method", "POST"}, {"origin", "http://evil.com/"},
  {"user-agent",
   "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/
537.36"},
  {"access-control-request-headers", "content-type,x-requested-with"},
  {"accept", "*/*"}, {"referer", "http://localhost:8081/debugger-ui"},
  {"accept-encoding", "gzip, deflate, sdch"},
  {"accept-language", "en-GB,en-US;q=0.8,en;q=0.6"}], request_path: "/products",
 resp_body: nil, resp_cookies: %{},
 resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}],
 scheme: :http, script_name: [], secret_key_base: nil, state: :unset,
 status: nil}
%Api.Product{__meta__: #Ecto.Schema.Metadata<:built, "products">, brand: nil,
 description: nil, id: nil, image: nil, name: nil, numberOfVotes: nil,
 rating: nil}

router.ex

defmodule Api.Router do
  use Plug.Router

  if Mix.env == :dev do
    use Plug.Debugger
  end
  plug :match
  plug Plug.Parsers, parsers: [:json],
                   pass:  ["application/json"],
                   json_decoder: Poison
  plug :dispatch

  get "/favicon.ico" do
    # Api.Repo.getCategories(conn)
  end

  get "/categories/" do
    Api.Repo.getCategories(conn)
  end

  options "/categories/" do
    Api.Repo.getCategories(conn)
  end

  post "/products" do
    Api.Repo.insertProduct(conn, conn.body_params)
  end

  options "/products" do
    IO.puts inspect conn.body_params
    Api.Repo.insertProduct(conn, conn.body_params)
  end

  get "/products" do
    Api.Repo.insertProduct(conn, conn.body_params)
  end
end

In repo.ex

  def insertProduct(conn, product) do
    IO.inspect(conn)
    changeset = Api.Product.changeset(%Api.Product{}, product)
    errors = changeset.errors
    valid = changeset.valid?
    case insert(changeset) do
      {:ok, product} ->
        conn
          |> put_resp_content_type("application/json")
          |> send_resp(200, Poison.encode!(%{
              successs: "success"
          }))
      {:error, changeset} ->

        conn
          |> put_resp_content_type("application/json")
          |> send_resp(500, Poison.encode!(%{
              failure: changeset
          }))
    end
  end

product.ex

defmodule Api.Product do
  use Ecto.Schema

  @derive {Poison.Encoder, only: [:name, :brand, :description, :image, :rating, :numberOfVotes]}
  schema "products" do
    field :name, :string
    field :brand, :string
    field :description, :string
    field :image, :string
    field :rating, :integer
    field :numberOfVotes, :integer
  end

  def changeset(product, params \\ %{}) do
    product
    |> Ecto.Changeset.cast(params, [:name, :brand, :description, :image, :rating, :numberOfVotes])
    |> Ecto.Changeset.validate_required([:name, :description, :brand])
  end
end

By the way - origin is evil.com because of a browser plugin I use to enable CORS

BeniaminoBaggins
  • 11,202
  • 41
  • 152
  • 287
  • 1
    The first line of your `insertProduct` function body is `IO.puts conn`. The problem is that `conn` is a struct, and `IO.puts/2` takes a string argument. You can switch this to `IO.inspect` if you are trying to debug something other than a string or char data. – stevelove May 23 '17 at 02:00
  • @stevelove Awesome thank you that helped a lot. Have updated the question. – BeniaminoBaggins May 23 '17 at 02:07
  • 1
    The answer is there in the error: `expected params to be a map, got: %Api.Product{...}`. Probably here: `lib/api/product.ex:16`. You seem to be calling one of Ecto's `cast` functions with a struct (i.e. `%Api.Product{}`) when the function is expecting you to pass a map, i.e. `%{}`. – stevelove May 23 '17 at 02:11
  • @stevelove Yeah thanks. Will make it expect a `%Api.Product{...}` – BeniaminoBaggins May 23 '17 at 02:17
  • 1
    I think the problem is in repo.ex, you are creating a Product struct out of `params` and sending that to `Api.Product.changeset`. Just send `params` straight in. – stevelove May 23 '17 at 02:22
  • @stevelove yes that fixed that error thanks! Onto the next one. Working on it myself but updated the question anyway since I got further ahead thanks to your comments. – BeniaminoBaggins May 23 '17 at 02:51

1 Answers1

0

Some javascript global constants used in react native to allow network requests to show in the chrome developer console:

GLOBAL.XMLHttpRequest = GLOBAL.originalXMLHttpRequest || GLOBAL.XMLHttpRequest

Seem to also mess with the network requests themselves. Once I got rid of that code it inserted the POSTed product into the database.

Thanks for the comments which got me most of the way.

BeniaminoBaggins
  • 11,202
  • 41
  • 152
  • 287