Skip to content

Commit

Permalink
add error handling function
Browse files Browse the repository at this point in the history
  • Loading branch information
hlindset committed Dec 8, 2024
1 parent 0797550 commit 2c6db51
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 22 deletions.
12 changes: 7 additions & 5 deletions lib/image_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,24 @@ defmodule ImagePlug do
url = "#{root_url}/#{path}"
image_resp = Req.get!(url)

with {:ok, chain} <- param_parser.parse(conn),
with {:ok, chain} <- param_parser.parse(conn) |> wrap_error(),
{:ok, image} <- Image.from_binary(image_resp.body),
{:ok, final_state} <- TransformChain.execute(%TransformState{image: image}, chain) do
send_image(conn, final_state)
else
{:error, {:invalid_params, {module, input}}} ->
Logger.info("parameter parse error for #{inspect(module)}: #{inspect(input)}")
send_resp(conn, 400, "invalid parameters")

{:error, {:transform_error, %TransformState{errors: errors} = final_state}} ->
# TODO: handle transform error - debug mode + graceful mode switch?
Logger.info("transform_error(s): #{inspect(errors)}")
send_image(conn, final_state)

{:error, {:handler, error}} ->
param_parser.handle_error(conn, error)
end
end

defp wrap_error({:error, _} = error), do: {:error, {:handler, error}}
defp wrap_error(result), do: result

defp accepted_formats(%Plug.Conn{} = conn) do
from_accept_header =
get_req_header(conn, "accept")
Expand Down
7 changes: 6 additions & 1 deletion lib/image_plug/param_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@ defmodule ImagePlug.ParamParser do
@doc """
Parse transform chain (a list of `ImagePlug.Transform` with parameters) from a `Plug.Conn`.
"""
@callback parse(Plug.Conn.t()) :: {:ok, transform_chain()} | {:error, list(parse_error())}
@callback parse_chain(Plug.Conn.t()) :: {:ok, transform_chain()} | {:error, list(parse_error())}

@doc """
Handle errors generated by the handler.
"""
@callback handle_error(Plug.Conn.t(), {:error, any()}) :: {:ok, Plug.Conn.t()}
end
25 changes: 23 additions & 2 deletions lib/image_plug/param_parser/twicpics.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
defmodule ImagePlug.ParamParser.Twicpics do
@behaviour ImagePlug.ParamParser

require Logger

alias ImagePlug.ParamParser.Twicpics
alias ImagePlug.ParamParser.Twicpics.Formatters
alias ImagePlug.ParamParser.Twicpics.Utils

@transforms %{
"crop" => {ImagePlug.Transform.Crop, Twicpics.Transform.CropParser},
Expand Down Expand Up @@ -46,10 +50,13 @@ defmodule ImagePlug.ParamParser.Twicpics do
case Twicpics.KVParser.parse(chain_str, @transform_keys, pos_offset) do
{:ok, kv_params} ->
Enum.reduce_while(kv_params, {:ok, []}, fn
{transform_name, params_str, pos}, {:ok, transforms_acc} ->
{transform_name, params_str, key_start_pos}, {:ok, transforms_acc} ->
{transform_mod, parser_mod} = Map.get(@transforms, transform_name)

case parser_mod.parse(params_str, pos) do
# key start pos + key length + 1 (=-sign)
value_pos = key_start_pos + String.length(transform_name) + 1

case parser_mod.parse(params_str, value_pos) do
{:ok, parsed_params} ->
{:cont, {:ok, [{transform_mod, parsed_params} | transforms_acc]}}

Expand All @@ -66,4 +73,18 @@ defmodule ImagePlug.ParamParser.Twicpics do
other -> other
end
end

@impl ImagePlug.ParamParser
def handle_error(%Plug.Conn{} = conn, {:error, _} = error) do
Logger.error(inspect(error))

error_msg =
error
|> Utils.update_error_input("#{conn.request_path}?#{conn.query_string}")
|> Formatters.format_error()

conn
|> Plug.Conn.put_resp_content_type("text/plain")
|> Plug.Conn.send_resp(400, error_msg)
end
end
27 changes: 15 additions & 12 deletions lib/image_plug/param_parser/twicpics/formatters.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,41 @@ defmodule ImagePlug.ParamParser.Twicpics.Formatters do
defp join_chars([choice | tail], acc),
do: join_chars(tail, ~s|#{acc}, "#{format_char(choice)}"|)

defp format_msg({:unexpected_char, opts}) do
defp format_msg({:error, {:expected_value, opts}}) do
~s|Expected value.|
end

defp format_msg({:error, {:unexpected_char, opts}}) do
expected_chars = Keyword.get(opts, :expected)
found_char = Keyword.get(opts, :found)
~s|Expected #{join_chars(expected_chars)} but "#{format_char(found_char)}" found.|
end

defp format_msg({:strictly_positive_number_required, opts}) do
defp format_msg({:error, {:strictly_positive_number_required, opts}}) do
found_number = Keyword.get(opts, :found)
~s|Strictly positive number expected, found #{format_char(found_number)} instead.|
end

defp format_msg({:positive_number_required, opts}) do
defp format_msg({:error, {:positive_number_required, opts}}) do
found_number = Keyword.get(opts, :found)
~s|Positive number expected, found #{format_char(found_number)} instead.|
end

defp format_msg({other, _}), do: to_string(other)

def format_error({_, opts} = error) do
def format_error({:error, {_, opts}} = error) when is_list(opts) do
input = Keyword.get(opts, :input, "")
error_offset = Keyword.get(opts, :pos, 0)
error_padding = String.duplicate(" ", error_offset)
IO.puts(input)
IO.puts("#{error_padding}▲")
IO.puts("#{error_padding}└── #{format_msg(error)}")
end

def print_result({:ok, result}) do
IO.puts(inspect(result, syntax_colors: IO.ANSI.syntax_colors()))
"""
#{input}
#{error_padding}
#{error_padding}└── #{format_msg(error)}
"""
end

def print_result({:error, err}) do
format_error(err)
def format_error(_error) do
"An unhandled error occurred."
end
end
2 changes: 1 addition & 1 deletion lib/image_plug/param_parser/twicpics/size_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule ImagePlug.ParamParser.Twicpics.SizeParser do
def parse(input, pos_offset \\ 0) do
case String.split(input, "x", parts: 2) do
["-", "-"] ->
{:error, {:unexpected_char, pos: pos_offset + 2, expected: ["(", "[0-9]", found: "-"]}}
Utils.unexpected_value_error(pos_offset + 2, expected: ["(", "[0-9]", found: "-"])

["-", height_str] ->
case parse_and_validate(height_str, pos_offset + 2) do
Expand Down
2 changes: 1 addition & 1 deletion lib/image_plug/param_parser/twicpics/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule ImagePlug.ParamParser.Twicpics.Utils do
# consume all other chars
defp balanced_parens?(<<_char::utf8, rest::binary>>, stack), do: balanced_parens?(rest, stack)

def update_error_input({:error, {reason, opts}}, input) do
def update_error_input({:error, {reason, opts}}, input) when is_list(opts) do
{:error, {reason, Keyword.put(opts, :input, input)}}
end

Expand Down

0 comments on commit 2c6db51

Please sign in to comment.