Elixir MOM v0.4.2 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

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

Calls the method and calls the callback continuation with the result

Functions

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}
cast(f, method, params, context, cb)

Calls the method and calls the callback continuation with the result.

If the method was async, it will be run in another task, if it was sync, its run right now.

If the method does not exists, returns :nok, if it does, returns :ok.

Callback is a function that can receive {:ok, value} or {:error, %Exception{…}}

Alternatively mc can be a function that receies a %RPC.Message and returns any of:

  • {:ok, ret}
  • {:error, error}
  • :nok
  • :empty

Possible errors:

  • :unknown_method
  • :bad_arity

Examples

iex> alias MOM.RPC.{Context, MethodCaller}
iex> {:ok, mc} = MethodCaller.start_link
iex> MethodCaller.add_method mc, "echo", fn [what], context -> "#{what}#{Context.get(context, :test, :fail)}" end, context: true
iex> {:ok, context} = Context.start_link
iex> Context.set context, :test, "ok"
iex> MethodCaller.call mc, "echo", ["test_"], context
{:ok, "test_ok"}
iex> alias MOM.RPC.{Context, MethodCaller}
iex> MethodCaller.cast(fn _ -> {:ok, :ok} end, "any", [], nil, fn
...>   {:ok, _v} -> :ok
...>   {:error, e} -> {:error, e}
...> end)
:ok
debug(pid)
start_link(options \\ [])
stop(pid, reason \\ :normal)