Current section
Files
Jump to
Current section
Files
src/coinglecko@request.erl
-module(coinglecko@request).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/coinglecko/request.gleam").
-export([optional_params/1, flexible_float/0, optional_flexible_float/0, bool_to_string/1, build_get_request/3, handle_response/2, send_and_decode/3]).
-export_type([query_param/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(
" Request building and response handling for the CoinGecko API.\n"
"\n"
" This module is transport-agnostic. It builds HTTP requests and decodes\n"
" responses, but does NOT perform HTTP calls. Users provide their own\n"
" sender function (e.g., httpc.send on Erlang, fetch.send on JavaScript).\n"
).
-type query_param() :: {query_param, binary(), binary()}.
-file("src/coinglecko/request.gleam", 38).
?DOC(
" Convert a list of optional query params to actual params,\n"
" filtering out None values.\n"
).
-spec optional_params(list({binary(), gleam@option:option(binary())})) -> list(query_param()).
optional_params(Params) ->
gleam@list:filter_map(Params, fun(Param) -> case erlang:element(2, Param) of
{some, Value} ->
{ok, {query_param, erlang:element(1, Param), Value}};
none ->
{error, nil}
end end).
-file("src/coinglecko/request.gleam", 51).
?DOC(
" A decoder that accepts both floats and ints, converting ints to floats.\n"
" CoinGecko sometimes returns integers where floats are expected.\n"
).
-spec flexible_float() -> gleam@dynamic@decode:decoder(float()).
flexible_float() ->
gleam@dynamic@decode:one_of(
{decoder, fun gleam@dynamic@decode:decode_float/1},
[begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_int/1},
gleam@dynamic@decode:map(_pipe, fun erlang:float/1)
end]
).
-file("src/coinglecko/request.gleam", 56).
?DOC(" A decoder for optional flexible floats (handles null, missing, int, or float).\n").
-spec optional_flexible_float() -> gleam@dynamic@decode:decoder(gleam@option:option(float())).
optional_flexible_float() ->
gleam@dynamic@decode:optional(flexible_float()).
-file("src/coinglecko/request.gleam", 61).
?DOC(" Convert a Bool to a lowercase string for query parameters.\n").
-spec bool_to_string(boolean()) -> binary().
bool_to_string(B) ->
case B of
true ->
<<"true"/utf8>>;
false ->
<<"false"/utf8>>
end.
-file("src/coinglecko/request.gleam", 70).
?DOC(
" Build a GET request for the CoinGecko API.\n"
" Returns a fully configured Request with auth headers and query params.\n"
).
-spec build_get_request(
coinglecko@client:client(),
binary(),
list(query_param())
) -> {ok, gleam@http@request:request(binary())} |
{error, coinglecko@error:coin_gecko_error()}.
build_get_request(Client, Path, Query) ->
Url = <<(coinglecko@client:base_url(Client))/binary, Path/binary>>,
Query_pairs = gleam@list:map(
Query,
fun(Q) -> {erlang:element(2, Q), erlang:element(3, Q)} end
),
gleam@result:'try'(
begin
_pipe = gleam@http@request:to(Url),
gleam@result:map_error(
_pipe,
fun(_) ->
{request_error,
<<"Failed to construct request for: "/utf8, Url/binary>>}
end
)
end,
fun(Req) ->
Auth_header = case coinglecko@client:api_tier(Client) of
demo ->
<<"x-cg-demo-api-key"/utf8>>;
pro ->
<<"x-cg-pro-api-key"/utf8>>
end,
{ok,
begin
_pipe@1 = Req,
_pipe@2 = gleam@http@request:set_query(_pipe@1, Query_pairs),
_pipe@3 = gleam@http@request:prepend_header(
_pipe@2,
Auth_header,
coinglecko@client:api_key(Client)
),
gleam@http@request:prepend_header(
_pipe@3,
<<"accept"/utf8>>,
<<"application/json"/utf8>>
)
end}
end
).
-file("src/coinglecko/request.gleam", 100).
?DOC(
" Handle an HTTP response: check status code and decode JSON body.\n"
" On non-200 status, tries to parse CoinGecko's error JSON for a message.\n"
).
-spec handle_response(
gleam@http@response:response(binary()),
gleam@dynamic@decode:decoder(ESL)
) -> {ok, ESL} | {error, coinglecko@error:coin_gecko_error()}.
handle_response(Resp, Decoder) ->
case erlang:element(2, Resp) of
200 ->
_pipe = gleam@json:parse(erlang:element(4, Resp), Decoder),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {decode_error, Field@0} end
);
Status ->
{error,
{api_error,
Status,
erlang:element(4, Resp),
coinglecko@error:parse_error_body(erlang:element(4, Resp))}}
end.
-file("src/coinglecko/request.gleam", 119).
?DOC(
" Send a request using the provided sender and decode the response.\n"
" Convenience function that chains build -> send -> decode.\n"
).
-spec send_and_decode(
gleam@http@request:request(binary()),
gleam@dynamic@decode:decoder(ESQ),
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, binary()})
) -> {ok, ESQ} | {error, coinglecko@error:coin_gecko_error()}.
send_and_decode(Req, Decoder, Sender) ->
gleam@result:'try'(
begin
_pipe = Sender(Req),
gleam@result:map_error(
_pipe,
fun(Field@0) -> {http_error, Field@0} end
)
end,
fun(Resp) -> handle_response(Resp, Decoder) end
).