defmodule Web3.Middleware.Parser do @moduledoc """ Wrapping before_dispatch and after_dispatch the request to JSON RPC API. NOTE: Awkward naming """ @behaviour Web3.Middleware require Logger alias Web3.Middleware.Pipeline import Pipeline def before_dispatch(%Pipeline{method: :__skip_parser__} = pipeline), do: pipeline def before_dispatch(%Pipeline{method: method, args: [head | tail]} = pipeline) when is_list(head) do id_to_params = head |> id_to_params() request = id_to_params |> Enum.map(fn {id, item} -> %{id: id, jsonrpc: "2.0", method: method, params: [item | tail]} end) pipeline |> assign(:id_to_params, id_to_params) |> set_request(request) end def before_dispatch(%Pipeline{method: method, args: args} = pipeline) do request = %{id: 1, jsonrpc: "2.0", method: method, params: args} pipeline |> set_request(request) end def after_dispatch(%Pipeline{method: :__skip_parser__} = pipeline), do: pipeline def after_dispatch(%Pipeline{assigns: %{id_to_params: id_to_params}, response: {:ok, response}, return_fn: return_fn} = pipeline) when is_list(response) do result = from_responses(response, id_to_params, return_fn) pipeline |> respond({:ok, result}) end def after_dispatch(%Pipeline{response: {:ok, response}, return_fn: return_fn} = pipeline) do result = response |> decode_value(return_fn) |> unwrap() pipeline |> respond({:ok, result}) end def after_failure(%Pipeline{method: :__skip_parser__} = pipeline), do: pipeline def after_failure(%Pipeline{} = pipeline) do Logger.info("Request Failed") pipeline end defp from_responses(responses, id_to_params, return_fn) do responses |> Enum.map(&from_response(&1, id_to_params, return_fn)) |> Enum.reduce( %{params_list: [], errors: []}, fn {:ok, params}, %{params_list: params_list} = acc -> %{acc | params_list: [params | params_list]} {:error, reason}, %{errors: errors} = acc -> %{acc | errors: [reason | errors]} end ) end defp from_response(%{id: id, result: result}, id_to_params, return_fn) when is_map(id_to_params) do param = Map.fetch!(id_to_params, id) decode_data = decode_value(result, return_fn) {:ok, {param, decode_data}} end defp id_to_params(params_list) do params_list |> Stream.with_index() |> Enum.into(%{}, fn {params, id} -> {id, params} end) end def decode_value(nil, _return_types), do: nil def decode_value(return_value, :raw), do: return_value def decode_value(return_value, :integer), do: String.to_integer(return_value) def decode_value("0x" <> return_value, :hex), do: String.to_integer(return_value, 16) def decode_value(return_value, decoder) when is_function(decoder, 1), do: decoder.(return_value) def decode_value("0x" <> return_value, return_types) do {:ok, data} = Base.decode16(return_value, case: :mixed) Web3.ABI.TypeDecoder.decode_data(data, return_types) end def unwrap([]), do: nil def unwrap({:ok, value}), do: unwrap(value) def unwrap([value]), do: value def unwrap(values) when is_list(values), do: List.to_tuple(values) def unwrap(value), do: value end