cover/Elixir.WalletIntegrations.IntegrationExceptionStore.html

1 defmodule WalletIntegrations.IntegrationExceptionStore do
2 @moduledoc """
3 ETS-backed GenServer for IntegrationException persistence.
4
5 Tables:
6 - `:wallet_integrations_exceptions` — keyed by exception_id.
7 - `:wallet_integrations_exc_transfer_idx` — bag: {transfer_id, exception_id}.
8 - `:wallet_integrations_exc_prov_ref_idx` — bag: {provider_reference, exception_id}.
9 - `:wallet_integrations_exc_status_idx` — bag: {status, exception_id}.
10 """
11
12 use GenServer
13
14 alias WalletIntegrations.IntegrationException
15
16 @table :wallet_integrations_exceptions
17 @xfer_idx :wallet_integrations_exc_transfer_idx
18 @prov_idx :wallet_integrations_exc_prov_ref_idx
19 @status_idx :wallet_integrations_exc_status_idx
20
21
:-(
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
22
23 @spec store(IntegrationException.t()) :: :ok | {:error, :duplicate_exception_id}
24 16 def store(%IntegrationException{} = ex), do: GenServer.call(__MODULE__, {:store, ex})
25
26 @spec get(exception_id :: String.t()) :: {:ok, IntegrationException.t()} | {:error, :not_found}
27 4 def get(id), do: GenServer.call(__MODULE__, {:get, id})
28
29 @spec update(IntegrationException.t()) :: :ok | {:error, :not_found}
30 4 def update(%IntegrationException{} = ex), do: GenServer.call(__MODULE__, {:update, ex})
31
32 @spec list_by_status(status :: atom()) :: [IntegrationException.t()]
33 3 def list_by_status(status), do: GenServer.call(__MODULE__, {:list_by_status, status})
34
35 @spec list_by_transfer(transfer_id :: String.t()) :: [IntegrationException.t()]
36 4 def list_by_transfer(transfer_id), do: GenServer.call(__MODULE__, {:list_by_transfer, transfer_id})
37
38 @spec list_by_provider_ref(provider_ref :: String.t()) :: [IntegrationException.t()]
39 1 def list_by_provider_ref(ref), do: GenServer.call(__MODULE__, {:list_by_prov_ref, ref})
40
41 @spec list_open() :: [IntegrationException.t()]
42 2 def list_open, do: list_by_status(:open)
43
44 @spec reset() :: :ok
45 68 def reset, do: GenServer.call(__MODULE__, :reset)
46
47 @impl true
48 def init(_opts) do
49
:-(
:ets.new(@table, [:set, :protected, :named_table])
50
:-(
:ets.new(@xfer_idx, [:bag, :protected, :named_table])
51
:-(
:ets.new(@prov_idx, [:bag, :protected, :named_table])
52
:-(
:ets.new(@status_idx, [:bag, :protected, :named_table])
53 {:ok, %{}}
54 end
55
56 @impl true
57 def handle_call({:store, ex}, _from, state) do
58 16 if :ets.member(@table, ex.exception_id) do
59 1 {:reply, {:error, :duplicate_exception_id}, state}
60 else
61 15 :ets.insert(@table, {ex.exception_id, ex})
62 15 if ex.transfer_id, do: :ets.insert(@xfer_idx, {ex.transfer_id, ex.exception_id})
63 15 if ex.provider_reference, do: :ets.insert(@prov_idx, {ex.provider_reference, ex.exception_id})
64 15 :ets.insert(@status_idx, {ex.status, ex.exception_id})
65 15 {:reply, :ok, state}
66 end
67 end
68
69 @impl true
70 def handle_call({:get, id}, _from, state) do
71 4 result =
72 case :ets.lookup(@table, id) do
73 3 [{_, ex}] -> {:ok, ex}
74 1 [] -> {:error, :not_found}
75 end
76 4 {:reply, result, state}
77 end
78
79 @impl true
80 def handle_call({:update, ex}, _from, state) do
81 4 result =
82 4 case :ets.lookup(@table, ex.exception_id) do
83 [{_, old}] ->
84 # Update status index: remove old entry, add new
85 4 :ets.delete_object(@status_idx, {old.status, old.exception_id})
86 4 :ets.insert(@status_idx, {ex.status, ex.exception_id})
87 4 :ets.insert(@table, {ex.exception_id, ex})
88 :ok
89
:-(
[] ->
90 {:error, :not_found}
91 end
92 4 {:reply, result, state}
93 end
94
95 @impl true
96 def handle_call({:list_by_status, status}, _from, state) do
97 3 ids = :ets.lookup(@status_idx, status) |> Enum.map(fn {_, id} -> id end)
98 3 items = fetch_many(ids)
99 3 {:reply, items, state}
100 end
101
102 @impl true
103 def handle_call({:list_by_transfer, transfer_id}, _from, state) do
104 4 ids = :ets.lookup(@xfer_idx, transfer_id) |> Enum.map(fn {_, id} -> id end)
105 4 {:reply, fetch_many(ids), state}
106 end
107
108 @impl true
109 def handle_call({:list_by_prov_ref, ref}, _from, state) do
110 1 ids = :ets.lookup(@prov_idx, ref) |> Enum.map(fn {_, id} -> id end)
111 1 {:reply, fetch_many(ids), state}
112 end
113
114 @impl true
115 def handle_call(:reset, _from, state) do
116 68 :ets.delete_all_objects(@table)
117 68 :ets.delete_all_objects(@xfer_idx)
118 68 :ets.delete_all_objects(@prov_idx)
119 68 :ets.delete_all_objects(@status_idx)
120 68 {:reply, :ok, state}
121 end
122
123 defp fetch_many(ids) do
124 8 Enum.flat_map(ids, fn id ->
125 9 case :ets.lookup(@table, id) do
126 9 [{_, ex}] -> [ex]
127
:-(
[] -> []
128 end
129 end)
130 end
131 end
Line Hits Source