-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create Maverick.Response protocol and switches response handling to it (
#28) * Create Maverick.Response protocol and switches response handling to it * Ran formatter
- Loading branch information
Showing
8 changed files
with
215 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
defprotocol Maverick.Response do | ||
@fallback_to_any true | ||
def handle(t, conn) | ||
end | ||
|
||
defimpl Maverick.Response, for: Any do | ||
def handle(term, %Plug.Conn{private: %{maverick_route: route}} = conn) do | ||
conn | ||
|> Plug.Conn.put_resp_content_type("application/json") | ||
|> Plug.Conn.resp(route.success_code, Jason.encode!(term)) | ||
end | ||
end | ||
|
||
defimpl Maverick.Response, for: Tuple do | ||
def handle({:ok, term}, conn) do | ||
Maverick.Response.handle(term, conn) | ||
end | ||
|
||
def handle({status, headers, term}, conn) do | ||
conn | ||
|> Plug.Conn.put_resp_content_type("application/json") | ||
|> add_headers(headers) | ||
|> Plug.Conn.resp(status, Jason.encode!(term)) | ||
end | ||
|
||
def handle({:error, exception}, conn) when is_exception(exception) do | ||
Maverick.Exception.handle(exception, conn) | ||
end | ||
|
||
def handle({:error, error_message}, %Plug.Conn{private: %{maverick_route: route}} = conn) do | ||
response = | ||
%{error_code: route.error_code, error_message: error_message} | ||
|> Jason.encode!() | ||
|
||
conn | ||
|> Plug.Conn.put_resp_content_type("application/json") | ||
|> Plug.Conn.send_resp(route.error_code, response) | ||
end | ||
|
||
defp add_headers(conn, headers) do | ||
Enum.reduce(headers, conn, fn {key, value}, conn -> | ||
Plug.Conn.put_resp_header(conn, key, value) | ||
end) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
defmodule Maverick.ResponseTest do | ||
use Maverick.ConnCase, async: true | ||
use Plug.Test | ||
|
||
setup do | ||
route = %Maverick.Route{success_code: 200, error_code: 403} | ||
|
||
[ | ||
route: route, | ||
conn: conn(:get, "/") |> Plug.Conn.put_private(:maverick_route, route) | ||
] | ||
end | ||
|
||
test "a raw map is json encoded with success response", ctx do | ||
response = | ||
Maverick.Response.handle(%{one: 1}, ctx.conn) | ||
|> json_response(200) | ||
|
||
assert %{"one" => 1} == response | ||
end | ||
|
||
test "a raw string is json encoded with success response", ctx do | ||
response = | ||
Maverick.Response.handle("hello world", ctx.conn) | ||
|> json_response(200) | ||
|
||
assert "hello world" == response | ||
end | ||
|
||
test "an ok tuple with json encode the term with success response", ctx do | ||
response = | ||
Maverick.Response.handle({:ok, %{one: 1}}, ctx.conn) | ||
|> json_response(200) | ||
|
||
assert %{"one" => 1} == response | ||
end | ||
|
||
test "a 3 element tuple controle status, headers and response explicitly", ctx do | ||
conn = Maverick.Response.handle({202, [{"key", "value"}], %{one: 1}}, ctx.conn) | ||
response = json_response(conn, 202) | ||
|
||
assert %{"one" => 1} == response | ||
assert ["value"] = Plug.Conn.get_resp_header(conn, "key") | ||
end | ||
|
||
test "a error tuple json encodes reason with error response", ctx do | ||
response = | ||
Maverick.Response.handle({:error, "bad stuff"}, ctx.conn) | ||
|> json_response(403) | ||
|
||
assert %{"error_code" => 403, "error_message" => "bad stuff"} == response | ||
end | ||
|
||
test "an error exception tuple triggers Maverick.Exception protocol", ctx do | ||
exception = ArgumentError.exception(message: "argument is bad") | ||
|
||
response = | ||
Maverick.Response.handle({:error, exception}, ctx.conn) | ||
|> json_response(500) | ||
|
||
assert %{"error_code" => 500, "error_message" => "argument is bad"} == response | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
defmodule Maverick.ConnCase do | ||
use ExUnit.CaseTemplate | ||
|
||
using do | ||
quote do | ||
import Maverick.Test.Helpers | ||
end | ||
end | ||
end | ||
|
||
defmodule Maverick.Test.Helpers do | ||
require ExUnit.Assertions | ||
|
||
def response(%Plug.Conn{status: status, resp_body: body}, given) do | ||
given = Plug.Conn.Status.code(given) | ||
|
||
if given == status do | ||
body | ||
else | ||
raise "expected response with status #{given}, got: #{status}, with body:\n#{inspect(body)}" | ||
end | ||
end | ||
|
||
def json_response(conn, status) do | ||
body = response(conn, status) | ||
_ = response_content_type(conn, :json) | ||
|
||
Jason.decode!(body) | ||
end | ||
|
||
def response_content_type(conn, format) when is_atom(format) do | ||
case Plug.Conn.get_resp_header(conn, "content-type") do | ||
[] -> | ||
raise "no content-type was set, expected a #{format} response" | ||
|
||
[h] -> | ||
if response_content_type?(h, format) do | ||
h | ||
else | ||
raise "expected content-type for #{format}, got: #{inspect(h)}" | ||
end | ||
|
||
[_ | _] -> | ||
raise "more than one content-type was set, expected a #{format} response" | ||
end | ||
end | ||
|
||
def response_content_type?(header, format) do | ||
case parse_content_type(header) do | ||
{part, subpart} -> | ||
format = Atom.to_string(format) | ||
|
||
format in MIME.extensions(part <> "/" <> subpart) or | ||
format == subpart or String.ends_with?(subpart, "+" <> format) | ||
|
||
_ -> | ||
false | ||
end | ||
end | ||
|
||
defp parse_content_type(header) do | ||
case Plug.Conn.Utils.content_type(header) do | ||
{:ok, part, subpart, _params} -> | ||
{part, subpart} | ||
|
||
_ -> | ||
false | ||
end | ||
end | ||
end |