ProtoChannel behaviour (ProtoChannel v0.1.0)
Copy MarkdownA 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 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
endWhat the macros generate
proto_message/2— onehandle_in/3clause per declared event that decodes the inbound bytes into the request struct, dispatches tohandle_proto/3, and encodes the reply struct back to bytes.proto_push/2andproto_broadcast/2— typed wrappers aroundPhoenix.Channel.push/3,broadcast/3,broadcast!/3,broadcast_from/3, andbroadcast_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, orproto_broadcast) raiseArgumentErrorat compile time. - Every referenced module must
use Protobuf— the macro checks for__message_props__/0and 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.
Summary
Types
Return shapes accepted by handle_proto/3.
A reply payload from handle_proto/3: the status atom plus a Protobuf
struct that the macro will encode to bytes.
Callbacks
Handles a decoded request struct for a proto_message/2-declared event.
Functions
Generates typed wrappers for an outbound broadcast event.
Declares an RPC-style inbound event.
Generates a typed push/3 wrapper for an outbound event.
Types
@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 handle_proto/3.
The reply form mirrors 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.
@type reply() :: {:ok | :error, struct()}
A reply payload from handle_proto/3: the status atom plus a Protobuf
struct that the macro will encode to bytes.
Callbacks
@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 handle_proto_result/0 for the supported return shapes.
Functions
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"})
Declares an RPC-style inbound event.
The channel receives event as a {:binary, bytes} payload, decodes the
bytes into a request struct, dispatches to handle_proto/3, and encodes
the returned reply struct back to bytes.
Options
:request— module returned byuse Protobuf; the request struct type.:reply— module returned byuse 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
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"})