# `Snodo.OTP.Contract`
[🔗](https://github.com/joshrotenberg/snodo/blob/v0.4.3/integrations/otp/lib/snodo/otp/contract.ex#L1)

One module describes a GenServer's remote-callable operations for both
sides. The server side (`Snodo.OTP.Server.expose/2`) turns it into
`Snodo.Tool.GenServer` declarations: JSON in, Elixir message, reply term,
JSON out. The client side (`Snodo.OTP.Proxy`) uses the inverse pair:
Elixir message in, JSON out over the wire, JSON reply back to a term.

Nothing is inferred. An operation exists because the contract names it,
with its schema, which keeps the boundary the GenServer tool experiment
settled on: a client never chooses a process, atom or term.

# `op`

```elixir
@type op() :: %{
  :name =&gt; String.t(),
  :kind =&gt; :call | :cast,
  :input_schema =&gt; map(),
  optional(:output_schema) =&gt; map(),
  optional(:description) =&gt; String.t(),
  optional(:timeout) =&gt; pos_integer()
}
```

# `build_message`

```elixir
@callback build_message(name :: String.t(), args :: map()) :: term()
```

Server side: validated JSON arguments to the GenServer message.

# `decode_reply`

```elixir
@callback decode_reply(name :: String.t(), json :: map()) :: term()
```

Client side: the JSON reply object back to the term the caller expects.

# `encode_call`

```elixir
@callback encode_call(message :: term()) :: {String.t(), map()} | :nomatch
```

Client side: a GenServer.call message to an operation and JSON arguments, or :nomatch.

# `encode_cast`

```elixir
@callback encode_cast(message :: term()) :: {String.t(), map()} | :nomatch
```

Client side: a GenServer.cast message to an operation and JSON arguments, or :nomatch.

# `encode_reply`

```elixir
@callback encode_reply(name :: String.t(), reply :: term()) :: map()
```

Server side: the GenServer reply to a JSON object.

# `ops`

```elixir
@callback ops() :: [op()]
```

The operations, in catalog order.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
