A Phoenix Channels specification library for automatic data validation and schema generation inspired by OpenAPI and built on top of ChannelHandler.
You can install ChannelSpec from git, and ChannelHandler from Hex:
def deps do
[
{:channel_handler, "~> 0.6"},
{:channel_spec, github: "felt/channel_spec"}
]
endFirst, you need to define the Phoenix Socket module by using ChannelSpec.Socket:
defmodule MyAppWeb.UserSocket do
use ChannelSpec.Socket
channel "room:*", MyAppWeb.RoomChannel
endThen, you must define the channel module. For this, you have to use three modules:
Phoenix.Channelfor basic Channel functionalityChannelHandler.Routerto define the event routingChannelSpec.Operationsto define operation schemas
defmodule MyAppWeb.RoomChannel do
use Phoenix.Channel
use ChannelHandler.Router
use ChannelSpec.Operations
join fn _topic, _payload, socket ->
{:ok, socket}
end
operation "new_msg",
payload: %{
type: :object,
properties: %{text: %{type: :string}}
},
replies: %{
ok: %{type: :string},
error: %{type: :string}
}
handle "new_msg", fn %{"text" => text}, _context, socket ->
{:reply, {:ok, text}, socket}
end
endThis will tell ChannelSpec that the server is capable of receiving a "new_msg" event,
with a map with a key text of type string and that it will reply with a string both
in case of success and error.
By using ChannelSpec, the following features will be available:
- A schema file can be automatically generated by passing the
:schema_pathoption touse ChannelSpec.Socket - Using
plug ChannelSpec.Plugs.ValidateInputwill allow you to validate incoming payloads against your operation schemas mix channelspec.routes MyAppWeb.Endpointto list all available events and their handlers, as defined withChannelHandler.Router. Passing the--verboseflag will also include file:line information about the files where the operation and the handler function are defined.- Testing that reply values conform to spec with
ChannelSpec.Testing.assert_reply_spec
If you add plug ChannelSpec.Plugs.ValidateInput to your channel or handler modules, the incoming message
payloads will be validated against your schemas. In case of a validation error, an error will immediately
be returned to the client.
You can configure the socket module to generate a schema file, that can be used to generate bindings for client code or documentation:
defmodule MyAppWeb.UserSocket do
use ChannelSpec.Socket, schema_path: "priv/schema.json"
endYou can use ChannelSpec's enhanced test helpers to verify the channel replies conform to the specified schemas:
defmodule MyAppWeb.RoomChannelTest do
use ExUnit.Case, async: true
use ChannelSpec.Testing
setup do
{:ok, _, socket} =
MyAppWeb.UserSocket
|> socket("123", %{})
|> subscribe_and_join(MyAppWeb.RoomChannel, "room:123")
%{socket: socket}
end
test "returns a valid reply", %{socket: socket} do
# Send a number body instead of a string
ref = push(socket, "new_msg", %{body: 123}
assert_reply_spec ref, :ok, reply # Will raise a validation error!
assert is_binary(reply)
end
endYou can use the generate schema file to generate client bindings. For Typescript bindings, you can use the companion channel_spec_tscodegen tool.