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