Project architecture boilerplate

This commit is contained in:
mitchell 2019-12-07 21:34:58 -05:00
commit 7ff70d452a
12 changed files with 261 additions and 0 deletions

35
lib/router.ex Normal file
View file

@ -0,0 +1,35 @@
defmodule Shortnr.Router do
use Plug.ErrorHandler
use Plug.Router
require Logger
alias Shortnr.Transport.{Json, Http}
alias Shortnr.Url
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
post "/urls" do
conn
|> Json.decode_request(Url.CreateRequest)
|> Http.handle(&Url.create(&1, Url.Repo.DETS))
|> Json.encode_response()
|> Http.send(:created, conn)
end
match _ do
{:error, {:not_found, "route not found"}, conn}
|> Json.encode_response()
|> Http.send(:ignore, conn)
end
def handle_errors(conn, %{kind: _kind, reason: reason, stack: stack}) do
Logger.error(inspect(reason), stack: stack)
{:error, {:internal_server_error, "internal server error"}, conn}
|> Json.encode_response()
|> Http.send(:ignore, conn)
end
end

28
lib/shortnr.ex Normal file
View file

@ -0,0 +1,28 @@
defmodule Shortnr do
@moduledoc """
Documentation for Shortnr.
"""
use Application
require Logger
@impl true
def start(_type, _args) do
port = port()
children = [
{Plug.Cowboy, scheme: :http, plug: Shortnr.Router, options: [port: port]}
]
Logger.info("server starting", port: port)
Supervisor.start_link(children, strategy: :one_for_one)
end
@spec port() :: integer()
defp port do
case Application.fetch_env(:service, :port) do
{:ok, port} -> port
_ -> 4000
end
end
end

36
lib/transport/http.ex Normal file
View file

@ -0,0 +1,36 @@
defmodule Shortnr.Transport.Http do
import Plug.Conn
@type ok_error :: {:ok, term(), Plug.Conn.t()} | error()
@type error :: {:error, {atom(), String.t()}, Plug.Conn.t()}
@spec handle(ok_error(), (term() -> ok_error())) :: ok_error()
def handle(error = {:error, _sub_error, _conn}, _func), do: error
def handle({:ok, request, conn}, func) do
case func.(request) do
{:ok, response} -> {:ok, response, conn}
{:error, error_value} -> convert_error(error_value, conn)
end
end
defp convert_error({:invalid_argument, message}, conn),
do: {:error, {:bad_request, message}, conn}
defp convert_error(_, conn), do: {:error, {:internal_server_error, "unknown error"}, conn}
@spec send(ok_error(), atom(), Plug.Conn.t()) :: Plug.Conn.t()
def send({:error, {status, body}, _conn}, _success_status, original_conn) do
send_resp(original_conn, status_to_code(status), body)
end
def send({:ok, body, conn}, success_status, _original_conn) do
send_resp(conn, status_to_code(success_status), body)
end
defp status_to_code(:ok), do: 200
defp status_to_code(:created), do: 201
defp status_to_code(:bad_request), do: 400
defp status_to_code(:not_found), do: 404
defp status_to_code(:internal_server_error), do: 500
end

28
lib/transport/json.ex Normal file
View file

@ -0,0 +1,28 @@
defmodule Shortnr.Transport.Json do
import Plug.Conn
alias Shortnr.Transport.Http
@spec decode_request(Plug.Conn.t(), module()) :: Http.ok_error()
def decode_request(conn, struct_module) do
{:ok, body, conn} = read_body(conn)
{:ok, params} = Jason.decode(body)
params_list =
params
|> Map.to_list()
|> Enum.map(fn {key, value} -> {String.to_atom(key), value} end)
{:ok, struct(struct_module, params_list), conn}
end
@spec encode_response(Http.ok_error()) :: Http.ok_error()
def encode_response({:error, {status, response}, conn}) do
{:ok, json_body} = Jason.encode(%{data: response})
{:error, {status, json_body}, conn}
end
def encode_response({:ok, response, conn}) do
{:ok, json_body} = Jason.encode(%{data: response})
{:ok, json_body, conn}
end
end

View file

@ -0,0 +1,4 @@
defmodule Shortnr.Transport do
@type error :: {:error, {atom(), String.t()}}
@type ok_error :: {:ok, term()} | error()
end