Use macro in Elixir's HTTPoison

Author:Gao
Created At:2022-12-05

使用了HTTPoison来创建一个http请求代理后, 当前有一个问题.处理http请求响应有太多的重复代码, 所以需要一个方式来优化重复问题.

这里, 尝试了之前很少使用的macro来解决.

关于macro

Elixir是基于beam上的语言, 语言上很多的statement实际上都是macro, 比如基本的if,else

在Elixir中,允许直接创建和访问abstract syntax tree (AST), 可以使用 quota and unquota来操作

简单示例

iex> denominator = 2
2
iex> quote do: divide(42, denominator)
{:divide, [], [42, {:denominator, [], Elixir}]}
iex> quote do: divide(42, unquote(denominator))
{:divide, [], [42, 2]}

简单示例, 如何使用macro

defmodule Logger do
  defmacro log(msg) do
    if Application.get_env(:logger, :enabled) do
      quote do
        IO.puts("Logged message: #{unquote(msg)}")
      end
    end
  end
end

defmodule Example do
  require Logger

  def test do
    Logger.log("This is a log message")
  end
end

解释:

这里使用宏是因为macro是在编译时展开的, 如果配置没有启用loger, 则不会展开对应macro, 程序中就不会有对应代码.

使用macro来处理HTTPoison的response请求

代码

  @spec process_result(HTTPoison.Response.t(), String.t()) :: {:ok, t} | {:error, t}
  defmacro process_result(result) do
    quote do
      case unquote(result) do
        {:ok,
         %HTTPoison.Response{
           body: data,
           status_code: status_code,
           request: %HTTPoison.Request{url: request_url}
         }}
        when status_code >= 200 and status_code < 300 ->
          IO.inspect({"Access Success", status_code, request_url})
          {:ok, data}

        {:ok,
         %HTTPoison.Response{
           body: data,
           status_code: 401,
           request: %HTTPoison.Request{url: request_url}
         }} ->
          IO.inspect({"Unauthorized Error", 401, request_url})
          {:error, data}

        {:ok,
         %HTTPoison.Response{
           body: body,
           status_code: status_code,
           request: %HTTPoison.Request{url: request_url}
         }}
        when status_code >= 400 and status_code < 500 ->
          IO.inspect({"Client Error", status_code, request_url, body})
          {:error, body}

        {:ok,
         %HTTPoison.Response{
           body: body,
           status_code: status_code,
           request: %HTTPoison.Request{url: request_url}
         }}
        when status_code >= 500 ->
          IO.inspect({"Server Error", status_code, request_url, body})
          {:error, body}

        {:error, %HTTPoison.Error{reason: reason} = error} ->
          IO.inspect({"HTTPoison.Error", error})
          {:error, reason}

      end
    end
  end

简单的来处理HTTPoison的Reponse, 添加对应处理逻辑