From 3f8c3aa0c0f6588de99bbf9ffb42bcb6cfdd26a7 Mon Sep 17 00:00:00 2001 From: T Floyd Wright Date: Mon, 11 May 2026 20:26:30 -0800 Subject: [PATCH] fix: reject contradictory create/update config Configuring a resource with `create_with: false` alongside a custom `:create` component (or `update_with: false` alongside a custom `:edit` component) is contradictory: the disabling option says the action is off, while the component override says it's customized. Catching the misconfiguration at compile time forces users to pick one approach instead of silently behaving in unexpected ways. Closes #139 --- lib/live_admin/router.ex | 34 ++++++++++++++++++++++++++++ test/live_admin/router_test.exs | 39 +++++++++++++++++++++++++++++++++ test/support/resources.ex | 25 +++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 test/live_admin/router_test.exs diff --git a/lib/live_admin/router.ex b/lib/live_admin/router.ex index 647a42f..f0a669e 100644 --- a/lib/live_admin/router.ex +++ b/lib/live_admin/router.ex @@ -22,6 +22,7 @@ defmodule LiveAdmin.Router do |> Map.fetch!(:path) @base_path Path.join(["/", current_path, unquote(path)]) + @__live_admin_scope_opts__ unquote(opts) scope unquote(path), alias: false, as: false do live_session :"live_admin_#{@base_path}", @@ -54,6 +55,14 @@ defmodule LiveAdmin.Router do import Phoenix.LiveView.Router, only: [live: 4] quote bind_quoted: [path: path, resource_mod: resource_mod] do + LiveAdmin.Router.__validate_config__!( + resource_mod, + @__live_admin_scope_opts__, + create_with: Application.compile_env(:live_admin, :create_with), + update_with: Application.compile_env(:live_admin, :update_with), + components: Application.compile_env(:live_admin, :components, []) + ) + full_path = Path.join(@base_path, path) live(path, LiveAdmin.Components.Container, :index, @@ -78,6 +87,31 @@ defmodule LiveAdmin.Router do end end + @disabled_with_custom_component [ + {:create_with, :create}, + {:update_with, :edit} + ] + + @doc false + def __validate_config__!(resource_mod, scope_opts, app_opts) do + Code.ensure_compiled!(resource_mod) + resource_opts = resource_mod.__live_admin_config__() + levels = [resource_opts, scope_opts, app_opts] + + Enum.each(@disabled_with_custom_component, fn {with_key, component_key} -> + disabled? = Enum.any?(levels, &(Keyword.get(&1, with_key) == false)) + + component_set? = + Enum.any?(levels, &Keyword.has_key?(Keyword.get(&1, :components, []), component_key)) + + if disabled? and component_set? do + raise ArgumentError, + "invalid config for resource #{inspect(resource_mod)}: " <> + "#{with_key}: false cannot be combined with a custom :#{component_key} component" + end + end) + end + def build_session(conn, base_path, opts) do opts_schema = LiveAdmin.base_configs_schema() ++ diff --git a/test/live_admin/router_test.exs b/test/live_admin/router_test.exs new file mode 100644 index 0000000..36c4051 --- /dev/null +++ b/test/live_admin/router_test.exs @@ -0,0 +1,39 @@ +defmodule LiveAdmin.RouterTest do + use ExUnit.Case, async: true + + alias LiveAdmin.Router + + describe "create_with: false at resource level with custom :create component at resource level" do + test "raises ArgumentError" do + assert_raise ArgumentError, ~r/create_with: false.*:create component/, fn -> + Router.__validate_config__!(LiveAdminTest.UserWithCustomCreate, [], []) + end + end + end + + describe "create_with: false at scope level with custom :create component at resource level" do + test "raises ArgumentError" do + assert_raise ArgumentError, ~r/create_with: false.*:create component/, fn -> + Router.__validate_config__!( + LiveAdminTest.UserWithCustomCreateOnly, + [create_with: false], + [] + ) + end + end + end + + describe "update_with: false at app level with custom :edit component at resource level" do + test "raises ArgumentError" do + assert_raise ArgumentError, ~r/update_with: false.*:edit component/, fn -> + Router.__validate_config__!(LiveAdminTest.UserWithCustomEditOnly, [], update_with: false) + end + end + end + + describe "resource without conflicting config" do + test "returns :ok" do + assert :ok == Router.__validate_config__!(LiveAdminTest.User, [], []) + end + end +end diff --git a/test/support/resources.ex b/test/support/resources.ex index 31932fb..2d90dde 100644 --- a/test/support/resources.ex +++ b/test/support/resources.ex @@ -1,3 +1,28 @@ +defmodule LiveAdminTest.CustomFormComponent do + use Phoenix.LiveComponent + + def render(assigns), do: ~H"
custom form
" +end + +defmodule LiveAdminTest.UserWithCustomCreate do + use LiveAdmin.Resource, + schema: LiveAdminTest.User, + create_with: false, + components: [create: LiveAdminTest.CustomFormComponent] +end + +defmodule LiveAdminTest.UserWithCustomCreateOnly do + use LiveAdmin.Resource, + schema: LiveAdminTest.User, + components: [create: LiveAdminTest.CustomFormComponent] +end + +defmodule LiveAdminTest.UserWithCustomEditOnly do + use LiveAdmin.Resource, + schema: LiveAdminTest.User, + components: [edit: LiveAdminTest.CustomFormComponent] +end + defmodule LiveAdminTest.User do use Ecto.Schema