coinprices/PriceResponseParsers.cpp¶
Namespaces¶
| Name |
|---|
| sgns |
Functions¶
| Name | |
|---|---|
| PriceResult< std::vector< PriceQuote > > | ParseCoinGeckoSimplePrice(const std::string & body, const std::vector< std::string > & tokenIds, const std::string & currency, std::chrono::system_clock::time_point fetchTime) Parse a CoinGecko /simple/price 200-body into quotes. |
| PriceResult< std::vector< PriceQuote > > | ParseGnusPriceEnvelope(const std::string & body, const std::vector< std::string > & tokenIds, const std::string & currency) Parse a token.gnus.ai price envelope into quotes. |
Functions Documentation¶
function ParseCoinGeckoSimplePrice¶
PriceResult< std::vector< PriceQuote > > ParseCoinGeckoSimplePrice(
const std::string & body,
const std::vector< std::string > & tokenIds,
const std::string & currency,
std::chrono::system_clock::time_point fetchTime
)
Parse a CoinGecko /simple/price 200-body into quotes.
Parameters:
- body The 200 response body
- tokenIds Requested ids — quotes are emitted for requested ids present in the body; unknown ids stay absent, not errors (D-09)
- currency Target currency
- fetchTime D-14 fetch time stamped on every quote
Return: One PriceQuote per requested id found in the body, or NoDataFound when none matched, or JsonParseError on malformed JSON (status 0 — the HTTP status belongs to the facade layer)
function ParseGnusPriceEnvelope¶
PriceResult< std::vector< PriceQuote > > ParseGnusPriceEnvelope(
const std::string & body,
const std::vector< std::string > & tokenIds,
const std::string & currency
)
Parse a token.gnus.ai price envelope into quotes.
Parameters:
- body The 200 response body (Phase-1 envelope.ts contract)
- tokenIds Requested ids — quotes are emitted for requested ids present in
prices; absent ids stay absent, not errors (D-09) - currency Expected target currency (envelope
currency)
Return: One PriceQuote per requested id found in prices, or NoDataFound when none matched, or JsonParseError on malformed JSON / an unknown source string (status 0 — the HTTP status belongs to the facade layer)
Note:
- LANDMINE: envelope
fetchedAtis epoch SECONDS (never ms) and is the max across the returned ids, SHARED by all quotes — do not invent per-id timestamps (Phase-1 D-11). - LANDMINE: the envelope source names the upstream that PRODUCED the price, not the serving tier — "coingecko" maps to PriceSource::CoinGecko and "coingecko-cache" to PriceSource::GnusPriceService (Phase-1 D-06). Counterintuitive but contractual; do NOT "fix" this.
Source code¶
#include "PriceResponseParsers.hpp"
#include <rapidjson/document.h>
#include <utility>
namespace sgns
{
PriceResult<std::vector<PriceQuote>> ParseCoinGeckoSimplePrice( const std::string &body,
const std::vector<std::string> &tokenIds,
const std::string ¤cy,
std::chrono::system_clock::time_point fetchTime )
{
rapidjson::Document document;
document.Parse( body.c_str() );
if ( document.HasParseError() )
{
return outcome::failure( PriceFetchFailure{ PriceFetchError::JsonParseError, 0 } );
}
std::vector<PriceQuote> quotes;
for ( const auto &id : tokenIds )
{
// IsNumber covers int and double literals — CoinGecko may
// serialize whole-number prices without a decimal point
if ( document.IsObject() && document.HasMember( id.c_str() ) && document[id.c_str()].IsObject()
&& document[id.c_str()].HasMember( currency.c_str() )
&& document[id.c_str()][currency.c_str()].IsNumber() )
{
PriceQuote quote;
quote.asset = id;
quote.currency = currency;
quote.price = document[id.c_str()][currency.c_str()].GetDouble();
quote.timestamp = fetchTime; // D-14: fetch time
quote.source = PriceSource::CoinGecko;
quote.stale = false;
quotes.push_back( std::move( quote ) );
}
// Unknown ids stay absent — partial coverage, not an error
}
if ( quotes.empty() )
{
return outcome::failure( PriceFetchFailure{ PriceFetchError::NoDataFound, 0 } );
}
return outcome::success( std::move( quotes ) );
}
PriceResult<std::vector<PriceQuote>> ParseGnusPriceEnvelope( const std::string &body,
const std::vector<std::string> &tokenIds,
const std::string ¤cy )
{
rapidjson::Document document;
document.Parse( body.c_str() );
if ( document.HasParseError() || !document.IsObject() )
{
return outcome::failure( PriceFetchFailure{ PriceFetchError::JsonParseError, 0 } );
}
if ( !document.HasMember( "currency" ) || !document["currency"].IsString()
|| !document.HasMember( "prices" ) || !document["prices"].IsObject()
|| !document.HasMember( "fetchedAt" ) || !document["fetchedAt"].IsInt64()
|| !document.HasMember( "source" ) || !document["source"].IsString()
|| !document.HasMember( "stale" ) || !document["stale"].IsBool() )
{
return outcome::failure( PriceFetchFailure{ PriceFetchError::JsonParseError, 0 } );
}
// LANDMINE (Phase-1 D-11): fetchedAt is epoch SECONDS and the max
// across the returned ids — one shared timestamp for all quotes.
const auto fetchedAt = std::chrono::system_clock::time_point{
std::chrono::seconds{ document["fetchedAt"].GetInt64() }
};
// LANDMINE (Phase-1 D-06): exactly two source kinds exist; the
// envelope source names the producing upstream, NOT the serving
// tier. Unknown source strings are malformed, not silently mapped.
const std::string source( document["source"].GetString() );
PriceSource quoteSource;
if ( source == "coingecko" )
{
quoteSource = PriceSource::CoinGecko;
}
else if ( source == "coingecko-cache" )
{
quoteSource = PriceSource::GnusPriceService;
}
else
{
return outcome::failure( PriceFetchFailure{ PriceFetchError::JsonParseError, 0 } );
}
const bool envelopeStale = document["stale"].GetBool();
const auto &prices = document["prices"];
std::vector<PriceQuote> quotes;
for ( const auto &id : tokenIds )
{
if ( prices.HasMember( id.c_str() ) && prices[id.c_str()].IsNumber() )
{
PriceQuote quote;
quote.asset = id;
quote.currency = currency;
quote.price = prices[id.c_str()].GetDouble();
quote.timestamp = fetchedAt; // shared max-of-ids timestamp (D-11)
quote.source = quoteSource;
quote.stale = envelopeStale;
quotes.push_back( std::move( quote ) );
}
// Absent ids stay absent — partial coverage, not an error (D-09)
}
if ( quotes.empty() )
{
return outcome::failure( PriceFetchFailure{ PriceFetchError::NoDataFound, 0 } );
}
return outcome::success( std::move( quotes ) );
}
} // namespace sgns
Updated on 2026-10-09 at 04:02:50 +0000