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

__dir(pid, context)

Return list of funtions known by this method caller

add_guard(pid, name, guard_f)

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.

add_method(pid, name, f, options \\ [])

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}
add_method_caller(pid, nmc)
add_method_caller(pid, pid, options)

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}
call(pid, method, params, context)

Calls a method by name.

Waits for execution always. Independent of async.

Returns one of:

  • {:ok, v}
  • {:error, e}
call_function_real(f, options, method, params, context, status)

Performs the real call to a function, checking guards, with the given params and so on.

call_method_callers(mcs, method, params, context, status)
debug(pid)

Shows debug info about this method caller

start_link(options \\ [])
stop(pid, reason \\ :normal)