refactor: Generalise result usages (#22)

* Return LogosResult as QVariantMap instead of formatted string.

* Group LogosResult helpers under a result namespace.

* Improve comments.

---------

Co-authored-by: Daniel <sanchez.quiros.daniel@gmail.com>
This commit is contained in:
Álex 2026-06-20 12:55:52 +00:00 committed by GitHub
parent 32257d20e2
commit 422166b2b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 130 additions and 92 deletions

View File

@ -35,13 +35,21 @@ static QString toLocalPath(const QString& pathInput)
return QUrl::fromUserInput(pathInput).toLocalFile();
}
// invokeRemoteMethod() returns an invalid QVariant (not a LogosResult) when
// the call itself fails to get a reply (timeout, disconnected module, etc.),
// as opposed to the remote method running and reporting failure normally.
namespace result {
static LogosResult err(const QString& message)
{
return LogosResult{false, QVariant(), message};
}
// Normalises a `QVariant` (e.g. from a `invokeRemoteMethod()`) call to a `LogosResult`.
//
// `invokeRemoteMethod()` might return an invalid `QVariant` when the call itself fails to get a reply (e.g.: timeout).
// This function normalises the reply for the `LogosResult` case.
static LogosResult toLogosResult(const QVariant& reply)
{
if (!reply.isValid())
return LogosResult{false, QVariant(), QStringLiteral("Call failed.")};
return err(QStringLiteral("Call failed."));
return reply.value<LogosResult>();
}
@ -50,15 +58,25 @@ static QString toErrorMessage(const LogosResult& result)
return QStringLiteral("Error: %1").arg(result.error.toString());
}
// QML callers distinguish success from failure by sniffing for an "Error"
// prefix (see BlockchainView.qml), so both branches must share this type.
// TODO: Currently functions are returning a string (for ok and ko cases), which is later re-parsed to know if the call
// succeeded or failed. We can simplify this by returning the result itself upward.
// Returns a stringified version of a `LogosResult`.
//
// Used in some places that consume the success and error properties in the same manner.
static QString toDisplayMessage(const LogosResult& result)
{
return result.success ? result.value.toString() : toErrorMessage(result);
}
static QVariantMap toVariantMap(const LogosResult& result)
{
return QVariantMap{
{"success", result.success},
{"value", result.value},
{"error", result.error},
};
}
} // namespace result
// Decode a base58 (Bitcoin alphabet) string to raw bytes. On an invalid
// character *ok is set to false and an empty array is returned.
static QByteArray decodeBase58(const QString& input, bool* ok)
@ -188,12 +206,12 @@ BlockchainBackend::~BlockchainBackend()
stopBlockchain();
}
QString BlockchainBackend::claimLeaderRewards()
QVariantMap BlockchainBackend::claimLeaderRewards()
{
if (!m_blockchainClient)
return QStringLiteral("Error: Module not initialized.");
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "leader_claim")));
}
@ -206,14 +224,14 @@ void BlockchainBackend::startBlockchain()
setStatus(Starting);
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
const LogosResult r = result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "start", userConfig(), deploymentConfig()));
if (result.success) {
if (r.success) {
setStatus(Running);
QTimer::singleShot(500, this, [this]() { refreshAccounts(); });
} else {
setError(result.error.toString());
setError(r.error.toString());
}
}
@ -229,13 +247,13 @@ void BlockchainBackend::stopBlockchain()
setStatus(Stopping);
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
const LogosResult r = result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "stop"));
if (result.success) {
if (r.success) {
setStatus(Stopped);
} else {
setError(result.error.toString());
setError(r.error.toString());
}
}
@ -243,11 +261,11 @@ void BlockchainBackend::refreshAccounts()
{
if (!m_blockchainClient) return;
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
const LogosResult r = result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses"));
if (!result.success) {
qWarning() << "refreshAccounts: failed:" << result.error.toString();
if (!r.success) {
qWarning() << "refreshAccounts: failed:" << r.error.toString();
return;
}
@ -256,7 +274,7 @@ void BlockchainBackend::refreshAccounts()
// QVariantList under Qt6), and fall back to toStringList() for the rare
// case where the value already arrives as a QStringList.
QStringList list;
const QVariantList items = result.value.toList();
const QVariantList items = r.value.toList();
if (!items.isEmpty()) {
for (const QVariant& item : items) {
const QString addr = item.toString();
@ -264,7 +282,7 @@ void BlockchainBackend::refreshAccounts()
list << addr;
}
} else {
list = result.value.toStringList();
list = r.value.toStringList();
}
qDebug() << "refreshAccounts: loaded" << list.size() << "addresses";
@ -284,39 +302,36 @@ void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list)
}
}
QString BlockchainBackend::getBalance(QString addressHex)
QVariantMap BlockchainBackend::getBalance(QString addressHex)
{
QString result;
if (!m_blockchainClient) {
result = QStringLiteral("Error: Module not initialized.");
} else {
result = toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex)));
}
const LogosResult lr = m_blockchainClient
? result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex))
: result::err(QStringLiteral("Module not initialized."));
m_accountsModel->setBalanceForAddress(addressHex, result);
return result;
m_accountsModel->setBalanceForAddress(addressHex, result::toDisplayMessage(lr));
return result::toVariantMap(lr);
}
QString BlockchainBackend::transferFunds(
QVariantMap BlockchainBackend::transferFunds(
QString fromKeyHex, QString toKeyHex, QString amountStr)
{
if (!m_blockchainClient)
return QStringLiteral("Error: Module not initialized.");
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
QStringList senders{fromKeyHex};
return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_transfer_funds",
fromKeyHex, senders, toKeyHex, amountStr, QString())));
}
int BlockchainBackend::generateConfig(
QVariantMap BlockchainBackend::generateConfig(
QString outputPath, QStringList initialPeers, int netPort, int blendPort,
QString httpAddr, QString externalAddress, bool noPublicIpCheck,
int deploymentMode, QString deploymentConfigPath, QString statePath)
{
if (!m_blockchainClient)
return -1;
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
QVariantMap normalized;
@ -364,29 +379,27 @@ int BlockchainBackend::generateConfig(
const QString jsonToSend =
QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "generate_user_config", jsonToSend));
return result.success ? 0 : -1;
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "generate_user_config", jsonToSend)));
}
QString BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTipHex)
QVariantMap BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTipHex)
{
if (!m_blockchainClient)
return QStringLiteral("Error: Module not initialized.");
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, "wallet_get_notes",
walletAddressHex, optionalTipHex)));
}
QString BlockchainBackend::channelDepositWithNotes(
QVariantMap BlockchainBackend::channelDepositWithNotes(
QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58,
QString changePublicKeyHex, QStringList fundingPublicKeyHexes,
QString maxTxFee, QString optionalTipHex)
{
if (!m_blockchainClient)
return QStringLiteral("Error: Module not initialized.");
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
// The metadata arrives base58-encoded; the module expects metadata_hex, so
// decode to bytes and hex-encode. Empty stays empty (metadata is optional).
@ -395,7 +408,7 @@ QString BlockchainBackend::channelDepositWithNotes(
bool ok = false;
const QByteArray bytes = decodeBase58(metadataBase58, &ok);
if (!ok)
return QStringLiteral("Error: Invalid base58 metadata.");
return result::toVariantMap(result::err(QStringLiteral("Invalid base58 metadata.")));
metadataHex = QString::fromLatin1(bytes.toHex());
}
@ -405,7 +418,7 @@ QString BlockchainBackend::channelDepositWithNotes(
args << channelIdHex << inputNoteIdHexes << metadataHex << changePublicKeyHex
<< fundingPublicKeyHexes << maxTxFee << optionalTipHex;
return toDisplayMessage(toLogosResult(m_blockchainClient->invokeRemoteMethod(
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
BLOCKCHAIN_MODULE_NAME, QStringLiteral("channel_deposit_with_notes"),
args)));
}

View File

@ -5,6 +5,7 @@
#include <QString>
#include <QStringList>
#include <QVariantList>
#include <QVariantMap>
#include "rep_BlockchainBackend_source.h"
@ -41,15 +42,15 @@ public slots:
void startBlockchain() override;
void stopBlockchain() override;
void refreshAccounts() override;
QString getBalance(QString addressHex) override;
QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr) override;
QString claimLeaderRewards() override;
int generateConfig(QString outputPath, QStringList initialPeers, int netPort,
QVariantMap getBalance(QString addressHex) override;
QVariantMap transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr) override;
QVariantMap claimLeaderRewards() override;
QVariantMap generateConfig(QString outputPath, QStringList initialPeers, int netPort,
int blendPort, QString httpAddr, QString externalAddress,
bool noPublicIpCheck, int deploymentMode,
QString deploymentConfigPath, QString statePath) override;
QString getNotes(QString walletAddressHex, QString optionalTipHex) override;
QString channelDepositWithNotes(QString channelIdHex,
QVariantMap getNotes(QString walletAddressHex, QString optionalTipHex) override;
QVariantMap channelDepositWithNotes(QString channelIdHex,
QStringList inputNoteIdHexes,
QString metadataBase58,
QString changePublicKeyHex,

View File

@ -12,12 +12,12 @@ class BlockchainBackend
SLOT(void startBlockchain())
SLOT(void stopBlockchain())
SLOT(void refreshAccounts())
SLOT(QString getBalance(QString addressHex))
SLOT(QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr))
SLOT(QString claimLeaderRewards())
SLOT(int generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath))
SLOT(QString getNotes(QString walletAddressHex, QString optionalTipHex))
SLOT(QString channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex))
SLOT(QVariantMap getBalance(QString addressHex))
SLOT(QVariantMap transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr))
SLOT(QVariantMap claimLeaderRewards())
SLOT(QVariantMap generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath))
SLOT(QVariantMap getNotes(QString walletAddressHex, QString optionalTipHex))
SLOT(QVariantMap channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataBase58, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex))
SLOT(void clearLogs())
SLOT(void copyToClipboard(QString text))
}

