mirror of
https://github.com/XRPLF/clio.git
synced 2026-02-24 07:42:34 +00:00
Compare commits
5 Commits
dependabot
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16030d1d81 | ||
|
|
1220d632b5 | ||
|
|
e9052bcd80 | ||
|
|
c1f6a6eb31 | ||
|
|
af736717fc |
2
.github/workflows/reusable-build.yml
vendored
2
.github/workflows/reusable-build.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
|
||||
4
.github/workflows/reusable-test.yml
vendored
4
.github/workflows/reusable-test.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Delete and start colima (macOS)
|
||||
# This is a temporary workaround for colima issues on macOS runners
|
||||
|
||||
14
.github/workflows/update-docker-ci.yml
vendored
14
.github/workflows/update-docker-ci.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/gcc/**"
|
||||
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/compilers/clang/**"
|
||||
|
||||
@@ -223,7 +223,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
@@ -285,7 +285,7 @@ jobs:
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4
|
||||
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1
|
||||
with:
|
||||
files: "docker/tools/**"
|
||||
|
||||
|
||||
@@ -104,10 +104,11 @@ CliArgs::parse(int argc, char const* argv[])
|
||||
|
||||
if (parsed.contains("migrate")) {
|
||||
auto const opt = parsed["migrate"].as<std::string>();
|
||||
if (opt == "status")
|
||||
if (opt == "status") {
|
||||
return Action{Action::Migrate{
|
||||
.configPath = std::move(configPath), .subCmd = MigrateSubCmd::status()
|
||||
}};
|
||||
}
|
||||
return Action{Action::Migrate{
|
||||
.configPath = std::move(configPath), .subCmd = MigrateSubCmd::migration(opt)
|
||||
}};
|
||||
|
||||
@@ -85,10 +85,11 @@ Handle::disconnect() const
|
||||
Handle::FutureType
|
||||
Handle::asyncReconnect(std::string_view keyspace) const
|
||||
{
|
||||
if (auto rc = asyncDisconnect().await(); not rc) // sync
|
||||
if (auto rc = asyncDisconnect().await(); not rc) { // sync
|
||||
throw std::logic_error(
|
||||
"Reconnect to keyspace '" + std::string{keyspace} + "' failed: " + rc.error()
|
||||
);
|
||||
}
|
||||
return asyncConnect(keyspace);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,10 +96,11 @@ public:
|
||||
{
|
||||
using std::to_string;
|
||||
auto throwErrorIfNeeded = [idx](CassError rc, std::string_view label) {
|
||||
if (rc != CASS_OK)
|
||||
if (rc != CASS_OK) {
|
||||
throw std::logic_error(
|
||||
fmt::format("[{}] at idx {}: {}", label, idx, cass_error_desc(rc))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
auto bindBytes = [this, idx](auto const* data, size_t size) {
|
||||
|
||||
@@ -203,10 +203,11 @@ LoadBalancer::LoadBalancer(
|
||||
LOG(log_.info()) << "Added etl source - " << sources_.back()->toString();
|
||||
}
|
||||
|
||||
if (!etlState_)
|
||||
if (!etlState_) {
|
||||
checkOnETLFailure(
|
||||
"Failed to fetch ETL state from any source. Please check the configuration and network"
|
||||
);
|
||||
}
|
||||
|
||||
if (sources_.empty())
|
||||
checkOnETLFailure("No ETL sources configured. Please check the configuration");
|
||||
|
||||
@@ -137,21 +137,19 @@ getNFTokenMintData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
|
||||
|
||||
// Find the first NFT ID that doesn't match. We're looking for an
|
||||
// added NFT, so the one we want will be the mismatch in finalIDs.
|
||||
// NOLINTNEXTLINE(modernize-use-ranges)
|
||||
auto const diff =
|
||||
std::mismatch(finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end());
|
||||
auto const [finalMismatch, prevMismatch] = std::ranges::mismatch(finalIDs, prevIDs);
|
||||
|
||||
// There should always be a difference so the returned finalIDs
|
||||
// iterator should never be end(). But better safe than sorry.
|
||||
if (finalIDs.size() != prevIDs.size() + 1 || diff.first == finalIDs.end() || !owner) {
|
||||
if (finalIDs.size() != prevIDs.size() + 1 || finalMismatch == finalIDs.end() || !owner) {
|
||||
throw std::runtime_error(
|
||||
fmt::format(" - unexpected NFTokenMint data in tx {}", strHex(sttx.getTransactionID()))
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
{NFTTransactionsData(*diff.first, txMeta, sttx.getTransactionID())},
|
||||
NFTsData(*diff.first, *owner, sttx.getFieldVL(ripple::sfURI), txMeta)
|
||||
{NFTTransactionsData(*finalMismatch, txMeta, sttx.getTransactionID())},
|
||||
NFTsData(*finalMismatch, *owner, sttx.getFieldVL(ripple::sfURI), txMeta)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -402,10 +400,11 @@ getNFTDataFromObj(std::uint32_t const seq, std::string const& key, std::string c
|
||||
auto const owner = ripple::AccountID::fromVoid(key.data());
|
||||
std::vector<NFTsData> nfts;
|
||||
|
||||
for (ripple::STObject const& node : sle.getFieldArray(ripple::sfNFTokens))
|
||||
for (ripple::STObject const& node : sle.getFieldArray(ripple::sfNFTokens)) {
|
||||
nfts.emplace_back(
|
||||
node.getFieldH256(ripple::sfNFTokenID), seq, owner, node.getFieldVL(ripple::sfURI)
|
||||
);
|
||||
}
|
||||
|
||||
return nfts;
|
||||
}
|
||||
|
||||
@@ -173,10 +173,11 @@ public:
|
||||
// send entire vector path
|
||||
{
|
||||
auto const expand = [&](auto&& p) {
|
||||
if constexpr (requires { p.onInitialObjects(seq, data, lastKey); })
|
||||
if constexpr (requires { p.onInitialObjects(seq, data, lastKey); }) {
|
||||
executeIfAllowed(p, [seq, &data, &lastKey](auto& p) {
|
||||
p.onInitialObjects(seq, data, lastKey);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
std::apply([&expand](auto&&... xs) { (expand(xs), ...); }, store_);
|
||||
@@ -212,10 +213,11 @@ public:
|
||||
{
|
||||
auto const expand = [&]<typename P>(P&& p, model::Transaction const& tx) {
|
||||
if constexpr (requires { p.onInitialTransaction(data.seq, tx); }) {
|
||||
if (std::decay_t<P>::spec::wants(tx.type))
|
||||
if (std::decay_t<P>::spec::wants(tx.type)) {
|
||||
executeIfAllowed(p, [&data, &tx](auto& p) {
|
||||
p.onInitialTransaction(data.seq, tx);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -153,10 +153,11 @@ fetchCredentialArray(
|
||||
if (credentials::checkExpired(sleCred, info))
|
||||
return Error{Status{RippledError::rpcBAD_CREDENTIALS, "credentials are expired"}};
|
||||
|
||||
if (sleCred.getAccountID(ripple::sfSubject) != srcAcc)
|
||||
if (sleCred.getAccountID(ripple::sfSubject) != srcAcc) {
|
||||
return Error{Status{
|
||||
RippledError::rpcBAD_CREDENTIALS, "credentials don't belong to the root account"
|
||||
}};
|
||||
}
|
||||
|
||||
auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
|
||||
credential.setAccountID(ripple::sfIssuer, sleCred.getAccountID(ripple::sfIssuer));
|
||||
|
||||
@@ -223,10 +223,11 @@ makeError(Status const& status)
|
||||
auto res = visit(
|
||||
util::OverloadSet{
|
||||
[&status, &wrapOptional](RippledError err) {
|
||||
if (err == ripple::rpcUNKNOWN)
|
||||
if (err == ripple::rpcUNKNOWN) {
|
||||
return boost::json::object{
|
||||
{"error", status.message}, {"type", "response"}, {"status", "error"}
|
||||
};
|
||||
}
|
||||
|
||||
return makeError(err, wrapOptional(status.error), wrapOptional(status.message));
|
||||
},
|
||||
|
||||
@@ -62,10 +62,11 @@ makeWsContext(
|
||||
commandValue = request.at("command");
|
||||
}
|
||||
|
||||
if (!commandValue.is_string())
|
||||
if (!commandValue.is_string()) {
|
||||
return Error{
|
||||
{ClioError::RpcCommandIsMissing, "Method/Command is not specified or is not a string."}
|
||||
};
|
||||
}
|
||||
|
||||
auto const apiVersion = apiVersionParser.get().parse(request);
|
||||
if (!apiVersion)
|
||||
@@ -99,11 +100,12 @@ makeHttpContext(
|
||||
|
||||
auto const command = boost::json::value_to<std::string>(request.at("method"));
|
||||
|
||||
if (command == "subscribe" || command == "unsubscribe")
|
||||
if (command == "subscribe" || command == "unsubscribe") {
|
||||
return Error{
|
||||
{RippledError::rpcBAD_SYNTAX,
|
||||
"Subscribe and unsubscribe are only allowed for websocket."}
|
||||
};
|
||||
}
|
||||
|
||||
if (!request.at("params").is_array())
|
||||
return Error{{ClioError::RpcParamsUnparsable, "Missing params array."}};
|
||||
|
||||
@@ -694,10 +694,11 @@ traverseOwnedNodes(
|
||||
auto const [nextNFTPage, nftsCount] = cursorMaybe.value();
|
||||
|
||||
// if limit reach , we return the next page and max as marker
|
||||
if (nftsCount >= limit)
|
||||
if (nftsCount >= limit) {
|
||||
return AccountCursor{
|
||||
.index = nextNFTPage, .hint = std::numeric_limits<uint32_t>::max()
|
||||
};
|
||||
}
|
||||
|
||||
// adjust limit ,continue traversing owned nodes
|
||||
limit -= nftsCount;
|
||||
@@ -772,10 +773,11 @@ traverseOwnedNodes(
|
||||
for (;;) {
|
||||
auto const ownerDir = backend.fetchLedgerObject(currentIndex.key, sequence, yield);
|
||||
|
||||
if (!ownerDir)
|
||||
if (!ownerDir) {
|
||||
return std::unexpected{
|
||||
Status(ripple::rpcINVALID_PARAMS, "Owner directory not found.")
|
||||
};
|
||||
}
|
||||
|
||||
ripple::SerialIter ownedDirIt{ownerDir->data(), ownerDir->size()};
|
||||
ripple::SLE const ownedDirSle{ownedDirIt, currentIndex.key};
|
||||
@@ -1428,25 +1430,29 @@ parseBook(
|
||||
std::expected<ripple::Book, Status>
|
||||
parseBook(boost::json::object const& request)
|
||||
{
|
||||
if (!request.contains("taker_pays"))
|
||||
if (!request.contains("taker_pays")) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "Missing field 'taker_pays'"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!request.contains("taker_gets"))
|
||||
if (!request.contains("taker_gets")) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "Missing field 'taker_gets'"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!request.at("taker_pays").is_object())
|
||||
if (!request.at("taker_pays").is_object()) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "Field 'taker_pays' is not an object"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!request.at("taker_gets").is_object())
|
||||
if (!request.at("taker_gets").is_object()) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "Field 'taker_gets' is not an object"}
|
||||
};
|
||||
}
|
||||
|
||||
auto takerPays = request.at("taker_pays").as_object();
|
||||
if (!takerPays.contains("currency"))
|
||||
@@ -1482,10 +1488,11 @@ parseBook(boost::json::object const& request)
|
||||
|
||||
ripple::AccountID payIssuer;
|
||||
if (takerPays.contains("issuer")) {
|
||||
if (!takerPays.at("issuer").is_string())
|
||||
if (!takerPays.at("issuer").is_string()) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "takerPaysIssuerNotString"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
payIssuer, boost::json::value_to<std::string>(takerPays.at("issuer"))
|
||||
@@ -1512,18 +1519,20 @@ parseBook(boost::json::object const& request)
|
||||
}};
|
||||
}
|
||||
|
||||
if ((!isXRP(payCurrency)) && (!takerPays.contains("issuer")))
|
||||
if ((!isXRP(payCurrency)) && (!takerPays.contains("issuer"))) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcSRC_ISR_MALFORMED, "Missing non-XRP issuer."}
|
||||
};
|
||||
}
|
||||
|
||||
ripple::AccountID getIssuer;
|
||||
|
||||
if (takerGets.contains("issuer")) {
|
||||
if (!takerGets["issuer"].is_string())
|
||||
if (!takerGets["issuer"].is_string()) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "taker_gets.issuer should be string"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
getIssuer, boost::json::value_to<std::string>(takerGets.at("issuer"))
|
||||
|
||||
@@ -78,10 +78,11 @@ public:
|
||||
[[nodiscard]] static std::optional<Warning>
|
||||
check(boost::json::value const& value, std::string_view key)
|
||||
{
|
||||
if (value.is_object() and value.as_object().contains(key))
|
||||
if (value.is_object() and value.as_object().contains(key)) {
|
||||
return Warning{
|
||||
WarningCode::WarnRpcDeprecated, fmt::format("Field '{}' is deprecated.", key)
|
||||
};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,10 +51,11 @@ namespace rpc::validation {
|
||||
[[nodiscard]] MaybeError
|
||||
Required::verify(boost::json::value const& value, std::string_view key)
|
||||
{
|
||||
if (not value.is_object() or not value.as_object().contains(key))
|
||||
if (not value.is_object() or not value.as_object().contains(key)) {
|
||||
return Error{Status{
|
||||
RippledError::rpcINVALID_PARAMS, "Required field '" + std::string{key} + "' missing"
|
||||
}};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -127,17 +128,19 @@ CustomValidator CustomValidators::ledgerIndexValidator =
|
||||
|
||||
CustomValidator CustomValidators::ledgerTypeValidator =
|
||||
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_string())
|
||||
if (!value.is_string()) {
|
||||
return Error{Status{
|
||||
RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', not string.", key)
|
||||
}};
|
||||
}
|
||||
|
||||
auto const type =
|
||||
util::LedgerTypes::getLedgerEntryTypeFromStr(boost::json::value_to<std::string>(value));
|
||||
if (type == ripple::ltANY)
|
||||
if (type == ripple::ltANY) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}'.", key)}
|
||||
};
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
}};
|
||||
@@ -185,18 +188,20 @@ CustomValidator CustomValidators::accountMarkerValidator =
|
||||
|
||||
CustomValidator CustomValidators::accountTypeValidator =
|
||||
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_string())
|
||||
if (!value.is_string()) {
|
||||
return Error{Status{
|
||||
RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', not string.", key)
|
||||
}};
|
||||
}
|
||||
|
||||
auto const type = util::LedgerTypes::getAccountOwnedLedgerTypeFromStr(
|
||||
boost::json::value_to<std::string>(value)
|
||||
);
|
||||
if (type == ripple::ltANY)
|
||||
if (type == ripple::ltANY) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}'.", key)}
|
||||
};
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
}};
|
||||
@@ -225,10 +230,11 @@ CustomValidator CustomValidators::issuerValidator =
|
||||
ripple::AccountID issuer;
|
||||
|
||||
// TODO: need to align with the error
|
||||
if (!ripple::to_issuer(issuer, boost::json::value_to<std::string>(value)))
|
||||
if (!ripple::to_issuer(issuer, boost::json::value_to<std::string>(value))) {
|
||||
return Error{Status{
|
||||
RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}', bad issuer.", key)
|
||||
}};
|
||||
}
|
||||
|
||||
if (issuer == ripple::noAccount()) {
|
||||
return Error{Status{
|
||||
@@ -308,21 +314,24 @@ CustomValidator CustomValidators::currencyIssueValidator =
|
||||
|
||||
CustomValidator CustomValidators::credentialTypeValidator =
|
||||
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (not value.is_string())
|
||||
if (not value.is_string()) {
|
||||
return Error{Status{
|
||||
ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " NotString"
|
||||
}};
|
||||
}
|
||||
|
||||
auto const& credTypeHex = ripple::strViewUnHex(value.as_string());
|
||||
if (!credTypeHex.has_value())
|
||||
if (!credTypeHex.has_value()) {
|
||||
return Error{Status{
|
||||
ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " NotHexString"
|
||||
}};
|
||||
}
|
||||
|
||||
if (credTypeHex->empty())
|
||||
if (credTypeHex->empty()) {
|
||||
return Error{
|
||||
Status{ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " is empty"}
|
||||
};
|
||||
}
|
||||
|
||||
if (credTypeHex->size() > ripple::maxCredentialTypeLength) {
|
||||
return Error{Status{
|
||||
@@ -374,10 +383,11 @@ CustomValidator CustomValidators::authorizeCredentialValidator =
|
||||
}
|
||||
|
||||
// don't want to change issuer error message to be about credentials
|
||||
if (!issuerValidator.verify(credObj, "issuer"))
|
||||
if (!issuerValidator.verify(credObj, "issuer")) {
|
||||
return Error{
|
||||
Status{ClioError::RpcMalformedAuthorizedCredentials, "issuer NotString"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!obj.contains("credential_type")) {
|
||||
return Error{Status{
|
||||
|
||||
@@ -124,10 +124,11 @@ public:
|
||||
[[nodiscard]] static MaybeError
|
||||
verify(boost::json::value const& value, std::string_view key)
|
||||
{
|
||||
if (value.is_object() and value.as_object().contains(key))
|
||||
if (value.is_object() and value.as_object().contains(key)) {
|
||||
return Error{Status{
|
||||
RippledError::rpcNOT_SUPPORTED, "Not supported field '" + std::string{key} + '\''
|
||||
}};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -419,10 +420,11 @@ public:
|
||||
return {}; // ignore. field does not exist, let 'required' fail instead
|
||||
|
||||
auto const res = value_to<Type>(value.as_object().at(key));
|
||||
if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_))
|
||||
if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_)) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, fmt::format("Invalid field '{}'.", key)}
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -65,15 +65,17 @@ ProductionAPIVersionParser::parse(boost::json::object const& request) const
|
||||
|
||||
auto const version = util::integralValueAs<uint32_t>(request.at("api_version"));
|
||||
|
||||
if (version > maxVersion_)
|
||||
if (version > maxVersion_) {
|
||||
return Error{fmt::format(
|
||||
"Requested API version is higher than maximum supported ({})", maxVersion_
|
||||
)};
|
||||
}
|
||||
|
||||
if (version < minVersion_)
|
||||
if (version < minVersion_) {
|
||||
return Error{fmt::format(
|
||||
"Requested API version is lower than minimum supported ({})", minVersion_
|
||||
)};
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -247,10 +247,11 @@ AMMInfoHandler::spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const kSTRING_ISSUE_VALIDATOR = validation::CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (not value.is_string())
|
||||
if (not value.is_string()) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotString"}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
ripple::issueFromJson(boost::json::value_to<std::string>(value));
|
||||
@@ -358,14 +359,16 @@ tag_invoke(boost::json::value_to_tag<AMMInfoHandler::Input>, boost::json::value
|
||||
if (jsonObject.contains(JS(asset2)))
|
||||
input.issue2 = parseIssue(jsonObject.at(JS(asset2)).as_object());
|
||||
|
||||
if (jsonObject.contains(JS(account)))
|
||||
if (jsonObject.contains(JS(account))) {
|
||||
input.accountID =
|
||||
accountFromStringStrict(boost::json::value_to<std::string>(jsonObject.at(JS(account))));
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(amm_account)))
|
||||
if (jsonObject.contains(JS(amm_account))) {
|
||||
input.ammAccount = accountFromStringStrict(
|
||||
boost::json::value_to<std::string>(jsonObject.at(JS(amm_account)))
|
||||
);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -166,9 +166,10 @@ tag_invoke(boost::json::value_to_tag<AccountChannelsHandler::Input>, boost::json
|
||||
if (jsonObject.contains(JS(ledger_hash)))
|
||||
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
|
||||
|
||||
if (jsonObject.contains(JS(destination_account)))
|
||||
if (jsonObject.contains(JS(destination_account))) {
|
||||
input.destinationAccount =
|
||||
boost::json::value_to<std::string>(jv.at(JS(destination_account)));
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(ledger_index))) {
|
||||
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
|
||||
|
||||
@@ -57,10 +57,11 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons
|
||||
{
|
||||
using namespace data;
|
||||
|
||||
if (!input.account && !input.ident)
|
||||
if (!input.account && !input.ident) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, ripple::RPC::missing_field_message(JS(account))}
|
||||
};
|
||||
}
|
||||
|
||||
auto const range = sharedPtrBackend_->fetchLedgerRange();
|
||||
ASSERT(range.has_value(), "AccountInfo's ledger range must be available");
|
||||
|
||||
@@ -82,10 +82,11 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input const& input, Context cons
|
||||
auto const blob = sharedPtrBackend_->fetchLedgerObject(pageKey, lgrInfo.seq, ctx.yield);
|
||||
|
||||
if (!blob) {
|
||||
if (input.marker.has_value())
|
||||
if (input.marker.has_value()) {
|
||||
return Error{Status{
|
||||
RippledError::rpcINVALID_PARAMS, "Marker field does not match any valid Page ID"
|
||||
}};
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -93,10 +94,11 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input const& input, Context cons
|
||||
ripple::SLE{ripple::SerialIter{blob->data(), blob->size()}, pageKey}
|
||||
};
|
||||
|
||||
if (page->getType() != ripple::ltNFTOKEN_PAGE)
|
||||
if (page->getType() != ripple::ltNFTOKEN_PAGE) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "Marker matches Page ID from another Account"}
|
||||
};
|
||||
}
|
||||
|
||||
auto numPages = 0u;
|
||||
|
||||
|
||||
@@ -89,10 +89,11 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c
|
||||
}
|
||||
|
||||
if (input.ledgerHash || input.ledgerIndex || input.usingValidatedLedger) {
|
||||
if (ctx.apiVersion > 1u && (input.ledgerIndexMax || input.ledgerIndexMin))
|
||||
if (ctx.apiVersion > 1u && (input.ledgerIndexMax || input.ledgerIndexMin)) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}
|
||||
};
|
||||
}
|
||||
|
||||
if (!input.ledgerIndexMax && !input.ledgerIndexMin) {
|
||||
// mimic rippled, when both range and index specified, respect the range.
|
||||
@@ -309,9 +310,10 @@ tag_invoke(boost::json::value_to_tag<AccountTxHandler::Input>, boost::json::valu
|
||||
};
|
||||
}
|
||||
|
||||
if (jsonObject.contains("tx_type"))
|
||||
if (jsonObject.contains("tx_type")) {
|
||||
input.transactionTypeInLowercase =
|
||||
boost::json::value_to<std::string>(jsonObject.at("tx_type"));
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -70,9 +70,10 @@ FeatureHandler::process(FeatureHandler::Input const& input, Context const& ctx)
|
||||
auto const& all = amendmentCenter_->getAll();
|
||||
|
||||
auto searchPredicate = [search = input.feature](auto const& feature) {
|
||||
if (search)
|
||||
if (search) {
|
||||
return ripple::to_string(feature.feature) == search.value() or
|
||||
feature.name == search.value();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -230,10 +230,11 @@ tag_invoke(boost::json::value_to_tag<LedgerDataHandler::Input>, boost::json::val
|
||||
input.ledgerIndex = *expectedLedgerIndex;
|
||||
}
|
||||
|
||||
if (jsonObject.contains(JS(type)))
|
||||
if (jsonObject.contains(JS(type))) {
|
||||
input.type = util::LedgerTypes::getLedgerEntryTypeFromStr(
|
||||
boost::json::value_to<std::string>(jv.at(JS(type)))
|
||||
);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -133,10 +133,11 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input const& input, Context cons
|
||||
);
|
||||
|
||||
auto const authCreds = credentials::createAuthCredentials(authorizedCredentials);
|
||||
if (authCreds.size() != authorizedCredentials.size())
|
||||
if (authCreds.size() != authorizedCredentials.size()) {
|
||||
return Error{Status{
|
||||
ClioError::RpcMalformedAuthorizedCredentials, "duplicates in credentials."
|
||||
}};
|
||||
}
|
||||
|
||||
key = ripple::keylet::depositPreauth(owner.value(), authCreds).key;
|
||||
}
|
||||
@@ -295,10 +296,11 @@ std::expected<ripple::uint256, Status>
|
||||
LedgerEntryHandler::composeKeyFromDirectory(boost::json::object const& directory) noexcept
|
||||
{
|
||||
// can not specify both dir_root and owner.
|
||||
if (directory.contains(JS(dir_root)) && directory.contains(JS(owner)))
|
||||
if (directory.contains(JS(dir_root)) && directory.contains(JS(owner))) {
|
||||
return std::unexpected{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "mayNotSpecifyBothDirRootAndOwner"}
|
||||
};
|
||||
}
|
||||
|
||||
// at least one should available
|
||||
if (!(directory.contains(JS(dir_root)) || directory.contains(JS(owner))))
|
||||
|
||||
@@ -85,10 +85,11 @@ LedgerIndexHandler::process(LedgerIndexHandler::Input const& input, Context cons
|
||||
return not earlierThan(ledgerIndex);
|
||||
});
|
||||
|
||||
if (greaterEqLedgerIter != view.end())
|
||||
if (greaterEqLedgerIter != view.end()) {
|
||||
return fillOutputByIndex(
|
||||
std::max(static_cast<std::uint32_t>(*greaterEqLedgerIter) - 1, minIndex)
|
||||
);
|
||||
}
|
||||
|
||||
return fillOutputByIndex(maxIndex);
|
||||
}
|
||||
|
||||
@@ -77,10 +77,11 @@ NFTHistoryHandler::process(NFTHistoryHandler::Input const& input, Context const&
|
||||
|
||||
if (input.ledgerHash || input.ledgerIndex) {
|
||||
// rippled does not have this check
|
||||
if (input.ledgerIndexMax || input.ledgerIndexMin)
|
||||
if (input.ledgerIndexMax || input.ledgerIndexMin) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"}
|
||||
};
|
||||
}
|
||||
|
||||
auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq(
|
||||
*sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence
|
||||
|
||||
@@ -69,16 +69,18 @@ SubscribeHandler::spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const kBOOKS_VALIDATOR = validation::CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_array())
|
||||
if (!value.is_array()) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}
|
||||
};
|
||||
}
|
||||
|
||||
for (auto const& book : value.as_array()) {
|
||||
if (!book.is_object())
|
||||
if (!book.is_object()) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}
|
||||
};
|
||||
}
|
||||
|
||||
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
|
||||
|
||||
@@ -70,10 +70,11 @@ TransactionEntryHandler::process(
|
||||
// the API for transaction_entry says the method only searches the specified
|
||||
// ledger; we simulate that here by returning not found if the transaction
|
||||
// is in a different ledger than the one specified.
|
||||
if (!dbRet || dbRet->ledgerSequence != output.ledgerHeader->seq)
|
||||
if (!dbRet || dbRet->ledgerSequence != output.ledgerHeader->seq) {
|
||||
return Error{
|
||||
Status{RippledError::rpcTXN_NOT_FOUND, "transactionNotFound", "Transaction not found."}
|
||||
};
|
||||
}
|
||||
|
||||
auto [txn, meta] = toExpandedJson(*dbRet, ctx.apiVersion);
|
||||
|
||||
|
||||
@@ -243,9 +243,10 @@ public:
|
||||
output.ledgerIndex = dbResponse->ledgerSequence;
|
||||
|
||||
// fetch ledger hash
|
||||
if (ctx.apiVersion > 1u)
|
||||
if (ctx.apiVersion > 1u) {
|
||||
output.ledgerHeader =
|
||||
sharedPtrBackend_->fetchLedgerBySequence(dbResponse->ledgerSequence, ctx.yield);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -56,16 +56,18 @@ UnsubscribeHandler::spec([[maybe_unused]] uint32_t apiVersion)
|
||||
{
|
||||
static auto const kBOOKS_VALIDATOR = validation::CustomValidator{
|
||||
[](boost::json::value const& value, std::string_view key) -> MaybeError {
|
||||
if (!value.is_array())
|
||||
if (!value.is_array()) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "NotArray"}
|
||||
};
|
||||
}
|
||||
|
||||
for (auto const& book : value.as_array()) {
|
||||
if (!book.is_object())
|
||||
if (!book.is_object()) {
|
||||
return Error{
|
||||
Status{RippledError::rpcINVALID_PARAMS, std::string(key) + "ItemNotObject"}
|
||||
};
|
||||
}
|
||||
|
||||
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool())
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, "bothNotBool"}};
|
||||
|
||||
@@ -44,37 +44,36 @@ static constexpr char kBUILD_DATE[] = BUILD_DATE;
|
||||
std::string const&
|
||||
getClioVersionString()
|
||||
{
|
||||
static std::string const value = kVERSION_STRING; // NOLINT(readability-identifier-naming)
|
||||
return value;
|
||||
static std::string const kVALUE = kVERSION_STRING;
|
||||
return kVALUE;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
getClioFullVersionString()
|
||||
{
|
||||
static std::string const value =
|
||||
"clio-" + getClioVersionString(); // NOLINT(readability-identifier-naming)
|
||||
return value;
|
||||
static std::string const kVALUE = "clio-" + getClioVersionString();
|
||||
return kVALUE;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
getGitCommitHash()
|
||||
{
|
||||
static std::string const value = kGIT_COMMIT_HASH; // NOLINT(readability-identifier-naming)
|
||||
return value;
|
||||
static std::string const kVALUE = kGIT_COMMIT_HASH;
|
||||
return kVALUE;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
getGitBuildBranch()
|
||||
{
|
||||
static std::string const value = kGIT_BUILD_BRANCH; // NOLINT(readability-identifier-naming)
|
||||
return value;
|
||||
static std::string const kVALUE = kGIT_BUILD_BRANCH;
|
||||
return kVALUE;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
getBuildDate()
|
||||
{
|
||||
static std::string const value = kBUILD_DATE; // NOLINT(readability-identifier-naming)
|
||||
return value;
|
||||
static std::string const kVALUE = kBUILD_DATE;
|
||||
return kVALUE;
|
||||
}
|
||||
|
||||
} // namespace util::build
|
||||
|
||||
@@ -78,10 +78,11 @@ makeAdminVerificationStrategy(util::config::ClioConfigDefinition const& config)
|
||||
auto adminPassword = config.maybeValue<std::string>("server.admin_password");
|
||||
auto const localAdmin = config.maybeValue<bool>("server.local_admin");
|
||||
|
||||
if (adminPassword.has_value() and localAdmin.has_value() and *localAdmin)
|
||||
if (adminPassword.has_value() and localAdmin.has_value() and *localAdmin) {
|
||||
return std::unexpected{
|
||||
"Admin config error: 'local_admin' and admin_password can not be set together."
|
||||
};
|
||||
}
|
||||
|
||||
if (localAdmin.has_value() and !*localAdmin and !adminPassword.has_value()) {
|
||||
return std::unexpected{
|
||||
|
||||
@@ -245,10 +245,11 @@ public:
|
||||
return sender_(httpResponse(http::status::ok, "text/html", kHEALTH_CHECK_HTML));
|
||||
|
||||
if (req_.method() == http::verb::get and req_.target() == "/cache_state") {
|
||||
if (cache_.get().isFull())
|
||||
if (cache_.get().isFull()) {
|
||||
return sender_(
|
||||
httpResponse(http::status::ok, "text/html", kCACHE_CHECK_LOADED_HTML)
|
||||
);
|
||||
}
|
||||
|
||||
return sender_(httpResponse(
|
||||
http::status::service_unavailable, "text/html", kCACHE_CHECK_NOT_LOADED_HTML
|
||||
|
||||
@@ -93,9 +93,10 @@ protected:
|
||||
wsFail(boost::beast::error_code ec, char const* what)
|
||||
{
|
||||
// Don't log if the WebSocket stream was gracefully closed at both endpoints
|
||||
if (ec != boost::beast::websocket::error::closed)
|
||||
if (ec != boost::beast::websocket::error::closed) {
|
||||
LOG(log_.error()) << tag() << ": " << what << ": " << ec.message() << ": "
|
||||
<< ec.value();
|
||||
}
|
||||
|
||||
if (!ec_ && ec != boost::asio::error::operation_aborted) {
|
||||
ec_ = ec;
|
||||
|
||||
@@ -161,9 +161,10 @@ public:
|
||||
<< connectionMetadata.tag() << "Adding to work queue";
|
||||
|
||||
if (not connectionMetadata.wasUpgraded() and
|
||||
shouldReplaceParams(parsedObject))
|
||||
shouldReplaceParams(parsedObject)) {
|
||||
parsedObject[JS(params)] =
|
||||
boost::json::array({boost::json::object{}});
|
||||
}
|
||||
|
||||
response = handleRequest(
|
||||
innerYield,
|
||||
|
||||
@@ -138,10 +138,11 @@ makeConnection(
|
||||
{
|
||||
impl::UpgradableConnectionPtr connection;
|
||||
if (sslDetectionResult.isSsl) {
|
||||
if (not sslContext.has_value())
|
||||
if (not sslContext.has_value()) {
|
||||
return std::unexpected{
|
||||
"Error creating a connection: SSL is not supported by this server"
|
||||
};
|
||||
}
|
||||
|
||||
auto sslConnection = std::make_unique<impl::SslHttpConnection>(
|
||||
std::move(sslDetectionResult.socket),
|
||||
@@ -152,10 +153,11 @@ makeConnection(
|
||||
);
|
||||
sslConnection->setTimeout(std::chrono::seconds{10});
|
||||
auto const expectedSuccess = sslConnection->sslHandshake(yield);
|
||||
if (not expectedSuccess.has_value())
|
||||
if (not expectedSuccess.has_value()) {
|
||||
return std::unexpected{
|
||||
fmt::format("SSL handshake error: {}", expectedSuccess.error().message())
|
||||
};
|
||||
}
|
||||
|
||||
connection = std::move(sslConnection);
|
||||
} else {
|
||||
|
||||
@@ -57,10 +57,11 @@ makeServerSslContext(util::config::ClioConfigDefinition const& config)
|
||||
bool const configHasCertFile = config.getValueView("ssl_cert_file").hasValue();
|
||||
bool const configHasKeyFile = config.getValueView("ssl_key_file").hasValue();
|
||||
|
||||
if (configHasCertFile != configHasKeyFile)
|
||||
if (configHasCertFile != configHasKeyFile) {
|
||||
return std::unexpected{
|
||||
"Config entries 'ssl_cert_file' and 'ssl_key_file' must be set or unset together."
|
||||
};
|
||||
}
|
||||
|
||||
if (not configHasCertFile)
|
||||
return std::nullopt;
|
||||
|
||||
@@ -116,8 +116,10 @@ TEST_F(CliArgsTests, Parse_Config)
|
||||
{
|
||||
std::string_view configPath = "some_config_path";
|
||||
std::array argv{
|
||||
"clio_server", "--conf", configPath.data()
|
||||
}; // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
"clio_server",
|
||||
"--conf",
|
||||
configPath.data() // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
};
|
||||
auto const action = CliArgs::parse(argv.size(), argv.data());
|
||||
|
||||
int const returnCode = 123;
|
||||
@@ -140,8 +142,10 @@ TEST_F(CliArgsTests, Parse_VerifyConfig)
|
||||
{
|
||||
std::string_view configPath = "some_config_path";
|
||||
std::array argv{
|
||||
"clio_server", configPath.data(), "--verify"
|
||||
}; // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
"clio_server",
|
||||
configPath.data(), // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
"--verify"
|
||||
};
|
||||
auto const action = CliArgs::parse(argv.size(), argv.data());
|
||||
|
||||
int const returnCode = 123;
|
||||
|
||||
@@ -302,7 +302,7 @@ TEST_F(RPCCountersMockPrometheusRecotdLedgerRequestTest, validatedDefaultLedger)
|
||||
{
|
||||
EXPECT_CALL(ageLedgersHistogramMock, observe(0));
|
||||
|
||||
boost::json::object params;
|
||||
boost::json::object const params;
|
||||
counters.recordLedgerRequest(params, 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -94,10 +94,11 @@ public:
|
||||
if (type == ContextType::IOContext)
|
||||
return ContextVariant(std::in_place_type_t<boost::asio::io_context>());
|
||||
|
||||
if (type == ContextType::ThreadPool)
|
||||
if (type == ContextType::ThreadPool) {
|
||||
return ContextVariant(
|
||||
std::in_place_type_t<boost::asio::thread_pool>(), kDEFAULT_THREAD_POOL_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
ASSERT(false, "Unknown new type of context");
|
||||
std::unreachable();
|
||||
@@ -395,11 +396,12 @@ TEST_P(ChannelCallbackTest, MultipleSendersOneReceiver)
|
||||
[self = std::forward<decltype(self)>(self),
|
||||
&executor,
|
||||
i](bool success) mutable {
|
||||
if (success)
|
||||
if (success) {
|
||||
boost::asio::post(
|
||||
executor,
|
||||
[self = std::move(self), i]() mutable { self(i + 1); }
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -456,11 +458,12 @@ TEST_P(ChannelCallbackTest, MultipleSendersMultipleReceivers)
|
||||
[self = std::forward<decltype(self)>(self),
|
||||
&executor,
|
||||
i](bool success) mutable {
|
||||
if (success)
|
||||
if (success) {
|
||||
boost::asio::post(
|
||||
executor,
|
||||
[self = std::move(self), i]() mutable { self(i + 1); }
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -70,9 +70,8 @@ struct MockWsBase : public web::ConnectionBase {
|
||||
}
|
||||
|
||||
void
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
|
||||
send(
|
||||
std::string&& msg,
|
||||
std::string&& msg, // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved)
|
||||
boost::beast::http::status status = boost::beast::http::status::ok
|
||||
) override
|
||||
{
|
||||
|
||||
@@ -46,10 +46,11 @@ struct NgErrorHandlingTests : public virtual ::testing::Test {
|
||||
static Request
|
||||
makeRequest(bool isHttp, std::optional<std::string> body = std::nullopt)
|
||||
{
|
||||
if (isHttp)
|
||||
if (isHttp) {
|
||||
return Request{
|
||||
http::request<http::string_body>{http::verb::post, "/", 11, body.value_or("")}
|
||||
};
|
||||
}
|
||||
static Request::HttpHeaders const kHEADERS;
|
||||
return Request{body.value_or(""), kHEADERS};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user