cover/Elixir.WalletLimitsFees.PolicyStore.html

1 defmodule WalletLimitsFees.PolicyStore do
2 @moduledoc """
3 ETS-backed GenServer for limit and fee policy persistence.
4
5 Stores limit policies keyed by `{tier, currency}` and fee policies
6 keyed by `{tier, currency, transfer_type}`.
7
8 No database required; this is an in-memory store for Phase 1-4 CI.
9 """
10
11 use GenServer
12
13 alias WalletLimitsFees.{LimitPolicy, FeePolicy}
14
15 @limit_table :wallet_limit_policies
16 @fee_table :wallet_fee_policies
17
18 # --- Client API ---
19
20 def start_link(opts) do
21
:-(
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
22 end
23
24 @doc "Stores or replaces a limit policy. Returns :ok."
25 @spec store_limit_policy(LimitPolicy.t()) :: :ok
26 def store_limit_policy(%LimitPolicy{} = policy) do
27 15 GenServer.call(__MODULE__, {:store_limit_policy, policy})
28 end
29
30 @doc "Retrieves a limit policy by tier and currency."
31 @spec get_limit_policy(tier :: atom(), currency :: String.t()) ::
32 {:ok, LimitPolicy.t()} | {:error, :not_found}
33 def get_limit_policy(tier, currency) do
34 18 GenServer.call(__MODULE__, {:get_limit_policy, tier, currency})
35 end
36
37 @doc "Stores or replaces a fee policy. Returns :ok."
38 @spec store_fee_policy(FeePolicy.t()) :: :ok
39 def store_fee_policy(%FeePolicy{} = policy) do
40 15 GenServer.call(__MODULE__, {:store_fee_policy, policy})
41 end
42
43 @doc "Retrieves a fee policy by tier, currency, and transfer type."
44 @spec get_fee_policy(tier :: atom(), currency :: String.t(), transfer_type :: atom()) ::
45 {:ok, FeePolicy.t()} | {:error, :not_found}
46 def get_fee_policy(tier, currency, transfer_type) do
47 17 GenServer.call(__MODULE__, {:get_fee_policy, tier, currency, transfer_type})
48 end
49
50 @doc "Resets all store state. For test use only."
51 29 def reset, do: GenServer.call(__MODULE__, :reset)
52
53 # --- Server Callbacks ---
54
55 @impl true
56 def init(_opts) do
57
:-(
limit_table = :ets.new(@limit_table, [:set, :protected, :named_table])
58
:-(
fee_table = :ets.new(@fee_table, [:set, :protected, :named_table])
59 {:ok, %{limit_table: limit_table, fee_table: fee_table}}
60 end
61
62 @impl true
63 def handle_call({:store_limit_policy, policy}, _from, state) do
64 15 :ets.insert(@limit_table, {{policy.tier, policy.currency}, policy})
65 15 {:reply, :ok, state}
66 end
67
68 @impl true
69 def handle_call({:get_limit_policy, tier, currency}, _from, state) do
70 18 result =
71 case :ets.lookup(@limit_table, {tier, currency}) do
72 8 [] -> {:error, :not_found}
73 10 [{_key, policy}] -> {:ok, policy}
74 end
75 18 {:reply, result, state}
76 end
77
78 @impl true
79 def handle_call({:store_fee_policy, policy}, _from, state) do
80 15 :ets.insert(@fee_table, {{policy.tier, policy.currency, policy.transfer_type}, policy})
81 15 {:reply, :ok, state}
82 end
83
84 @impl true
85 def handle_call({:get_fee_policy, tier, currency, transfer_type}, _from, state) do
86 17 result =
87 case :ets.lookup(@fee_table, {tier, currency, transfer_type}) do
88 7 [] -> {:error, :not_found}
89 10 [{_key, policy}] -> {:ok, policy}
90 end
91 17 {:reply, result, state}
92 end
93
94 @impl true
95 def handle_call(:reset, _from, state) do
96 29 :ets.delete_all_objects(@limit_table)
97 29 :ets.delete_all_objects(@fee_table)
98 29 {:reply, :ok, state}
99 end
100 end
Line Hits Source