From 98684feb2dab48402d3306c48d4e1e64e11e1af2 Mon Sep 17 00:00:00 2001 From: mitchell Date: Sun, 5 Jan 2020 14:54:55 -0500 Subject: [PATCH] Add Endpoints behaviour and refactor router and related methods --- dialyzer.ignore-warnings | 11 +++++++++ lib/router.ex | 52 +++++++++------------------------------- lib/shortnr.ex | 12 ++++------ lib/transport/http.ex | 9 +++++++ lib/transport/text.ex | 17 ++++++------- lib/url/endpoints.ex | 45 ++++++++++++++++++++++++++++++++++ lib/url/repo/ets.ex | 13 +++++----- mix.exs | 1 + 8 files changed, 98 insertions(+), 62 deletions(-) create mode 100644 dialyzer.ignore-warnings create mode 100644 lib/url/endpoints.ex diff --git a/dialyzer.ignore-warnings b/dialyzer.ignore-warnings new file mode 100644 index 0000000..b11134b --- /dev/null +++ b/dialyzer.ignore-warnings @@ -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 diff --git a/lib/router.ex b/lib/router.ex index 6dd3fe5..a412508 100644 --- a/lib/router.ex +++ b/lib/router.ex @@ -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 diff --git a/lib/shortnr.ex b/lib/shortnr.ex index 5ebb732..caafc11 100644 --- a/lib/shortnr.ex +++ b/lib/shortnr.ex @@ -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 diff --git a/lib/transport/http.ex b/lib/transport/http.ex index 1e1084e..380b0b2 100644 --- a/lib/transport/http.ex +++ b/lib/transport/http.ex @@ -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 diff --git a/lib/transport/text.ex b/lib/transport/text.ex index b153c47..938cf9c 100644 --- a/lib/transport/text.ex +++ b/lib/transport/text.ex @@ -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 diff --git a/lib/url/endpoints.ex b/lib/url/endpoints.ex new file mode 100644 index 0000000..402c33d --- /dev/null +++ b/lib/url/endpoints.ex @@ -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 diff --git a/lib/url/repo/ets.ex b/lib/url/repo/ets.ex index ec5bf1b..904d0ac 100644 --- a/lib/url/repo/ets.ex +++ b/lib/url/repo/ets.ex @@ -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 diff --git a/mix.exs b/mix.exs index 707240d..9caa4bb 100644 --- a/mix.exs +++ b/mix.exs @@ -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