Skip to content

Commit

Permalink
use credo
Browse files Browse the repository at this point in the history
  • Loading branch information
slogsdon committed Feb 7, 2017
1 parent eecf9d8 commit 5678f5c
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
language: elixir
elixir:
- 1.2
- 1.3
- 1.4
script:
- mix test
- MIX_ENV=test mix credo --strict
after_success:
- MIX_ENV=test mix coveralls.travis
93 changes: 92 additions & 1 deletion lib/web_socket.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,91 @@
defmodule WebSocket do
@moduledoc """
An exploration into a stand-alone library for
Plug applications to easily adopt WebSockets.
## Integrating with Plug
If you're looking to try this in your own test
application, do something like this:
```elixir
defmodule MyApp.Router do
use Plug.Router
use WebSocket
# WebSocket routes
# route controller/handler function & name
socket "/topic", MyApp.TopicController, :handle
socket "/echo", MyApp.EchoController, :echo
# Rest of your router's plugs and routes
# ...
def run(opts \\ []) do
dispatch = dispatch_table(opts)
Plug.Adapters.Cowboy.http __MODULE__, opts, [dispatch: dispatch]
end
end
```
For the time being, there is a `run/1` function
generated for your router that starts a HTTP/WS
listener. Not sure if this will stay or get
reduced to helper functions that aid in the
creation of a similar function. Most likely the
latter will win out to help compose functionality.
The big part that it plays is the building of a
dispatch table to pass as an option to Cowboy that
has an entry for each of your socket routes and a
catch all for HTTP requests.
### Add the necessary bits to a module
From the topic example:
```elixir
defmodule MyApp.TopicController do
def handle(:init, state) do
{:ok, state}
end
def handle(:terminate, _state) do
:ok
end
def handle("topic:" <> letter, state, data) do
payload = %{awesome: "blah #{letter}",
orig: data}
{:reply, {:text, payload}, state}
end
end
```
Currently, the function name needs to be unique
across all controllers/handlers as its used for
the Events layer.
### Broadcast from elsewhere
Need to send data out from elsewhere in your app?
```elixir
# Build your message
topic = "my_event"
data = %{foo: "awesome"}
mes = WebSocket.Message.build(topic, data)
json = Poison.encode!(mes)
# Pick your destination (from your routes)
name = :handle
# Send away!
WebSockets.broadcast!(name, json)
```
This needs to be nicer, but this is still in
progress.
"""
@type route :: {atom | binary, Module.t, {Module.t, Keyword.t}}

defmacro __using__(_) do
quote do
import unquote(__MODULE__)
Expand All @@ -23,15 +110,19 @@ defmodule WebSocket do
plug = env.module
routes = Module.get_attribute(env.module, :ws_routes)
quote do
@spec dispatch_table(Keyword.t) :: [WebSocket.route]
def dispatch_table(opts \\ []) do
opts = unquote(plug).init(opts)
build_dispatch(unquote(plug), unquote(routes), opts)
end
end
end

