Add Endpoints behaviour and refactor router and related methods

This commit is contained in:
mitchell 2020-01-05 14:54:55 -05:00
parent 1502d10cdc
commit 98684feb2d
8 changed files with 98 additions and 62 deletions

11
dialyzer.ignore-warnings Normal file
View File

@ -0,0 +1,11 @@
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Atom':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.BitString':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Float':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Function':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Integer':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.List':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Map':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.PID':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Port':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Reference':'__impl__'/1
:0: Unknown function 'Elixir.Shortnr.Transport.Text.Encodable.Tuple':'__impl__'/1

View File

@ -3,57 +3,27 @@ defmodule Shortnr.Router do
This module contains the Router for the Shortnr application. Do not import, other than
Application entry.
"""
use Plug.ErrorHandler
use Plug.Router
alias Shortnr.Transport.HTTP
alias Shortnr.URL
require Logger
alias Shortnr.Transport.{HTTP, Text}
alias Shortnr.URL
use Plug.ErrorHandler
use Plug.Router
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
get "/" do
conn
|> HTTP.wrap()
|> HTTP.handle(fn -> URL.list(URL.Repo.ETS) end)
|> Text.encode_response()
|> HTTP.send(:ok)
end
# BEGIN URL routes
post("/urls/:url", do: URL.Endpoints.select(conn, :create, url))
post "/urls/:url" do
conn
|> HTTP.wrap(url)
|> HTTP.handle(&URL.create(&1, URL.Repo.ETS))
|> Text.encode_response()
|> HTTP.send(:created)
end
get("/", do: URL.Endpoints.select(conn, :list))
get("/urls", do: URL.Endpoints.select(conn, :list))
get("/:id", do: URL.Endpoints.select(conn, :get, id))
get "/urls" do
conn
|> HTTP.wrap()
|> HTTP.handle(fn -> URL.list(URL.Repo.ETS) end)
|> Text.encode_response()
|> HTTP.send(:ok)
end
get "/:id" do
conn
|> HTTP.wrap(id)
|> HTTP.handle(&URL.get(&1, URL.Repo.ETS))
|> Text.encode_response()
|> HTTP.send(:found)
end
delete "/:id" do
conn
|> HTTP.wrap(id)
|> HTTP.handle(&URL.delete(&1, URL.Repo.ETS))
|> Text.encode_response()
|> HTTP.send(:ok)
end
delete("/:id", do: URL.Endpoints.select(conn, :delete, id))
# END
match _ do
conn

View File

@ -3,15 +3,13 @@ defmodule Shortnr do
The Shortnr application entry point. Check README for usage documenation.
"""
use Application
require Logger
use Application
@impl true
@impl Application
def start(_type, _args) do
port = port()
children = [
{Plug.Cowboy, scheme: :http, plug: Shortnr.Router, options: [port: port]}
{Plug.Cowboy, scheme: :http, plug: Shortnr.Router, options: [port: port()]}
]
if ets_implementation() == :dets do
@ -20,11 +18,11 @@ defmodule Shortnr do
:ets.new(:urls, [:set, :named_table])
end
Logger.info("server starting", port: port)
Logger.info("server starting", port: port())
Supervisor.start_link(children, strategy: :one_for_one)
end
@impl true
@impl Application
def stop(_state) do
if ets_implementation() == :dets, do: :dets.close(:urls)
end

View File

@ -64,3 +64,12 @@ defmodule Shortnr.Transport.HTTP do
defp status_to_code(:not_found), do: 404
defp status_to_code(:internal_server_error), do: 500
end
defmodule Shortnr.Transport.HTTP.Endpoints do
@moduledoc """
This modules is a behaviour that should be implemented by each service that needs to be routed by
Plug.Router
"""
@callback select(Plug.Conn.t(), atom, term) :: Plug.Conn.t()
end

View File

