Files
wanderer/lib/wanderer_app/gen_impl.ex
Dmitry Popov 4136aaad76 Initial commit
2024-09-18 01:55:30 +04:00

48 lines
1.7 KiB
Elixir

defmodule WandererApp.GenImpl do
@moduledoc "Simple utility for avoiding boilerplate in a GenServer implementation."
@doc """
If your GenServer is a thin wrapper around a struct, you can make its handle_call/3 and/or
handle_cast/2 implementations be "just this."
Use it like this:
GenImpl.apply_call(&MyGenServer.Impl.update/3, %MyGenServer.Impl{}, [arg2, arg3])
This will result in a call that looks like:
MyGenServer.Impl.update(%MyGenServer.Impl{}, arg2, arg3)
Supports operations that:
- Update the state struct
- Return a result tuple, or even just :error
- Query the state and return a value
...but not operations that both modify the state *and* query something.
"""
def apply_call(impl_function, state, additional_args)
when is_function(impl_function) and is_struct(state) and is_list(additional_args) do
result = apply(impl_function, [state | additional_args])
handle_call_result(state, result)
end
def apply_call(impl_function_name, %struct_module{} = state, additional_args)
when is_atom(impl_function_name) and is_list(additional_args) do
result = apply(struct_module, impl_function_name, [state | additional_args])
handle_call_result(state, result)
end
defp handle_call_result(%struct_type{}, %struct_type{} = updated_state),
do: {:reply, :ok, updated_state}
defp handle_call_result(%struct_type{}, {:ok, %struct_type{} = updated_state}),
do: {:reply, :ok, updated_state}
defp handle_call_result(%struct_type{}, {result, %struct_type{} = updated_state}),
do: {:reply, result, updated_state}
defp handle_call_result(%_struct_type{} = state, :error), do: {:reply, :error, state}
defp handle_call_result(%_struct_type{} = state, result), do: {:reply, result, state}
end