Elixir MOM v0.5.3 MOM.RPC.MethodCaller
This module stores methods to be called later.
They are stored in an execute efficient way, and allow to have a list
of methods to allow introspection (dir
method).
Can be connected with a RPC gateway with add_method_caller
Example
iex> alias MOM.RPC.MethodCaller
iex> {:ok, mc} = MethodCaller.start_link()
iex> MethodCaller.add_method mc, "ping", fn _ -> "pong" end, async: false
iex> MethodCaller.call mc, "ping", [], nil
{:ok, "pong"}
iex> MethodCaller.call mc, "dir", [], nil
{:ok, ["dir", "ping"]}
Summary
Functions
Return list of funtions known by this method caller
Adds a guard to the method caller
Adds a method to be called later
Method callers can be chained, so that if current does not resolve, try on another
Calls a method by name
Performs the real call to a function, checking guards, with the given params and so on
Shows debug info about this method caller
Functions
Adds a guard to the method caller
This guards are called to ensure that a called method is allowed to be called.
If the method call is not allowed, it will be skipped as if never added, and
dir
will not return it neither.
Guards are functions that receive the RPC message and the options of the method, and return true or false to mark if they allow or not that method call. This way generic guards can be created.
name
is a debug name used to log what guard failed. Any error/exception on
guards are interpreted as denial. Clause errors are not logged to ease
creation of guards for specific pattern matches. Other errors are reraised.
The very same dir
that would be called with a method caller can have guards
that prevent its call, but the dir implementation has to make sure to return
only the approved methods.
Example
It creates a method and a guard.
iex> require Logger
iex> {:ok, mc} = start_link()
iex> add_method mc, "echo", &(&1), require_perm: "echo"
iex> add_method_caller mc, fn
...> %{ method: "dir" } -> {:ok, ["echo_fn"]}
...> %{ method: "echo_fn", params: params } -> {:ok, params}
...> _ -> {:error, :unknown_method }
...> end, require_perm: "echo"
iex> add_guard mc, "perms", fn %{ context: context }, options ->
...> case Keyword.get(options, :require_perm) do
...> nil -> true # no require perms, ok
...> required_perm ->
...> Enum.member? Map.get(context, :perms, []), required_perm
...> end
...> end
iex> call mc, "echo", [1,2,3], %{ } # no context
{:error, :unknown_method}
iex> call mc, "echo", [1,2,3], %{ perms: [] } # no perms
{:error, :unknown_method}
iex> call mc, "echo", [1,2,3], %{ perms: ["echo"] } # no perms
{:ok, [1,2,3]}
iex> call mc, "echo_fn", [1,2,3], %{ } # no context
{:error, :unknown_method}
iex> call mc, "echo_fn", [1,2,3], %{ perms: [] } # no perms
{:error, :unknown_method}
iex> call mc, "echo_fn", [1,2,3], %{ perms: ["echo"] } # no perms
{:ok, [1,2,3]}
iex> call mc, "dir", [], %{}
{:ok, ["dir"]}
iex> call mc, "dir", [], %{ perms: ["echo"] }
{:ok, ["dir", "echo", "echo_fn"]}
In this example a map is used as context. Normally it would be a RPC.Context.
Adds a method to be called later.
Method function must returns:
- {:ok, v} — Ok value
- {:error, v} — Error to return to client
- v — Ok value
Options may be:
async
, default true. Execute in another task, returns a promise.context
, the called function will be called with the client context. It is a MOM.RPC.Context
Example
iex> alias MOM.RPC.MethodCaller
iex> {:ok, mc} = MethodCaller.start_link
iex> MethodCaller.add_method mc, "test_ok", fn _ -> {:ok, :response_ok} end
iex> MethodCaller.add_method mc, "test_error", fn _ -> {:error, :response_error} end
iex> MethodCaller.add_method mc, "test_plain", fn _ -> :response_plain_ok end
iex> MethodCaller.call mc, "test_ok", [], nil
{:ok, :response_ok}
iex> MethodCaller.call mc, "test_error", [], nil
{:error, :response_error}
iex> MethodCaller.call mc, "test_plain", [], nil
{:ok, :response_plain_ok}
Method callers can be chained, so that if current does not resolve, try on another
This another caller can be even shared between several method callers.
Method callers are optimized callers, but a single function can be passed and it will be called with an %RPC.Message. If it returns {:ok, res} or {:error, error} its processed, :empty or :nok tries next callers.
Example:
I will create three method callers, so that a calls, and b too. Method can be shadowed at parent too.
iex> alias MOM.RPC.{MethodCaller, Context}
iex> {:ok, a} = MethodCaller.start_link
iex> {:ok, b} = MethodCaller.start_link
iex> {:ok, c} = MethodCaller.start_link
iex> MethodCaller.add_method a, "a", fn _ -> :a end
iex> MethodCaller.add_method b, "b", fn _ -> :b end
iex> MethodCaller.add_method c, "c", fn _ -> :c end
iex> MethodCaller.add_method c, "c_", fn _, context -> {:c, Context.get(context, :user, nil)} end, context: true
iex> MethodCaller.add_method_caller a, c
iex> MethodCaller.add_method_caller b, c
iex> {:ok, context} = Context.start_link
iex> Context.set context, :user, :me
iex> MethodCaller.call a, "c", [], context
{:ok, :c}
iex> MethodCaller.call a, "c_", [], context
{:ok, {:c, :me}}
iex> MethodCaller.call b, "c", [], context
{:ok, :c}
iex> MethodCaller.call b, "c_", [], context
{:ok, {:c, :me}}
iex> MethodCaller.add_method a, "c", fn _ -> :shadow end
iex> MethodCaller.call a, "c", [], context
{:ok, :shadow}
iex> MethodCaller.call b, "c", [], context
{:ok, :c}
iex> MethodCaller.call b, "d", [], context
{:error, :unknown_method}
Custom method caller that calls a function
iex> alias MOM.RPC.MethodCaller
iex> {:ok, mc} = MethodCaller.start_link
iex> MethodCaller.add_method_caller(mc, fn msg ->
...> case msg.method do
...> "hello."<>ret -> {:ok, ret}
...> _ -> :nok
...> end
...> end)
iex> MethodCaller.call mc, "hello.world", [], nil
{:ok, "world"}
iex> MethodCaller.call mc, "world.hello", [], nil
{:error, :unknown_method}
Calls a method by name.
Waits for execution always. Independent of async.
Returns one of:
- {:ok, v}
- {:error, e}
Performs the real call to a function, checking guards, with the given params and so on.