Jev.Server behaviour (Jev v0.2.0)

Copy Markdown View Source

A GenServer that talks to Jev by replying.

Your module implements init/1, the usual handle_call/3, handle_cast/2, handle_info/2, handle_continue/2, and one new callback, handle_answer/3. From any callback, return

{:reply, {tag, state, questions}, your_state}

and the request is posted to Jev under Jev.TaskSupervisor without blocking the server. When Jev answers,

handle_answer(reply | {:error, reason}, tag, your_state)

is called. tag is any term and plays the role from plays in handle_call/3; passing from itself is the common case, and callers of handle_call/3 are then answered with GenServer.reply/2.

A server can have any number of requests in flight. Tags tell them apart. A crashed request arrives as {:error, reason} instead of taking the server down. Clause order in handle_answer/3 is the routing:

defmodule Triage do
  use Jev.Server

  def init(_), do: {:ok, %{}}

  def handle_call({:labels, issue}, from, s) do
    {:reply, {from, issue,
       kind: {"What kind of issue?", %{bug: nil, feature: nil, other: nil}},
       security: "Is this a vulnerability?"}, s}
  end

  def handle_answer(%{security: p}, from, s) when p > 0.5, do: done(from, [:security], s)
  def handle_answer(%{kind: k, confidence: %{kind: c}}, from, s) when c > 0.85, do: done(from, [k], s)
  def handle_answer(%{kind: k}, from, s), do: done(from, [k, :"needs-triage"], s)
  def handle_answer({:error, reason}, from, s), do: done(from, {:error, reason}, s)

  defp done(from, result, s) do
    GenServer.reply(from, result)
    {:noreply, s}
  end
end

Per-request options such as model: go in a fourth element, which means the questions need their own brackets:

{:reply, {tag, state, [kind: {"Which?", %{a: nil, b: nil}}], [model: "jev-preview"]}, s}

endpoint: picks a named server from config :jev, endpoints:, for a self-hosted model that speaks the same wire format; see Jev.HTTP. backend: picks a Jev.Backend module instead of Jev.HTTP altogether, for an in-process model or a fake; config :jev, backend: sets the default.

Built the way GenStage and Agent are built: this module owns the real GenServer callbacks and delegates to yours, so :sys.get_state/1, Observer, and every GenServer option keep working. init/1 may return a timeout or {:continue, term} as usual, and terminate/2 and code_change/3 are delegated when defined. As with any GenServer, terminate/2 runs on a supervisor shutdown only if the process traps exits.

Summary

Types

What every callback returns.

Functions

Adopts the behaviour and defines child_spec/1.

Returns a specification to start this module under a supervisor.

Types

reply()

@type reply() ::
  {:reply, {tag :: term(), Jev.entry(), keyword() | map()}, state :: term()}
  | {:reply,
     {tag :: term(), Jev.entry(), keyword() | map(),
      [Jev.HTTP.option() | {:backend, module()}]}, state :: term()}
  | {:noreply, state :: term()}
  | {:noreply, state :: term(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), state :: term()}

What every callback returns.

Callbacks

code_change(old_vsn, state, extra)

(optional)
@callback code_change(
  old_vsn :: term() | {:down, term()},
  state :: term(),
  extra :: term()
) ::
  {:ok, state :: term()} | {:error, term()}

handle_answer(arg1, tag, state)

@callback handle_answer(Jev.reply() | {:error, term()}, tag :: term(), state :: term()) ::
  reply()

handle_call(request, from, state)

(optional)
@callback handle_call(request :: term(), GenServer.from(), state :: term()) :: reply()

handle_cast(request, state)

(optional)
@callback handle_cast(request :: term(), state :: term()) :: reply()

handle_continue(continue, state)

(optional)
@callback handle_continue(continue :: term(), state :: term()) :: reply()

handle_info(message, state)

(optional)
@callback handle_info(message :: term(), state :: term()) :: reply()

init(arg)

@callback init(arg :: term()) ::
  {:ok, state :: term()}
  | {:ok, state :: term(), timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term()}
  | :ignore

terminate(reason, state)

(optional)
@callback terminate(reason :: term(), state :: term()) :: term()

Functions

__using__(opts)

(macro)

Adopts the behaviour and defines child_spec/1.

Options are child spec overrides, as with use GenServer:

use Jev.Server, restart: :temporary, shutdown: 10_000

Default handle_call/3, handle_cast/2, and handle_info/2 clauses behave like GenServer's: an unexpected call or cast stops the server with a clear error, and an unexpected message is logged and ignored.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(module, arg, opts \\ [])

@spec start_link(module(), term(), GenServer.options()) :: GenServer.on_start()

Starts module as a Jev.Server. opts are GenServer.start_link/3 options.