cover/Elixir.WalletSettlement.ExceptionStore.html

1 defmodule WalletSettlement.ExceptionStore do
2 @moduledoc """
3 ETS-backed GenServer for settlement exception record persistence.
4
5 Tables:
6 - `:wallet_exc_records` — exceptions keyed by exception_id.
7 - `:wallet_exc_batch_idx` — bag index: {batch_id, exception_id}.
8 - `:wallet_exc_status_idx`— bag index: {status, exception_id}.
9 """
10 use GenServer
11
12 alias WalletSettlement.ExceptionRecord
13
14 @table :wallet_exc_records
15 @batch_idx :wallet_exc_batch_idx
16 @status_idx :wallet_exc_status_idx
17
18
:-(
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
19
20 @spec store(ExceptionRecord.t()) :: :ok
21 10 def store(%ExceptionRecord{} = exc), do: GenServer.call(__MODULE__, {:store, exc})
22
23 @spec get(String.t()) :: {:ok, ExceptionRecord.t()} | {:error, :not_found}
24 7 def get(id), do: GenServer.call(__MODULE__, {:get, id})
25
26 @spec update(ExceptionRecord.t()) :: :ok | {:error, :not_found}
27 4 def update(%ExceptionRecord{} = exc), do: GenServer.call(__MODULE__, {:update, exc})
28
29 @spec list_by_batch(String.t()) :: [ExceptionRecord.t()]
30 2 def list_by_batch(batch_id), do: GenServer.call(__MODULE__, {:list_by_batch, batch_id})
31
32 @spec list_by_status(atom()) :: [ExceptionRecord.t()]
33 2 def list_by_status(status), do: GenServer.call(__MODULE__, {:list_by_status, status})
34
35 @spec list_all() :: [ExceptionRecord.t()]
36 1 def list_all, do: GenServer.call(__MODULE__, :list_all)
37
38 16 def reset, do: GenServer.call(__MODULE__, :reset)
39
40 @impl true
41 def init(_opts) do
42
:-(
:ets.new(@table, [:set, :protected, :named_table])
43
:-(
:ets.new(@batch_idx, [:bag, :protected, :named_table])
44
:-(
:ets.new(@status_idx, [:bag, :protected, :named_table])
45 {:ok, %{}}
46 end
47
48 @impl true
49 def handle_call({:store, exc}, _from, state) do
50 10 :ets.insert(@table, {exc.exception_id, exc})
51 10 :ets.insert(@batch_idx, {exc.batch_id, exc.exception_id})
52 10 :ets.insert(@status_idx, {exc.status, exc.exception_id})
53 10 {:reply, :ok, state}
54 end
55
56 @impl true
57 def handle_call({:get, id}, _from, state) do
58 7 result = case :ets.lookup(@table, id) do
59 6 [{_, e}] -> {:ok, e}
60 1 [] -> {:error, :not_found}
61 end
62 7 {:reply, result, state}
63 end
64
65 @impl true
66 def handle_call({:update, exc}, _from, state) do
67 4 result = case :ets.lookup(@table, exc.exception_id) do
68 [{_, old}] ->
69 4 :ets.delete_object(@status_idx, {old.status, old.exception_id})
70 4 :ets.insert(@table, {exc.exception_id, exc})
71 4 :ets.insert(@status_idx, {exc.status, exc.exception_id})
72 :ok
73
:-(
[] -> {:error, :not_found}
74 end
75 4 {:reply, result, state}
76 end
77
78 @impl true
79 def handle_call({:list_by_batch, batch_id}, _from, state) do
80 2 ids = :ets.lookup(@batch_idx, batch_id) |> Enum.map(fn {_, id} -> id end)
81 2 {:reply, fetch_many(ids), state}
82 end
83
84 @impl true
85 def handle_call({:list_by_status, status}, _from, state) do
86 2 ids = :ets.lookup(@status_idx, status) |> Enum.map(fn {_, id} -> id end)
87 2 {:reply, fetch_many(ids), state}
88 end
89
90 @impl true
91 def handle_call(:list_all, _from, state) do
92 1 {:reply, :ets.tab2list(@table) |> Enum.map(fn {_, e} -> e end), state}
93 end
94
95 @impl true
96 def handle_call(:reset, _from, state) do
97 16 :ets.delete_all_objects(@table)
98 16 :ets.delete_all_objects(@batch_idx)
99 16 :ets.delete_all_objects(@status_idx)
100 16 {:reply, :ok, state}
101 end
102
103 defp fetch_many(ids) do
104 4 Enum.flat_map(ids, fn id ->
105 4 case :ets.lookup(@table, id) do
106 4 [{_, e}] -> [e]
107
:-(
[] -> []
108 end
109 end)
110 end
111 end
Line Hits Source