@spec build_dispatch(Module.t,
[{binary, Module.t, atom}],
Keyword.t) :: [{:_, [route]}]
def build_dispatch(plug, routes \\ [], opts \\ []) do
default = [ {:_, Plug.Adapters.Cowboy.Handler, {plug, opts}} ]
default = [{:_, Plug.Adapters.Cowboy.Handler, {plug, opts}}]
routes = routes
|> Enum.reverse
|> Enum.reduce(default, fn {route, mod, func}, acc ->
Expand Down
10 changes: 8 additions & 2 deletions lib/web_socket/cowboy/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ defmodule WebSocket.Cowboy.Handler do

@doc """
"""
@spec init(atom, :cowboy_req.req, {atom, atom}) :: {:upgrade, :protocol, :cowboy_websocket}
@spec init(atom,
:cowboy_req.req,
{atom, atom}) :: {:upgrade,
:protocol,
:cowboy_websocket}
def init(_transport, _req, {_plug, action}) do
{:ok, _pid} = Events.start_link(action)
{:upgrade, :protocol, :cowboy_websocket}
Expand All @@ -39,7 +43,9 @@ defmodule WebSocket.Cowboy.Handler do
"""
@spec websocket_init(atom, :cowboy_req.req, {atom, atom}) :: reply
def websocket_init(transport, req, opts) do
state = @connection.conn(req, transport)
state =
req
|> @connection.conn(transport)
|> build_state(opts)
Events.subscribe(state.action, self)
args = get_args(:init, state)
Expand Down
4 changes: 3 additions & 1 deletion lib/web_socket/events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ defmodule WebSocket.Events do

def handle_event({:send, event, originator}, clients) do
spawn fn ->
clients |> Enum.map(&(maybe_send(&1, originator, event)))
_ =
clients
|> Enum.map(&(maybe_send(&1, originator, event)))
end
{:ok, clients}
end
Expand Down
9 changes: 9 additions & 0 deletions lib/web_socket/message.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
defmodule WebSocket.Message do
@moduledoc """
Wrapper for defining WebSocket messages
"""

defstruct event: nil, data: nil
@type t :: %__MODULE__{
event: binary,
data: Map.t
}

@spec build(binary, Map.t) :: t
def build(event, data) do
%__MODULE__{
event: event,
Expand Down
7 changes: 4 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ defmodule WebSocket.Mixfile do
name: "WebSocket",
source_url: "https://github.com/slogsdon/plug-web-socket",
homepage_url: "https://github.com/slogsdon/plug-web-socket",
deps: deps,
package: package,
description: description,
deps: deps(),
package: package(),
description: description(),
docs: [extras: ["README.md"],
main: "readme"],
test_coverage: [tool: ExCoveralls]]
Expand All @@ -26,6 +26,7 @@ defmodule WebSocket.Mixfile do
{:poison, "~> 3.0"},
{:earmark, "~> 1.0", only: :dev},
{:ex_doc, "~> 0.14", only: :dev},
{:credo, "~> 0.5", only: [:dev, :test]},
{:excoveralls, "~> 0.5", only: :test},
{:dialyze, "~> 0.2", only: :test}]
end
Expand Down
8 changes: 5 additions & 3 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
%{"certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
%{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
"certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
"credo": {:hex, :credo, "0.6.1", "a941e2591bd2bd2055dc92b810c174650b40b8290459c89a835af9d59ac4a5f8", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]},
"dialyze": {:hex, :dialyze, "0.2.1", "9fb71767f96649020d769db7cbd7290059daff23707d6e851e206b1fdfa92f9d", [:mix], []},
"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.14.2", "c89d60db464e8a0849a35dbcd6eed71f2b076c339d0b05b3bb5c90d6bab31e4f", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
Expand All @@ -15,5 +17,5 @@
"plug": {:hex, :plug, "1.2.2", "cfbda521b54c92ab8ddffb173fbaabed8d8fc94bec07cd9bb58a84c1c501b0bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
"poison": {:hex, :poison, "3.0.0", "625ebd64d33ae2e65201c2c14d6c85c27cc8b68f2d0dd37828fde9c6920dd131", [:mix], []},
"ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:make, :rebar], []},
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}}
24 changes: 12 additions & 12 deletions test/web_socket/events_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,57 +21,57 @@ defmodule WebSocket.EventsTest do

test "subscribe/2" do
assert {:ok, _} = start_link(@ref)
assert subscribe(@ref, self) == :ok
assert subscribe(@ref, self()) == :ok
assert stop(@ref) == :ok
end

test "unsubscribe/2" do
assert {:ok, _} = start_link(@ref)
assert subscribe(@ref, self) == :ok
assert unsubscribe(@ref, self) == :ok
assert subscribe(@ref, self()) == :ok
assert unsubscribe(@ref, self()) == :ok
assert stop(@ref) == :ok
end

test "broadcast/3" do
assert {:ok, _} = start_link(@ref)
assert subscribe(@ref, self) == :ok
assert broadcast(@ref, {:text, "test"}, self) == :ok
assert subscribe(@ref, self()) == :ok
assert broadcast(@ref, {:text, "test"}, self()) == :ok
refute_receive {:text, "test"}
assert stop(@ref) == :ok
end

test "broadcast!/2" do
assert {:ok, _} = start_link(@ref)
assert subscribe(@ref, self) == :ok
assert subscribe(@ref, self()) == :ok
assert broadcast!(@ref, {:text, "test"}) == :ok
assert_receive {:text, "test"}
assert stop(@ref) == :ok
end

test "handle_event/2" do
state = []
calculated = handle_event({:add_client, self}, state)
calculated = handle_event({:add_client, self()}, state)

assert calculated |> elem(0) == :ok
assert calculated |> elem(1) |> is_list
assert calculated |> elem(1) == [self|[]]
assert calculated |> elem(1) == [self()|[]]

state = calculated |> elem(1)
calculated = handle_event({:send, {:text, "test"}, nil}, state)

assert calculated |> elem(0) == :ok
assert calculated |> elem(1) |> is_list
assert calculated |> elem(1) == [self|[]]
assert calculated |> elem(1) == [self()|[]]
assert_receive {:text, "test"}

calculated = handle_event({:send, {:text, "test"}, self}, state)
calculated = handle_event({:send, {:text, "test"}, self()}, state)

assert calculated |> elem(0) == :ok
assert calculated |> elem(1) |> is_list
assert calculated |> elem(1) == [self|[]]
assert calculated |> elem(1) == [self()|[]]
refute_receive {:text, "test"}

calculated = handle_event({:remove_client, self}, state)
calculated = handle_event({:remove_client, self()}, state)

assert calculated |> elem(0) == :ok
assert calculated |> elem(1) |> is_list
Expand Down

0 comments on commit 5678f5c

Please sign in to comment.