[BUG] Resource creation errors when schema contains {:array, Ecto.Enum} field ((FunctionClauseError) no function clause matching in String.length/1) #294
Versions Used
Kaffy: ab41f6456a80164ba5512db56183a63beb9c5a33
Phoenix: 1.7.7
Elixir: 1.14
This is a regression we at @hexafarms noticed when upgrading from 0.9.4 to 0.10.0-rc.2. The bug is still present on master at ab41f6456a80164ba5512db56183a63beb9c5a33.
When trying to create a record for a schema that contains an Enum array, e.g.:
schema "organization_configurations" do
field :measurements, {:array, Ecto.Enum}, values: @measurements
belongs_to :organization, Organization
timestamps()
end
What's actually happening?
An error is being thrown when submitting the form for resource creation.
# FunctionClauseError at POST /kaffy/accounts/organization_configuration
Exception:
** (FunctionClauseError) no function clause matching in String.length/1
(elixir 1.14.2) lib/string.ex:2017: String.length(["weight", "average_weight"])
(kaffy 0.10.0-rc.3) lib/kaffy/resource_params.ex:10: anonymous fn/2 in Kaffy.ResourceParams.decode_map_fields/3
(elixir 1.14.2) lib/enum.ex:1662: anonymous fn/3 in Enum.map/2
(stdlib 4.3.1.2) maps.erl:411: :maps.fold_1/3
(elixir 1.14.2) lib/enum.ex:2480: Enum.map/2
(kaffy 0.10.0-rc.3) lib/kaffy/resource_params.ex:9: Kaffy.ResourceParams.decode_map_fields/3
(kaffy 0.10.0-rc.3) lib/kaffy_web/controllers/resource_controller.ex:247: KaffyWeb.ResourceController.create/2
(kaffy 0.10.0-rc.3) lib/kaffy_web/controllers/resource_controller.ex:1: KaffyWeb.ResourceController.action/2
(kaffy 0.10.0-rc.3) lib/kaffy_web/controllers/resource_controller.ex:1: KaffyWeb.ResourceController.phoenix_controller_pipeline/2
(phoenix 1.7.7) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
(hexchange 0.1.0) lib/hexchange_web/endpoint.ex:1: HexchangeWeb.Endpoint.plug_builder_call/2
(hexchange 0.1.0) lib/plug/debugger.ex:136: HexchangeWeb.Endpoint."call (overridable 3)"/2
(hexchange 0.1.0) lib/hexchange_web/endpoint.ex:1: HexchangeWeb.Endpoint.call/2
(phoenix 1.7.7) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.10.0) /home/ntraum/coding/hexafarms/hexchange/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.10.0) /home/ntraum/coding/hexafarms/hexchange/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.10.0) /home/ntraum/coding/hexafarms/hexchange/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 4.3.1.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Code:
`lib/string.ex`
No code available.
Called with 1 arguments
* `["weight", "average_weight"]`
Attempted function clauses (showing 1 out of 1)
def length(string) when is_binary(string)
`lib/kaffy/resource_params.ex`
5 map_fields = ResourceSchema.get_map_fields(schema) |> Enum.map(fn {f, _} -> to_string(f) end)
6
7 attrs =
8 Map.get(params, resource, %{})
9 |> Enum.map(fn {k, v} ->
10> case k in map_fields && String.length(v) > 0 do
11 true -> {k, Kaffy.Utils.json().decode!(v)}
12 false -> {k, v}
13 end
14 end)
15 |> Map.new()
`lib/enum.ex`
No code available.
`maps.erl`
No code available.
`lib/enum.ex`
No code available.
`lib/kaffy/resource_params.ex`
4 def decode_map_fields(resource, schema, params) do
5 map_fields = ResourceSchema.get_map_fields(schema) |> Enum.map(fn {f, _} -> to_string(f) end)
6
7 attrs =
8 Map.get(params, resource, %{})
9> |> Enum.map(fn {k, v} ->
10 case k in map_fields && String.length(v) > 0 do
11 true -> {k, Kaffy.Utils.json().decode!(v)}
12 false -> {k, v}
13 end
14 end)
`lib/kaffy_web/controllers/resource_controller.ex`
242 end
243 end
244
245 def create(conn, %{"context" => context, "resource" => resource} = params) do
246 my_resource = Kaffy.Utils.get_resource(conn, context, resource)
247> params = Kaffy.ResourceParams.decode_map_fields(resource, my_resource[:schema], params)
248 changes = Map.get(params, resource, %{})
249 resource_name = Kaffy.ResourceAdmin.singular_name(my_resource)
250
251 with {:permitted, true} <- {:permitted, can_proceed?(my_resource, conn)},
252 {:enabled, true} <- {:enabled, is_enabled?(my_resource, :new)} do
`lib/kaffy_web/controllers/resource_controller.ex`
1> defmodule KaffyWeb.ResourceController do
2 @moduledoc false
3
4 use Phoenix.Controller, namespace: KaffyWeb
5 use Phoenix.HTML
6 alias Kaffy.Pagination
`lib/kaffy_web/controllers/resource_controller.ex`
1> defmodule KaffyWeb.ResourceController do
2 @moduledoc false
3
4 use Phoenix.Controller, namespace: KaffyWeb
5 use Phoenix.HTML
6 alias Kaffy.Pagination
`lib/phoenix/router.ex`
425 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
426 halted_conn
427
428 %Plug.Conn{} = piped_conn ->
429 try do
430> plug.call(piped_conn, plug.init(opts))
431 else
432 conn ->
433 measurements = %{duration: System.monotonic_time() - start}
434 metadata = %{metadata | conn: conn}
435 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
`lib/hexchange_web/endpoint.ex`
1> defmodule HexchangeWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :hexchange
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
`lib/plug/debugger.ex`
No code available.
`lib/hexchange_web/endpoint.ex`
1> defmodule HexchangeWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :hexchange
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
`lib/phoenix/endpoint/sync_code_reload_plug.ex`
17
18 def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19
20 defp do_call(conn, endpoint, opts, retry?) do
21 try do
22> endpoint.call(conn, opts)
23 rescue
24 exception in [UndefinedFunctionError] ->
25 case exception do
26 %UndefinedFunctionError{module: ^endpoint} when retry? ->
27 # Sync with the code reloader and retry once
`lib/plug/cowboy/handler.ex`
6 def init(req, {plug, opts}) do
7 conn = @connection.conn(req)
8
9 try do
10 conn
11> |> plug.call(opts)
12 |> maybe_send(plug)
13 |> case do
14 %Plug.Conn{adapter: {@connection, %{upgrade: {:websocket, websocket_args}} = req}} = conn ->
15 {handler, state, cowboy_opts} = websocket_args
16 {__MODULE__, copy_resp_headers(conn, req), {handler, state}, cowboy_opts}
`/home/ntraum/coding/hexafarms/hexchange/deps/cowboy/src/cowboy_handler.erl`
32 -optional_callbacks([terminate/3]).
33
34 -spec execute(Req, Env) -> {ok, Req, Env}
35 when Req::cowboy_req:req(), Env::cowboy_middleware:env().
36 execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
37> try Handler:init(Req, HandlerOpts) of
38 {ok, Req2, State} ->
39 Result = terminate(normal, Req2, State, Handler),
40 {ok, Req2, Env#{result => Result}};
41 {Mod, Req2, State} ->
42 Mod:upgrade(Req2, Env, Handler, State);
`/home/ntraum/coding/hexafarms/hexchange/deps/cowboy/src/cowboy_stream_h.erl`
301 end.
302
303 execute(_, _, []) ->
304 ok;
305 execute(Req, Env, [Middleware|Tail]) ->
306> case Middleware:execute(Req, Env) of
307 {ok, Req2, Env2} ->
308 execute(Req2, Env2, Tail);
309 {suspend, Module, Function, Args} ->
310 proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
311 {stop, _Req2} ->
`/home/ntraum/coding/hexafarms/hexchange/deps/cowboy/src/cowboy_stream_h.erl`
290 %% to simplify the debugging of errors. The proc_lib library
291 %% already adds the stacktrace to other types of exceptions.
292 -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
293 request_process(Req, Env, Middlewares) ->
294 try
295> execute(Req, Env, Middlewares)
296 catch
297 exit:Reason={shutdown, _}:Stacktrace ->
298 erlang:raise(exit, Reason, Stacktrace);
299 exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
300 erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)
`proc_lib.erl`
No code available.
Versions Used Kaffy: ab41f6456a80164ba5512db56183a63beb9c5a33 Phoenix: 1.7.7 Elixir: 1.14
This is a regression we at @hexafarms noticed when upgrading from
0.9.4
to0.10.0-rc.2
. The bug is still present onmaster
at ab41f6456a80164ba5512db56183a63beb9c5a33.When trying to create a record for a schema that contains an Enum array, e.g.:
What's actually happening?
An error is being thrown when submitting the form for resource creation.
What should happen instead?
Resource should be saved successfuly.