Current section
Files
Jump to
Current section
Files
src/gleeth@rpc@batch.erl
-module(gleeth@rpc@batch).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/rpc/batch.gleam").
-export([new/0, add/3, add_method/3, execute_strings/2, execute_raw/2]).
-export_type([batch/0, batch_request/0, parsed_response/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Batch JSON-RPC requests into a single HTTP call.\n"
"\n"
" Reduces HTTP overhead when making multiple independent queries by sending\n"
" them as a JSON array per the JSON-RPC batch specification.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let assert Ok(results) =\n"
" batch.new()\n"
" |> batch.add(\"eth_blockNumber\", [])\n"
" |> batch.add(\"eth_gasPrice\", [])\n"
" |> batch.execute_strings(provider)\n"
"\n"
" // results: [Ok(\"0x1234\"), Ok(\"0x3b9aca00\")]\n"
" ```\n"
).
-type batch() :: {batch, list(batch_request())}.
-opaque batch_request() :: {batch_request,
binary(),
list(gleam@json:json()),
integer()}.
-type parsed_response() :: {parsed_response,
integer(),
binary(),
binary(),
boolean()}.
-file("src/gleeth/rpc/batch.gleam", 38).
?DOC(" Create an empty batch.\n").
-spec new() -> batch().
new() ->
{batch, []}.
-file("src/gleeth/rpc/batch.gleam", 43).
?DOC(" Add a raw method name and params to the batch.\n").
-spec add(batch(), binary(), list(gleam@json:json())) -> batch().
add(Batch, Method, Params) ->
Id = erlang:length(erlang:element(2, Batch)) + 1,
{batch, [{batch_request, Method, Params, Id} | erlang:element(2, Batch)]}.
-file("src/gleeth/rpc/batch.gleam", 52).
?DOC(" Add a request using an EthMethod value.\n").
-spec add_method(
batch(),
gleeth@rpc@types:eth_method(),
list(gleam@json:json())
) -> batch().
add_method(Batch, Method, Params) ->
add(Batch, gleeth@rpc@types:method_to_string(Method), Params).
-file("src/gleeth/rpc/batch.gleam", 111).
-spec encode_batch(list(batch_request())) -> binary().
encode_batch(Requests) ->
gleam@json:to_string(
gleam@json:array(
Requests,
fun(Req) ->
gleam@json:object(
[{<<"jsonrpc"/utf8>>, gleam@json:string(<<"2.0"/utf8>>)},
{<<"method"/utf8>>,
gleam@json:string(erlang:element(2, Req))},
{<<"params"/utf8>>,
gleam@json:array(
erlang:element(3, Req),
fun(X) -> X end
)},
{<<"id"/utf8>>, gleam@json:int(erlang:element(4, Req))}]
)
end
)
).
-file("src/gleeth/rpc/batch.gleam", 173).
-spec error_message_decoder() -> gleam@dynamic@decode:decoder(binary()).
error_message_decoder() ->
gleam@dynamic@decode:one_of(
begin
gleam@dynamic@decode:field(
<<"message"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Msg) -> gleam@dynamic@decode:success(Msg) end
)
end,
[gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(S) -> S end
)]
).
-file("src/gleeth/rpc/batch.gleam", 144).
-spec response_decoder() -> gleam@dynamic@decode:decoder(parsed_response()).
response_decoder() ->
gleam@dynamic@decode:field(
<<"id"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Id) ->
gleam@dynamic@decode:optional_field(
<<"result"/utf8>>,
<<""/utf8>>,
gleam@dynamic@decode:one_of(
{decoder, fun gleam@dynamic@decode:decode_string/1},
[gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(N) -> gleam@string:inspect(N) end
),
gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_bool/1},
fun(B) -> case B of
true ->
<<"true"/utf8>>;
false ->
<<"false"/utf8>>
end end
),
gleam@dynamic@decode:success(<<""/utf8>>)]
),
fun(Result) ->
gleam@dynamic@decode:optional_field(
<<"error"/utf8>>,
<<""/utf8>>,
error_message_decoder(),
fun(Error_msg) ->
Has_result = Error_msg =:= <<""/utf8>>,
gleam@dynamic@decode:success(
{parsed_response,
Id,
Result,
Error_msg,
Has_result}
)
end
)
end
)
end
).
-file("src/gleeth/rpc/batch.gleam", 133).
-spec parse_batch_response(binary()) -> {ok, list(parsed_response())} |
{error, gleeth@rpc@types:gleeth_error()}.
parse_batch_response(Body) ->
Decoder = gleam@dynamic@decode:list(response_decoder()),
case gleam@json:parse(Body, Decoder) of
{ok, Results} ->
{ok, Results};
{error, _} ->
{error,
{parse_error,
<<"Failed to parse batch JSON-RPC response"/utf8>>}}
end.
-file("src/gleeth/rpc/batch.gleam", 234).
-spec find_response(list(parsed_response()), integer()) -> {ok,
parsed_response()} |
{error, nil}.
find_response(Responses, Id) ->
gleam@list:find(Responses, fun(R) -> erlang:element(2, R) =:= Id end).
-file("src/gleeth/rpc/batch.gleam", 191).
-spec match_and_decode_strings(list(parsed_response()), list(batch_request())) -> list({ok,
binary()} |
{error, gleeth@rpc@types:gleeth_error()}).
match_and_decode_strings(Responses, Requests) ->
gleam@list:map(
Requests,
fun(Req) -> case find_response(Responses, erlang:element(4, Req)) of
{ok, Parsed} ->
case erlang:element(5, Parsed) of
true ->
{ok, erlang:element(3, Parsed)};
false ->
{error,
{rpc_error,
<<"RPC Error: "/utf8,
(erlang:element(4, Parsed))/binary>>}}
end;
{error, _} ->
{error,
{parse_error, <<"No response for batch request"/utf8>>}}
end end
).
-file("src/gleeth/rpc/batch.gleam", 63).
?DOC(
" Execute the batch and decode each response as a string.\n"
" Returns one `Result(String, GleethError)` per request, in order.\n"
" This is the most common use case - batching simple hex-returning RPC calls.\n"
).
-spec execute_strings(batch(), gleeth@provider:provider()) -> {ok,
list({ok, binary()} | {error, gleeth@rpc@types:gleeth_error()})} |
{error, gleeth@rpc@types:gleeth_error()}.
execute_strings(Batch, Provider) ->
Requests = lists:reverse(erlang:element(2, Batch)),
case Requests of
[] ->
{ok, []};
_ ->
Batch_json = encode_batch(Requests),
Rpc_url = gleeth@provider:rpc_url(Provider),
gleam@result:'try'(
gleeth@rpc@client:make_batch_request(Rpc_url, Batch_json),
fun(Response_body) ->
gleam@result:'try'(
parse_batch_response(Response_body),
fun(Parsed) ->
{ok, match_and_decode_strings(Parsed, Requests)}
end
)
end
)
end.
-file("src/gleeth/rpc/batch.gleam", 207).
-spec match_raw(list(parsed_response()), list(batch_request())) -> list({ok,
binary()} |
{error, gleeth@rpc@types:gleeth_error()}).
match_raw(Responses, Requests) ->
gleam@list:map(
Requests,
fun(Req) -> case find_response(Responses, erlang:element(4, Req)) of
{ok, Parsed} ->
case erlang:element(5, Parsed) of
true ->
Body = gleam@json:to_string(
gleam@json:object(
[{<<"jsonrpc"/utf8>>,
gleam@json:string(<<"2.0"/utf8>>)},
{<<"id"/utf8>>,
gleam@json:int(
erlang:element(2, Parsed)
)},
{<<"result"/utf8>>,
gleam@json:string(
erlang:element(3, Parsed)
)}]
)
),
{ok, Body};
false ->
{error,
{rpc_error,
<<"RPC Error: "/utf8,
(erlang:element(4, Parsed))/binary>>}}
end;
{error, _} ->
{error,
{parse_error, <<"No response for batch request"/utf8>>}}
end end
).
-file("src/gleeth/rpc/batch.gleam", 87).
?DOC(
" Execute the batch and return raw JSON response bodies per request.\n"
" Each string is the full `{\"jsonrpc\":\"2.0\",\"id\":N,\"result\":...}` for that\n"
" request, suitable for passing to `response_utils.decode_rpc_response`\n"
" with a custom decoder.\n"
).
-spec execute_raw(batch(), gleeth@provider:provider()) -> {ok,
list({ok, binary()} | {error, gleeth@rpc@types:gleeth_error()})} |
{error, gleeth@rpc@types:gleeth_error()}.
execute_raw(Batch, Provider) ->
Requests = lists:reverse(erlang:element(2, Batch)),
case Requests of
[] ->
{ok, []};
_ ->
Batch_json = encode_batch(Requests),
Rpc_url = gleeth@provider:rpc_url(Provider),
gleam@result:'try'(
gleeth@rpc@client:make_batch_request(Rpc_url, Batch_json),
fun(Response_body) ->
gleam@result:'try'(
parse_batch_response(Response_body),
fun(Parsed) -> {ok, match_raw(Parsed, Requests)} end
)
end
)
end.