mirror of https://github.com/mitchell/shortnr.git
Add HTTP wrap func and Text Encodable:
- Refactor HTTP send func - Impl. Text Encodable for URL - Error when url is not found
This commit is contained in:
parent
b699e661e2
commit
1502d10cdc
|
@ -1,4 +1,4 @@
|
|||
FROM elixir:1.9-slim as build
|
||||
FROM elixir:1.9 as build
|
||||
|
||||
WORKDIR /root/shortnr
|
||||
COPY . .
|
||||
|
@ -8,7 +8,7 @@ RUN mix local.rebar --force
|
|||
|
||||
RUN env MIX_ENV=prod mix release
|
||||
|
||||
FROM debian:buster-20191014-slim
|
||||
FROM debian:stable-20191224-slim
|
||||
|
||||
WORKDIR /home/shortnr
|
||||
COPY --from=build /root/shortnr/_build/prod/rel/shortnr/ .
|
||||
|
|
|
@ -19,6 +19,7 @@ config :logger, :console,
|
|||
|
||||
config :credo,
|
||||
checks: [
|
||||
# Ignore these checks because they don't apply to the projects Elixir version
|
||||
{Credo.Check.Refactor.MapInto, false},
|
||||
{Credo.Check.Warning.LazyLogging, false}
|
||||
]
|
||||
|
|
|
@ -15,45 +15,57 @@ defmodule Shortnr.Router do
|
|||
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
|
||||
|
||||
post "/urls/:url" do
|
||||
{:ok, url, conn}
|
||||
conn
|
||||
|> HTTP.wrap(url)
|
||||
|> HTTP.handle(&URL.create(&1, URL.Repo.ETS))
|
||||
|> Text.encode_response()
|
||||
|> HTTP.send(:created, conn)
|
||||
|> HTTP.send(:created)
|
||||
end
|
||||
|
||||
get "/urls" do
|
||||
{:ok, :ignore, conn}
|
||||
conn
|
||||
|> HTTP.wrap()
|
||||
|> HTTP.handle(fn -> URL.list(URL.Repo.ETS) end)
|
||||
|> Text.encode_response()
|
||||
|> HTTP.send(:ok, conn)
|
||||
|> HTTP.send(:ok)
|
||||
end
|
||||
|
||||
get "/:id" do
|
||||
{:ok, id, conn}
|
||||
conn
|
||||
|> HTTP.wrap(id)
|
||||
|> HTTP.handle(&URL.get(&1, URL.Repo.ETS))
|
||||
|> Text.encode_response()
|
||||
|> HTTP.send(:found, conn)
|
||||
|> HTTP.send(:found)
|
||||
end
|
||||
|
||||
delete "/:id" do
|
||||
{:ok, id, conn}
|
||||
conn
|
||||
|> HTTP.wrap(id)
|
||||
|> HTTP.handle(&URL.delete(&1, URL.Repo.ETS))
|
||||
|> Text.encode_response()
|
||||
|> HTTP.send(:ok, conn)
|
||||
|> HTTP.send(:ok)
|
||||
end
|
||||
|
||||
match _ do
|
||||
{:error, {:not_found, "route not found"}, conn}
|
||||
|> Text.encode_response()
|
||||
|> HTTP.send(:ignore, conn)
|
||||
conn
|
||||
|> HTTP.wrap({:not_found, "route not found"})
|
||||
|> HTTP.send()
|
||||
end
|
||||
|
||||
def handle_errors(conn, %{kind: _kind, reason: reason, stack: stack}) do
|
||||
Logger.error(reason, stack: stack)
|
||||
Logger.error(inspect(reason), stack: ~s|"#{inspect(stack)}"|)
|
||||
|
||||
{:error, {:internal_server_error, "internal server error"}, conn}
|
||||
|> Text.encode_response()
|
||||
|> HTTP.send(:ignore, conn)
|
||||
conn
|
||||
|> HTTP.wrap({:internal_server_error, "internal server error"})
|
||||
|> HTTP.send()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,13 +3,20 @@ defmodule Shortnr.Transport.HTTP do
|
|||
This module contains functions that can be used to handle HTTP requests and send responses, by
|
||||
manipulating Plug.Conn.
|
||||
"""
|
||||
|
||||
alias Shortnr.Transport
|
||||
import Plug.Conn
|
||||
|
||||
@type error :: {:error, {atom(), String.t()}, Plug.Conn.t()}
|
||||
@type ok_error :: {:ok, term(), Plug.Conn.t()} | error()
|
||||
|
||||
@spec handle(ok_error(), (... -> ok_error())) :: ok_error()
|
||||
@spec wrap(Plug.Conn.t(), term()) :: ok_error
|
||||
def wrap(conn, argument \\ nil)
|
||||
def wrap(conn, nil), do: {:ok, nil, conn}
|
||||
def wrap(conn, {first, _second} = argument) when is_atom(first), do: {:error, argument, conn}
|
||||
def wrap(conn, argument), do: {:ok, argument, conn}
|
||||
|
||||
@spec handle(ok_error(), (term() -> Transport.ok_error()) | (() -> Transport.ok_error())) ::
|
||||
ok_error()
|
||||
def handle(error = {:error, _sub_error, _conn}, _func), do: error
|
||||
|
||||
def handle({:ok, request, conn}, func) when is_function(func, 1) do
|
||||
|
@ -29,19 +36,24 @@ defmodule Shortnr.Transport.HTTP do
|
|||
defp convert_error({:invalid_argument, message}, conn),
|
||||
do: {:error, {:bad_request, message}, conn}
|
||||
|
||||
defp convert_error({:not_found, message}, conn),
|
||||
do: {:error, {:not_found, 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)
|
||||
@spec send(ok_error(), atom()) :: Plug.Conn.t()
|
||||
def send(ok_error, success_status \\ nil)
|
||||
|
||||
def send({:error, {status, body}, conn}, _success_status) do
|
||||
send_resp(conn, status_to_code(status), body)
|
||||
end
|
||||
|
||||
def send({:ok, body, conn}, :found, _original_conn) do
|
||||
conn = put_resp_header(conn, "Location", URI.to_string(body))
|
||||
send_resp(conn, status_to_code(:found), URI.to_string(body))
|
||||
def send({:ok, location, conn}, :found) do
|
||||
conn = put_resp_header(conn, "Location", location)
|
||||
send_resp(conn, status_to_code(:found), location)
|
||||
end
|
||||
|
||||
def send({:ok, body, conn}, success_status, _original_conn) do
|
||||
def send({:ok, body, conn}, success_status) do
|
||||
send_resp(conn, status_to_code(success_status), body)
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Shortnr.Transport.Text do
|
|||
|
||||
import Plug.Conn
|
||||
alias Shortnr.Transport.HTTP
|
||||
alias Shortnr.URL
|
||||
alias Shortnr.Transport.Text.Encodable
|
||||
|
||||
@spec decode_request(Plug.Conn.t()) :: HTTP.ok_error()
|
||||
def decode_request(conn) do
|
||||
|
@ -24,11 +24,20 @@ defmodule Shortnr.Transport.Text do
|
|||
{:ok, for(item <- body, into: "", do: "#{item}\n"), conn}
|
||||
end
|
||||
|
||||
def encode_response({:ok, %URL{url: url}, conn}) do
|
||||
{:ok, url, conn}
|
||||
end
|
||||
|
||||
def encode_response({:ok, body, conn}) do
|
||||
{:ok, "#{body}", conn}
|
||||
{:ok, Encodable.encode(body), conn}
|
||||
end
|
||||
end
|
||||
|
||||
defprotocol Shortnr.Transport.Text.Encodable do
|
||||
@moduledoc """
|
||||
Implement this protocol for your type if you would like to text encode it.
|
||||
"""
|
||||
|
||||
@fallback_to_any true
|
||||
def encode(encodable)
|
||||
end
|
||||
|
||||
defimpl Shortnr.Transport.Text.Encodable, for: Any do
|
||||
def encode(encodable), do: to_string(encodable)
|
||||
end
|
||||
|
|
|
@ -20,21 +20,24 @@ defmodule Shortnr.URL do
|
|||
|
||||
@spec create(String.t(), module()) :: {:ok, String.t()} | Transport.error()
|
||||
def create(url, repo) do
|
||||
url_struct = %__MODULE__{id: Util.gen_id(), url: URI.parse(url)}
|
||||
url_struct = %__MODULE__{id: Util.generate_id(), url: URI.parse(url)}
|
||||
|
||||
{:ok, extant_url} = repo.get(url_struct.id)
|
||||
|
||||
if is_nil(extant_url) do
|
||||
if extant_url do
|
||||
create(url, repo)
|
||||
else
|
||||
:ok = repo.put(url_struct)
|
||||
{:ok, url_struct.id}
|
||||
else
|
||||
create(url, repo)
|
||||
end
|
||||
end
|
||||
|
||||
@spec get(String.t(), module()) :: {:ok, t()} | Transport.error()
|
||||
def get(key, repo) do
|
||||
{:ok, _} = repo.get(key)
|
||||
case repo.get(key) do
|
||||
{:ok, nil} -> {:error, {:not_found, "url could not be found with the given id"}}
|
||||
{:ok, url} -> {:ok, url}
|
||||
end
|
||||
end
|
||||
|
||||
@spec list(module()) :: {:ok, list(t())} | Transport.error()
|
||||
|
@ -48,11 +51,17 @@ defmodule Shortnr.URL do
|
|||
{:ok, "Success"}
|
||||
end
|
||||
|
||||
defimpl Transport.Text.Encodable do
|
||||
alias Shortnr.URL
|
||||
@spec encode(URL.t()) :: String.t()
|
||||
def encode(url), do: URI.to_string(url.url)
|
||||
end
|
||||
|
||||
defimpl String.Chars do
|
||||
alias Shortnr.URL
|
||||
@spec to_string(URL.t()) :: String.t()
|
||||
def to_string(url) do
|
||||
"id=#{url.id} created=#{url.created_at} updated=#{url.updated_at} url=#{url.url}"
|
||||
"id=#{url.id} created_at=#{url.created_at} updated_at=#{url.updated_at} url=#{url.url}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,8 @@ defmodule Shortnr.URL.Util do
|
|||
URL module utility functions.
|
||||
"""
|
||||
@id_chars String.codepoints("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXWYZ0123456789")
|
||||
@spec gen_id() :: String.t()
|
||||
def gen_id do
|
||||
@spec generate_id() :: String.t()
|
||||
def generate_id do
|
||||
for _ <- 0..7,
|
||||
into: "",
|
||||
do: Enum.random(@id_chars)
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -4,6 +4,7 @@ defmodule Shortnr.MixProject do
|
|||
def project do
|
||||
[
|
||||
app: :shortnr,
|
||||
description: "A small & simple url shortener",
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.9",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
|
@ -24,7 +25,6 @@ defmodule Shortnr.MixProject do
|
|||
[
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
{:elixir_uuid, "~> 1.2"},
|
||||
{:jason, "~> 1.1"},
|
||||
{:dialyxir, "~> 0.5.1", only: [:dev], runtime: false},
|
||||
{:credo, "~> 1.1.5", only: [:dev, :test], runtime: false}
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
|
|
Loading…
Reference in New Issue