@ -3,28 +3,29 @@ defmodule Shortnr.Transport.Text do
This modules contains functions to decode and encode text formatted http requests and responses.
"""
import Plug.Conn
alias Shortnr.Transport.HTTP
alias Shortnr.Transport.Text.Encodable
@spec decode_request(Plug.Conn.t()) :: HTTP.ok_error()
def decode_request(conn) do
import Plug.Conn
@spec decode(HTTP.ok_error()) :: HTTP.ok_error()
def decode({:ok, _body, conn}) do
{:ok, body, conn} = read_body(conn)
{:ok, body, conn}
end
@spec encode_response(HTTP.ok_error()) :: HTTP.ok_error()
def encode_response(ok_error = {:error, _, _}), do: ok_error
@spec encode(HTTP.ok_error()) :: HTTP.ok_error()
def encode(ok_error = {:error, _, _}), do: ok_error
def encode_response({:ok, [], conn}) do
def encode({:ok, [], conn}) do
{:ok, "", conn}
end
def encode_response({:ok, body, conn}) when is_list(body) do
def encode({:ok, body, conn}) when is_list(body) do
{:ok, for(item <- body, into: "", do: "#{item}\n"), conn}
end
def encode_response({:ok, body, conn}) do
def encode({:ok, body, conn}) do
{:ok, Encodable.encode(body), conn}
end
end

45
lib/url/endpoints.ex Normal file
View File

@ -0,0 +1,45 @@
defmodule Shortnr.URL.Endpoints do
@moduledoc """
This module implements the Endpoints behaviour.
"""
alias Shortnr.Transport.{HTTP, Text}
alias Shortnr.Transport.HTTP.Endpoints
alias Shortnr.URL
@behaviour Endpoints
@impl Endpoints
def select(conn, name, arg \\ nil)
def select(conn, :list, _arg) do
conn
|> HTTP.wrap()
|> HTTP.handle(fn -> URL.list(URL.Repo.ETS) end)
|> Text.encode()
|> HTTP.send(:ok)
end
def select(conn, :create, url) do
conn
|> HTTP.wrap(url)
|> HTTP.handle(&URL.create(&1, URL.Repo.ETS))
|> Text.encode()
|> HTTP.send(:created)
end
def select(conn, :get, id) do
conn
|> HTTP.wrap(id)
|> HTTP.handle(&URL.get(&1, URL.Repo.ETS))
|> Text.encode()
|> HTTP.send(:found)
end
def select(conn, :delete, id) do
conn
|> HTTP.wrap(id)
|> HTTP.handle(&URL.delete(&1, URL.Repo.ETS))
|> Text.encode()
|> HTTP.send(:ok)
end
end

View File

@ -2,10 +2,11 @@ defmodule Shortnr.URL.Repo.ETS do
@moduledoc """
This module is an implemention of the Repo behaviour using the Erlang ETS library.
"""
alias Shortnr.URL.Repo
@behaviour Shortnr.URL.Repo
@behaviour Repo
@impl true
@impl Repo
def get(key) do
case ets().lookup(:urls, key) |> List.first() do
{_, url} -> {:ok, url}
@ -13,25 +14,25 @@ defmodule Shortnr.URL.Repo.ETS do
end
end
@impl true
@impl Repo
def put(url) do
:ok = ets().insert(:urls, {url.id, url})
:ok
end
@impl true
@impl Repo
def list do
resp = ets().select(:urls, [{:"$1", [], [:"$1"]}])
{:ok, resp |> Enum.map(&elem(&1, 1))}
end
@impl true
@impl Repo
def delete(key) do
:ok = ets().delete(:urls, key)
:ok
end
@impl true
@impl Repo
def reset do
:ok = ets().delete_all_objects(:urls)
end

View File

@ -8,6 +8,7 @@ defmodule Shortnr.MixProject do
version: "0.1.0",
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
dialyzer: [ignore_warnings: "dialyzer.ignore-warnings"],
deps: deps()
]
end