# `ProtoChannel`

A typed Protobuf layer over `Phoenix.Channel`.

A `use ProtoChannel` channel declares its event ⇄ Protobuf-message pairs at
compile time and exchanges typed structs with handlers instead of raw
`{:binary, bytes}` payloads. Pattern-matching the structs at the boundary
gives compile-time field-name safety, and the `c:handle_proto/3` spec gives
dialyzer full value-type checking.

## Example

    defmodule MyAppWeb.MyChannel do
      use ProtoChannel

      alias MyApp.{Request, Response, Notice}

      proto_message "ping", request: Request, reply: Response
      proto_push "notice", Notice
      proto_broadcast "notice", Notice

      @impl Phoenix.Channel
      def join("room:" <> _, _payload, socket), do: {:ok, socket}

      @impl ProtoChannel
      def handle_proto("ping", %Request{} = req, socket) do
        push(socket, "notice", %Notice{text: req.text})
        broadcast(socket, "notice", %Notice{text: req.text})
        {:reply, {:ok, %Response{text: req.text}}, socket}
      end
    end

## What the macros generate

  * `proto_message/2` — one `handle_in/3` clause per declared event that
    decodes the inbound bytes into the request struct, dispatches to
    `c:handle_proto/3`, and encodes the reply struct back to bytes.
  * `proto_push/2` and `proto_broadcast/2` — typed wrappers around
    `Phoenix.Channel.push/3`, `broadcast/3`, `broadcast!/3`,
    `broadcast_from/3`, and `broadcast_from!/3`. Each declared event accepts
    only its declared struct; anything else is a function-clause mismatch at
    the call site.

The unqualified `push/3`, `broadcast/3`, ... names inside your channel
resolve to the generated wrappers — the macro imports `Phoenix.Channel` with
those five names excluded. To bypass the wrappers, call
`Phoenix.Channel.push/3` etc. directly.

## Compile-time validation

  * Duplicate event names within the same macro family (`proto_message`,
    `proto_push`, or `proto_broadcast`) raise `ArgumentError` at compile
    time.
  * Every referenced module must `use Protobuf` — the macro checks for
    `__message_props__/0` and raises if missing, so typos and stray plain
    structs are caught up-front.

## Wire format

The macro only produces `{:binary, bytes}` payloads. To frame those over
the socket as protobuf, pair this with `ProtoChannel.Serializer`.

# `handle_proto_result`

```elixir
@type handle_proto_result() ::
  {:reply, reply(), Phoenix.Socket.t()}
  | {:noreply, Phoenix.Socket.t()}
  | {:noreply, Phoenix.Socket.t(), timeout() | :hibernate}
  | {:stop, reason :: term(), Phoenix.Socket.t()}
  | {:stop, reason :: term(), reply(), Phoenix.Socket.t()}
```

Return shapes accepted by `c:handle_proto/3`.

The reply form mirrors `c:Phoenix.Channel.handle_in/3`, but the reply
struct is encoded to `{:binary, bytes}` by the macro before being handed
back to Phoenix. `:noreply` and bare `:stop` variants pass through
unchanged.

# `reply`

```elixir
@type reply() :: {:ok | :error, struct()}
```

A reply payload from `c:handle_proto/3`: the status atom plus a Protobuf
struct that the macro will encode to bytes.

# `handle_proto`
*optional* 

```elixir
@callback handle_proto(
  event :: String.t(),
  request :: struct(),
  socket :: Phoenix.Socket.t()
) ::
  handle_proto_result()
```

Handles a decoded request struct for a `proto_message/2`-declared event.

Invoked from the generated `handle_in/3` clause after the inbound bytes
have been decoded into the declared request struct. The returned reply
struct (if any) is encoded back to bytes before being handed to Phoenix.

See `t:handle_proto_result/0` for the supported return shapes.

# `proto_broadcast`
*macro* 

Generates typed wrappers for an outbound broadcast event.

Generates clauses for `broadcast/3`, `broadcast!/3`, `broadcast_from/3`, and
`broadcast_from!/3`. Each encodes the struct and forwards to the matching
`Phoenix.Channel` function as `{:binary, bytes}`.

## Example

    proto_broadcast "notice", MyApp.Notice
    # ...
    broadcast(socket, "notice", %MyApp.Notice{text: "hi"})

# `proto_message`
*macro* 

Declares an RPC-style inbound event.

The channel receives `event` as a `{:binary, bytes}` payload, decodes the
bytes into a `request` struct, dispatches to `c:handle_proto/3`, and encodes
the returned reply struct back to bytes.

## Options

  * `:request` — module returned by `use Protobuf`; the request struct type.
  * `:reply` — module returned by `use Protobuf`; the reply struct type.

Both keys are required. Each module is resolved and checked for
`__message_props__/0` at compile time.

## Example

    proto_message "ping", request: MyApp.Ping, reply: MyApp.Pong

# `proto_push`
*macro* 

Generates a typed `push/3` wrapper for an outbound event.

Inside the channel, `push(socket, event, %module{} = msg)` encodes `msg` and
forwards to `Phoenix.Channel.push/3` as `{:binary, bytes}`. Other event
names still resolve to the unwrapped `Phoenix.Channel.push/3`.

## Example

    proto_push "notice", MyApp.Notice
    # ...
    push(socket, "notice", %MyApp.Notice{text: "hi"})

---

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