From 3f0d51047a9910e55ede175c0786276e89ec8536 Mon Sep 17 00:00:00 2001 From: cking Date: Wed, 17 Dec 2025 07:26:06 -0800 Subject: [PATCH] fix(con cache): allow concache to accept ets options --- CHANGELOG.md | 4 +++ lib/cache/con_cache.ex | 2 +- lib/cache/ets.ex | 47 ++++++++++++++++++++++++++++++++++- lib/cache/sandbox.ex | 4 +-- mix.exs | 2 +- test/cache/con_cache_test.exs | 33 ++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad64a4b..164bda7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.3.13 +- fix: allow `Cache.ConCache` to accept `ets_options` (strict NimbleOptions validation + normalization) +- feat: allow `Cache.ETS` `write_concurrency: :auto` (OTP 25+) + # 0.3.12 - chore: fix warnings diff --git a/lib/cache/con_cache.ex b/lib/cache/con_cache.ex index fa8c85d..a84eb0f 100644 --- a/lib/cache/con_cache.ex +++ b/lib/cache/con_cache.ex @@ -38,7 +38,7 @@ defmodule Cache.ConCache do :named_table | :compressed | {:heir, pid()} - | {:write_concurrency, boolean()} + | {:write_concurrency, boolean() | :auto} | {:read_concurrency, boolean()} | :ordered_set | :set diff --git a/lib/cache/ets.ex b/lib/cache/ets.ex index ca3cdae..07bb50b 100644 --- a/lib/cache/ets.ex +++ b/lib/cache/ets.ex @@ -1,7 +1,7 @@ defmodule Cache.ETS do @opts_definition [ write_concurrency: [ - type: :boolean, + type: {:or, [:boolean, {:in, [:auto]}]}, doc: "Enable write concurrency" ], read_concurrency: [ @@ -225,6 +225,51 @@ defmodule Cache.ETS do @impl Cache def opts_definition, do: @opts_definition + def opts_definition(opts) when is_list(opts) do + if Keyword.keyword?(opts) do + validate_and_normalize_ets_options(opts) + else + {:error, "expected a keyword list"} + end + end + + def opts_definition(_opts), do: {:error, "expected a keyword list"} + + defp validate_and_normalize_ets_options(opts) do + case NimbleOptions.validate(opts, @opts_definition) do + {:ok, validated} -> + {:ok, normalize_ets_options(opts, validated)} + + {:error, %NimbleOptions.ValidationError{} = error} -> + {:error, Exception.message(error)} + end + end + + defp normalize_ets_options(original_opts, validated) do + Enum.reject( + [ + maybe_type(original_opts, validated), + maybe_compressed(original_opts, validated), + maybe_kv(original_opts, validated, :read_concurrency), + maybe_kv(original_opts, validated, :write_concurrency), + maybe_kv(original_opts, validated, :decentralized_counters) + ], + &is_nil/1 + ) + end + + defp maybe_type(original_opts, validated) do + if Keyword.has_key?(original_opts, :type), do: validated[:type] + end + + defp maybe_compressed(original_opts, validated) do + if Keyword.has_key?(original_opts, :compressed) and validated[:compressed], do: :compressed + end + + defp maybe_kv(original_opts, validated, key) do + if Keyword.has_key?(original_opts, key), do: {key, validated[key]} + end + @impl Cache def start_link(opts) do Task.start_link(fn -> diff --git a/lib/cache/sandbox.ex b/lib/cache/sandbox.ex index 9030a04..b8e3c22 100644 --- a/lib/cache/sandbox.ex +++ b/lib/cache/sandbox.ex @@ -274,9 +274,7 @@ defmodule Cache.Sandbox do end) end - def insert_raw(cache_name, data) when is_tuple(data) do - {key, value} = data - + def insert_raw(cache_name, {key, value}) do Agent.update(cache_name, fn state -> Map.put(state, key, value) end) diff --git a/mix.exs b/mix.exs index ad3a102..053e151 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ElixirCache.MixProject do def project do [ app: :elixir_cache, - version: "0.3.12", + version: "0.3.13", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, description: diff --git a/test/cache/con_cache_test.exs b/test/cache/con_cache_test.exs index fe1315c..f0c9b0e 100644 --- a/test/cache/con_cache_test.exs +++ b/test/cache/con_cache_test.exs @@ -5,6 +5,39 @@ defmodule Cache.ConCacheTest do """ use ExUnit.Case, async: true + describe "ets_options validation" do + test "accepts ets_options keyword list" do + validated = + NimbleOptions.validate!( + [ets_options: [read_concurrency: true, write_concurrency: :auto]], + Cache.ConCache.opts_definition() + ) + + assert [ + {:read_concurrency, true}, + {:write_concurrency, :auto} + ] = validated[:ets_options] + end + + test "rejects unknown ets_options keys" do + assert_raise NimbleOptions.ValidationError, fn -> + NimbleOptions.validate!( + [ets_options: [read_concurency: true]], + Cache.ConCache.opts_definition() + ) + end + end + + test "rejects invalid ets_options value types" do + assert_raise NimbleOptions.ValidationError, fn -> + NimbleOptions.validate!( + [ets_options: [read_concurrency: :yes]], + Cache.ConCache.opts_definition() + ) + end + end + end + defmodule ConCacheAdapter do use Cache, adapter: Cache.ConCache,