View File

@ -59,6 +59,10 @@ Rectangle {
QtObject {
id: _d
function errorText(message) {
return qsTr("Error: %1").arg(message)
}
function getStatusString(s) {
switch(s) {
case BlockchainBackend.NotStarted: return qsTr("Not Started")
@ -66,7 +70,7 @@ Rectangle {
case BlockchainBackend.Running: return qsTr("Running")
case BlockchainBackend.Stopping: return qsTr("Stopping...")
case BlockchainBackend.Stopped: return qsTr("Stopped")
case BlockchainBackend.Error: return qsTr("Error: %1").arg(root.backend.lastErrorMessage)
case BlockchainBackend.Error: return _d.errorText(root.backend.lastErrorMessage)
default: return qsTr("Unknown")
}
}
@ -138,16 +142,14 @@ Rectangle {
outputPath, initialPeers, netPort, blendPort,
httpAddr, externalAddress, noPublicIpCheck,
deploymentMode, deploymentConfigPath, statePath),
function(code) {
// logos.watch stringifies the returned int coerce back.
var rc = parseInt(code, 10)
console.log("[BlockchainView] generateConfig success callback: code=", code, "type=", typeof code, "→ rc=", rc)
configChoiceView.generateResultSuccess = (rc === 0)
function(result) {
console.log("[BlockchainView] generateConfig success callback: result=", JSON.stringify(result))
configChoiceView.generateResultSuccess = result.success
configChoiceView.generateResultMessage =
rc === 0
result.success
? qsTr("Config generated successfully.")
: qsTr("Generate failed (code: %1).").arg(rc)
if (rc === 0) {
: qsTr("Generate failed: %1").arg(result.error)
if (result.success) {
root.backend.userConfig = (outputPath !== "")
? outputPath : root.backend.generatedUserConfigPath
root.backend.deploymentConfig =
@ -230,17 +232,17 @@ Rectangle {
logos.watch(
root.backend.getBalance(addressHex),
function(result) {
if ((result || "").indexOf("Error") === 0) {
walletView.lastBalanceErrorAddress = addressHex
walletView.lastBalanceError = result
} else {
if (result.success) {
walletView.lastBalanceErrorAddress = ""
walletView.lastBalanceError = ""
} else {
walletView.lastBalanceErrorAddress = addressHex
walletView.lastBalanceError = _d.errorText(result.error)
}
},
function(error) {
walletView.lastBalanceErrorAddress = addressHex
walletView.lastBalanceError = "Error: " + error
walletView.lastBalanceError = _d.errorText(error)
}
)
}
@ -251,16 +253,28 @@ Rectangle {
if (!root.backend) return
logos.watch(
root.backend.transferFunds(fromKeyHex, toKeyHex, amount),
function(result) { walletView.setTransferResult(result) },
function(error) { walletView.setTransferResult("Error: " + error) }
function(result) {
if (result.success) {
walletView.setTransferResult(result.value)
} else {
walletView.setTransferResult(_d.errorText(result.error))
}
},
function(error) { walletView.setTransferResult(_d.errorText(error)) }
)
}
onClaimLeaderRewardsRequested: function() {
if (!root.backend) return
logos.watch(
root.backend.claimLeaderRewards(),
function(result) { walletView.setLeaderClaimResult(result) },
function(error) { walletView.setLeaderClaimResult("Error: " + error) }
function(result) {
if (result.success) {
walletView.setLeaderClaimResult(result.value)
} else {
walletView.setLeaderClaimResult(_d.errorText(result.error))
}
},
function(error) { walletView.setLeaderClaimResult(_d.errorText(error)) }
)
}
onRefreshAccountsRequested: if (root.backend) root.backend.refreshAccounts()
@ -294,8 +308,13 @@ Rectangle {
if (!root.backend) return
logos.watch(
root.backend.getNotes(addressHex, optionalTipHex),
function(result) { channelDepositView.setNotesResult(result) },
function(error) { channelDepositView.setNotesResult("Error: " + error) }
function(result) {
if (result.success)
channelDepositView.setNotes(result.value)
else
channelDepositView.setNotesError(_d.errorText(result.error))
},
function(error) { channelDepositView.setNotesError(_d.errorText(error)) }
)
}
onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) {
@ -304,8 +323,13 @@ Rectangle {
root.backend.channelDepositWithNotes(
channelIdHex, inputNoteIdHexes, metadataBase58,
changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex),
function(result) { channelDepositView.setSubmitResult(result) },
function(error) { channelDepositView.setSubmitResult("Error: " + error) }
function(result) {
if (result.success)
channelDepositView.setSubmitResult(true, result.value)
else
channelDepositView.setSubmitResult(false, _d.errorText(result.error))
},
function(error) { channelDepositView.setSubmitResult(false, _d.errorText(error)) }
)
}
onCopyToClipboard: (text) => {

View File

@ -31,14 +31,9 @@ ColumnLayout {
noteSelector.errorText = ""
}
function setNotesResult(jsonStr) {
function setNotes(jsonStr) {
noteSelector.loading = false
var s = jsonStr || ""
if (s.indexOf("Error") === 0) {
noteSelector.errorText = s
noteSelector.notes = []
return
}
try {
var parsed = JSON.parse(s)
d.notesTip = parsed.tip || ""
@ -50,11 +45,16 @@ ColumnLayout {
}
}
function setSubmitResult(resultStr) {
function setNotesError(message) {
noteSelector.loading = false
noteSelector.errorText = message
noteSelector.notes = []
}
function setSubmitResult(success, text) {
d.resultPending = false
var s = resultStr || ""
d.resultSuccess = (s.indexOf("Error") !== 0)
d.resultText = s
d.resultSuccess = success
d.resultText = text
}
spacing: Theme.spacing.large