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