Macaw.Data.Derived.Functions (macaw_data v0.5.3)

Copy Markdown View Source

A host's derived-source functions (spec/02 §7), written as Elixir in the host's own module and tested with ExUnit:

defmodule MyApp.Sources do
  use Macaw.Data.Derived.Functions

  defsource health(rows) do
    cpu = Enum.map(rows, & &1.cpu)
    %{state: if(List.last(cpu) > 90, do: :error, else: :ok)}
  end

  defsource bins(inputs, context) do
    %{"bounds" => [lo, hi]} = context["inputs"]["hosts"]["schema"]["cpu"]
    ...
  end
end

use Macaw.Data.Derived.Functions imports defsource/2 and compiles __derived__/0, each function's name, its arity and its source text, [{name, arity, source}] in definition order. A function of two arguments is called with its inputs and its context (spec/02 §2.3), one of one argument with its inputs alone. The text is captured when the module compiles, since a release strips the debug information a later reading would need.

A host's function is trusted (the sandbox's checker does not read it), but it runs through the same bounded runner as a stored one (Macaw.Data.Derived.Sandbox.run/3), so a runaway ends as a fault, not as its owner's crash. A derived source names it with function: :health; the host passes the module to the registry explicitly, and nothing else registers one: no application environment is read.

Ported from visualize's Visualize.VirtualSources.

Examples

iex> defmodule Doc.Sources do
...>   use Macaw.Data.Derived.Functions
...>   defsource doubled(rows), do: Enum.map(rows, &(&1 * 2))
...> end
iex> Doc.Sources.doubled([1, 2])
[2, 4]
iex> Doc.Sources.__derived__()
[{:doubled, 1, "fn rows -> Enum.map(rows, &(&1 * 2)) end"}]
iex> Macaw.Data.Derived.Functions.registry([Doc.Sources])
%{"doubled" => {Doc.Sources, 1}}

Summary

Types

Each host function's name, its string, by the module that defines it and its arity.

Functions

A host's derived-source function: defsource name(inputs) do … end is a public def of one argument, and defsource name(inputs, context) do … end one of two, which is called with its context (spec/02 §2.3); either is recorded with its arity and source text under the module's __derived__/0. A function of several clauses is defined clause by clause, as any function is, and listed once with every clause.

The registry of modules (spec/02 §7): each function's name, its string, by the module that defines it and its arity. Default closed: only the modules given are read. Raises ArgumentError for a module without __derived__/0 and for a name two modules define.

Types

registry()

@type registry() :: %{required(String.t()) => {module(), 1 | 2}}

Each host function's name, its string, by the module that defines it and its arity.

Functions

defsource(head, body)

(macro)

A host's derived-source function: defsource name(inputs) do … end is a public def of one argument, and defsource name(inputs, context) do … end one of two, which is called with its context (spec/02 §2.3); either is recorded with its arity and source text under the module's __derived__/0. A function of several clauses is defined clause by clause, as any function is, and listed once with every clause.

registry(modules)

@spec registry([module()]) :: registry()

The registry of modules (spec/02 §7): each function's name, its string, by the module that defines it and its arity. Default closed: only the modules given are read. Raises ArgumentError for a module without __derived__/0 and for a name two modules define.