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:
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
Some javascript global constants used in react native to allow network requests to show in the chrome developer console:
Seem to also mess with the network requests themselves. Once I got rid of that code it inserted the
POST
ed product into the database.Thanks for the comments which got me most of the way.