diff --git a/src/api-objects/src/apikeysprovider.cpp b/src/api-objects/src/apikeysprovider.cpp index 5019b88e..520891e2 100644 --- a/src/api-objects/src/apikeysprovider.cpp +++ b/src/api-objects/src/apikeysprovider.cpp @@ -8,13 +8,14 @@ #include "accountowner.hpp" #include "apikey.hpp" #include "cct_exception.hpp" -#include "cct_json-container.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "exchangename.hpp" #include "exchangesecretsinfo.hpp" #include "file.hpp" +#include "read-json.hpp" #include "runmodes.hpp" +#include "secret-schema.hpp" namespace cct::api { namespace { @@ -73,61 +74,48 @@ APIKeysProvider::APIKeysPerExchange APIKeysProvider::ParseAPIKeys(std::string_vi const ExchangeSecretsInfo& exchangeSecretsInfo, settings::RunMode runMode) { APIKeysProvider::APIKeysPerExchange apiKeysPerExchange; + if (exchangeSecretsInfo.allExchangesWithoutSecrets()) { log::info("Not loading private keys, using only public exchanges"); - } else { - std::string_view secretFileName = GetSecretFileName(runMode); - File secretsFile(dataDir, File::Type::kSecret, secretFileName, - settings::AreTestKeysRequested(runMode) ? File::IfError::kThrow : File::IfError::kNoThrow); - json::container jsonData = secretsFile.readAllJson(); - bool atLeastOneKeyFound = false; - for (auto& [publicExchangeName, keyObj] : jsonData.items()) { - const auto& exchangesWithoutSecrets = exchangeSecretsInfo.exchangesWithoutSecrets(); - if (std::ranges::find(exchangesWithoutSecrets, ExchangeName(publicExchangeName)) != - exchangesWithoutSecrets.end()) { - log::info("Not loading {} private keys as requested", publicExchangeName); + return apiKeysPerExchange; + } + + std::string_view secretFileName = GetSecretFileName(runMode); + const auto throwOrNoThrow = settings::AreTestKeysRequested(runMode) ? File::IfError::kThrow : File::IfError::kNoThrow; + File secretsFile(dataDir, File::Type::kSecret, secretFileName, throwOrNoThrow); + + schema::APIKeysPerExchangeMap apiKeysPerExchangeMap; + + ReadJsonOrThrow(secretsFile.readAll(), apiKeysPerExchangeMap); + + const auto& exchangesWithoutSecrets = exchangeSecretsInfo.exchangesWithoutSecrets(); + + bool atLeastOneKeyFound = false; + for (auto& [exchangeNameEnum, apiKeys] : apiKeysPerExchangeMap) { + auto publicExchangeName = kSupportedExchanges[static_cast(exchangeNameEnum)]; + if (std::ranges::any_of(exchangesWithoutSecrets, [exchangeNameEnum](const auto& exchangeName) { + return exchangeName.exchangeNameEnum() == exchangeNameEnum; + })) { + log::debug("Not loading {} private keys as requested", publicExchangeName); + continue; + } + + for (auto& [keyName, apiKey] : apiKeys) { + if (apiKey.key.empty() || apiKey.priv.empty()) { + log::error("Wrong format for secret.json file. It should contain at least fields 'key' and 'private'"); continue; } - ExchangeNameEnum exchangeNameEnum = static_cast( - std::find(std::begin(kSupportedExchanges), std::end(kSupportedExchanges), publicExchangeName) - - std::begin(kSupportedExchanges)); - - for (auto& [name, keySecretObj] : keyObj.items()) { - auto keyIt = keySecretObj.find("key"); - auto privateIt = keySecretObj.find("private"); - if (keyIt == keySecretObj.end() || privateIt == keySecretObj.end()) { - log::error("Wrong format for secret.json file. It should contain at least fields 'key' and 'private'"); - continue; - } - string passphrase; - auto passphraseIt = keySecretObj.find("passphrase"); - if (passphraseIt != keySecretObj.end()) { - passphrase = std::move(passphraseIt->get_ref()); - } - std::string_view ownerEnName; - std::string_view ownerKoName; - auto accountOwnerPartIt = keySecretObj.find("accountOwner"); - if (accountOwnerPartIt != keySecretObj.end()) { - auto ownerEnNameIt = accountOwnerPartIt->find("enName"); - if (ownerEnNameIt != accountOwnerPartIt->end()) { - ownerEnName = ownerEnNameIt->get(); - } - auto ownerKoNameIt = accountOwnerPartIt->find("koName"); - if (ownerKoNameIt != accountOwnerPartIt->end()) { - ownerKoName = ownerKoNameIt->get(); - } - } - - apiKeysPerExchange[static_cast(exchangeNameEnum)].emplace_back( - publicExchangeName, name, std::move(keyIt->get_ref()), std::move(privateIt->get_ref()), - std::move(passphrase), AccountOwner(ownerEnName, ownerKoName)); - atLeastOneKeyFound = true; - } - } - if (!atLeastOneKeyFound) { - log::warn("No private api keys file '{}' found. Only public exchange queries will be supported", secretFileName); + + apiKeysPerExchange[static_cast(exchangeNameEnum)].emplace_back( + publicExchangeName, keyName, std::move(apiKey.key), std::move(apiKey.priv), std::move(apiKey.passphrase), + AccountOwner(apiKey.accountOwner.enName, apiKey.accountOwner.koName)); + + atLeastOneKeyFound = true; } } + if (!atLeastOneKeyFound) { + log::warn("No private api keys file '{}' found. Only public exchange queries will be supported", secretFileName); + } return apiKeysPerExchange; } diff --git a/src/basic-objects/include/market.hpp b/src/basic-objects/include/market.hpp index a9ff8ea3..b61c3221 100644 --- a/src/basic-objects/include/market.hpp +++ b/src/basic-objects/include/market.hpp @@ -163,7 +163,7 @@ struct from { static void op(auto &&value, is_context auto &&, It &&it, End &&end) noexcept { // used as a value. As a key, the first quote will not be present. auto endIt = std::find(*it == '"' ? ++it : it, end, '"'); - value = std::string_view(it, endIt); + value = ::cct::Market(std::string_view(it, endIt)); it = ++endIt; } }; diff --git a/src/basic-objects/test/market_test.cpp b/src/basic-objects/test/market_test.cpp index 36322450..2233091f 100644 --- a/src/basic-objects/test/market_test.cpp +++ b/src/basic-objects/test/market_test.cpp @@ -3,6 +3,7 @@ #include #include "cct_exception.hpp" +#include "cct_json-serialization.hpp" #include "currencycode.hpp" namespace cct { @@ -65,4 +66,46 @@ TEST(MarketTest, StrLen) { market = Market("1INCH", "EUR", Market::Type::kFiatConversionMarket); EXPECT_EQ(market.strLen(), 10); } + +struct Foo { + bool operator==(const Foo &) const noexcept = default; + + Market market; +}; + +TEST(MarketTest, JsonSerializationValue) { + Foo foo{Market{"DOGE", "BTC"}}; + + string buffer; + auto res = json::write(foo, buffer); // NOLINT(readability-implicit-bool-conversion) + + EXPECT_FALSE(res); + + EXPECT_EQ(buffer, R"({"market":"DOGE-BTC"})"); +} + +using MarketMap = std::map; + +TEST(MarketTest, JsonSerializationKey) { + MarketMap map{{Market{"DOGE", "BTC"}, true}, {Market{"BTC", "ETH"}, false}}; + + string buffer; + auto res = json::write(map, buffer); // NOLINT(readability-implicit-bool-conversion) + + EXPECT_FALSE(res); + + EXPECT_EQ(buffer, R"({"BTC-ETH":false,"DOGE-BTC":true})"); +} + +TEST(MarketTest, JsonDeserialization) { + Foo foo; + + // NOLINTNEXTLINE(readability-implicit-bool-conversion) + auto ec = json::read(foo, R"({"market":"DOGE-ETH"})"); + + ASSERT_FALSE(ec); + + EXPECT_EQ(foo, Foo{Market("DOGE", "ETH")}); +} + } // namespace cct diff --git a/src/objects/src/coincenterinfo.cpp b/src/objects/src/coincenterinfo.cpp index e7b3783b..85d7fc3d 100644 --- a/src/objects/src/coincenterinfo.cpp +++ b/src/objects/src/coincenterinfo.cpp @@ -6,7 +6,6 @@ #include #include "cct_exception.hpp" -#include "cct_json-container.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "currencycode.hpp" @@ -14,6 +13,7 @@ #include "loadconfiguration.hpp" #include "logginginfo.hpp" #include "monitoringinfo.hpp" +#include "read-json.hpp" #include "reader.hpp" #include "runmodes.hpp" #include "toupperlower-string.hpp" @@ -28,26 +28,17 @@ namespace cct { namespace { -CoincenterInfo::CurrencyEquivalentAcronymMap ComputeCurrencyEquivalentAcronymMap( - const Reader& currencyAcronymsTranslatorReader) { - json::container jsonData = currencyAcronymsTranslatorReader.readAllJson(); + +CoincenterInfo::CurrencyEquivalentAcronymMap ComputeCurrencyEquivalentAcronymMap(const Reader& reader) { CoincenterInfo::CurrencyEquivalentAcronymMap map; - map.reserve(jsonData.size()); - for (const auto& [key, value] : jsonData.items()) { - log::trace("Currency {} <=> {}", key, value.get()); - map.insert_or_assign(CurrencyCode(key), value.get()); - } + ReadJsonOrThrow(reader.readAll(), map); return map; } -CoincenterInfo::StableCoinsMap ComputeStableCoinsMap(const Reader& stableCoinsReader) { - json::container jsonData = stableCoinsReader.readAllJson(); - CoincenterInfo::StableCoinsMap ret; - for (const auto& [key, value] : jsonData.items()) { - log::trace("Stable Crypto {} <=> {}", key, value.get()); - ret.emplace(key, value.get()); - } - return ret; +CoincenterInfo::StableCoinsMap ComputeStableCoinsMap(const Reader& reader) { + CoincenterInfo::StableCoinsMap map; + ReadJsonOrThrow(reader.readAll(), map); + return map; } #ifdef CCT_ENABLE_PROMETHEUS @@ -73,12 +64,11 @@ CoincenterInfo::CoincenterInfo(settings::RunMode runMode, const LoadConfiguratio ? new MetricGatewayType(monitoringInfo) : nullptr), _monitoringInfo(std::move(monitoringInfo)) { - json::container jsonData = currencyPrefixesReader.readAllJson(); - for (auto& [prefix, acronym_prefix] : jsonData.items()) { - log::trace("Currency prefix {} <=> {}", prefix, acronym_prefix.get()); + ReadJsonOrThrow(currencyPrefixesReader.readAll(), _currencyPrefixAcronymMap); + for (auto& [prefix, acronym_prefix] : _currencyPrefixAcronymMap) { + log::trace("Currency prefix {} <=> {}", prefix, acronym_prefix); _minPrefixLen = std::min(_minPrefixLen, static_cast(prefix.length())); _maxPrefixLen = std::max(_maxPrefixLen, static_cast(prefix.length())); - _currencyPrefixAcronymMap.insert_or_assign(ToUpper(prefix), std::move(acronym_prefix.get_ref())); } } diff --git a/src/schema/include/read-json.hpp b/src/schema/include/read-json.hpp index 89dd40b7..f6ee81cc 100644 --- a/src/schema/include/read-json.hpp +++ b/src/schema/include/read-json.hpp @@ -15,6 +15,10 @@ constexpr auto kJsonOptions = template void ReadJsonOrThrow(std::string_view strContent, auto &outObject) { + if (strContent.empty()) { + return; + } + auto ec = json::read(outObject, strContent); if (ec) { diff --git a/src/schema/include/secret-schema.hpp b/src/schema/include/secret-schema.hpp new file mode 100644 index 00000000..a4665039 --- /dev/null +++ b/src/schema/include/secret-schema.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "cct_const.hpp" +#include "cct_json-serialization.hpp" +#include "cct_string.hpp" + +namespace cct::schema { + +struct AccountOwner { + string enName; + string koName; + + using trivially_relocatable = is_trivially_relocatable::type; +}; + +struct APIKey { + string key; + string priv; // private is a reserved keyword - we override the json field name below + string passphrase; + AccountOwner accountOwner; + + using trivially_relocatable = is_trivially_relocatable::type; +}; + +using APIKeys = std::unordered_map; + +using APIKeysPerExchangeMap = std::unordered_map; + +} // namespace cct::schema + +template <> +struct glz::meta<::cct::schema::APIKey> { + using V = ::cct::schema::APIKey; + static constexpr auto value = + object("key", &V::key, "private", &V::priv, "passphrase", &V::passphrase, "accountOwner", &V::accountOwner); +}; \ No newline at end of file