-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.