Address #14 with request correlation, bounded parser reasons, startup version reporting, and rate-limited storage and settlement logs. Preserve accounting and API identity contracts, extend portable tests, and update release versions and rollout guidance.
99 lines
3.8 KiB
C++
99 lines
3.8 KiB
C++
#include <stdinc.hpp>
|
|
#include "loader/component_loader.hpp"
|
|
#include "gsc.hpp"
|
|
#include "game/scripting/array.hpp"
|
|
#include <utils/bank.hpp>
|
|
#include <array>
|
|
#include <chrono>
|
|
|
|
namespace bank
|
|
{
|
|
namespace
|
|
{
|
|
utils::bank::outbox spool(const std::string& server, const std::string& endpoint)
|
|
{
|
|
return {"tws-bank/t6", server, endpoint};
|
|
}
|
|
|
|
// Only fixed operation names are printed, never exception text or paths
|
|
// from private configuration. Per-operation state has bounded memory.
|
|
void storage_result(size_t operation, bool failed)
|
|
{
|
|
struct status
|
|
{
|
|
bool failed = false;
|
|
std::chrono::steady_clock::time_point logged{};
|
|
unsigned suppressed = 0;
|
|
};
|
|
static std::array<status, 3> states;
|
|
constexpr const char* names[] = {"load", "write", "remove"};
|
|
auto& state = states[operation];
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (failed && state.failed && now - state.logged < std::chrono::seconds(30))
|
|
{
|
|
++state.suppressed;
|
|
return;
|
|
}
|
|
if (failed || state.failed)
|
|
printf("[tws_bank] native_storage operation=%s outcome=%s suppressed=%u\n",
|
|
names[operation], failed ? "storage_failure" : "recovered", state.suppressed);
|
|
state.failed = failed;
|
|
state.logged = now;
|
|
state.suppressed = 0;
|
|
}
|
|
|
|
}
|
|
|
|
class component final : public component_interface
|
|
{
|
|
public:
|
|
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
|
{
|
|
gsc::function::add("bankversion", []() { return std::string("1.2.0"); });
|
|
gsc::function::add("bankload", [](const std::string& server, const std::string& endpoint) -> scripting::script_value
|
|
{
|
|
try
|
|
{
|
|
scripting::array entries;
|
|
for (const auto& value : spool(server, endpoint).load())
|
|
{
|
|
scripting::array entry;
|
|
entry["id"] = value.id;
|
|
entry["decision"] = value.decision;
|
|
entry["body"] = value.body;
|
|
entries.emplace_back(entry);
|
|
}
|
|
storage_result(0, false);
|
|
return entries;
|
|
}
|
|
catch (...) { storage_result(0, true); return {}; }
|
|
});
|
|
gsc::function::add("bankstore", [](const std::string& server, const std::string& endpoint, const std::string& body)
|
|
{
|
|
try { spool(server, endpoint).store(body); storage_result(1, false); return true; }
|
|
catch (...) { storage_result(1, true); return false; }
|
|
});
|
|
gsc::function::add("bankremove", [](const std::string& server, const std::string& endpoint, const std::string& id)
|
|
{
|
|
try { spool(server, endpoint).erase(id); storage_result(2, false); return true; }
|
|
catch (...) { storage_result(2, true); return false; }
|
|
});
|
|
gsc::function::add("bankresponse", [](const std::string& response)
|
|
{
|
|
const auto ack = utils::bank::parse_acknowledgement(response);
|
|
scripting::array result;
|
|
result["valid"] = ack.valid;
|
|
result["reason"] = ack.reason;
|
|
result["state"] = ack.state;
|
|
result["code"] = ack.code;
|
|
result["request_id"] = ack.id;
|
|
result["amount"] = ack.amount;
|
|
result["balance"] = ack.balance;
|
|
return result;
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
REGISTER_COMPONENT(bank::component)
|