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
endPer-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
Functions
Adopts the behaviour and defines child_spec/1.
Returns a specification to start this module under a supervisor.
Starts module as a Jev.Server. opts are GenServer.start_link/3 options.
Types
@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
Functions
Adopts the behaviour and defines child_spec/1.
Options are child spec overrides, as with use GenServer:
use Jev.Server, restart: :temporary, shutdown: 10_000The child spec starts the server through the module's own start_link/1
when it defines one, as use GenServer does, so a name: or other option
given there holds under a supervisor. A module without start_link/1 is
started through Jev.Server.start_link/2 directly.
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.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec start_link(module(), term(), GenServer.options()) :: GenServer.on_start()
Starts module as a Jev.Server. opts are GenServer.start_link/3 options.