From 4fdf157120a7cb9bf4e8810a39c285df28fc5fb5 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Wed, 25 Feb 2026 18:13:43 +0400 Subject: [PATCH] feat: LogosResult (#14) * Add LogosResult * Add documentation for complex types * Add get type util function * Add more shorthand functions * Add bool support * Provide more shorthand functions * Throw exception on bad access * Fix typo in doc --- README.md | 109 ++++++++++++++++++++++++++++++++ cpp-generator/main.cpp | 5 +- cpp/CMakeLists.txt | 3 + cpp/compile.sh | 1 + cpp/logos_api.cpp | 5 ++ cpp/logos_api.h | 3 +- cpp/logos_types.cpp | 11 ++++ cpp/logos_types.h | 138 +++++++++++++++++++++++++++++++++++++++++ cpp/module_proxy.cpp | 25 +++++++- nix/include.nix | 2 +- 10 files changed, 297 insertions(+), 5 deletions(-) create mode 100644 cpp/logos_types.cpp create mode 100644 cpp/logos_types.h diff --git a/README.md b/README.md index cee5fb8..f03938e 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,115 @@ logos-cpp-generator --metadata metadata.json --general-only --output-dir ./gener This approach gives you fine-grained control over which modules to include and allows rebuilding just the umbrella headers without regenerating all module wrappers. +### API + +#### LogosResult + +`LogosResult` provides a structured way to return either a value or an error from synchronous method calls. + +If the `success` attribute is `true`, you can retrieve the value using a cast. Otherwise, retrieve the error which should be a string (though not enforced). + +The `success` attribute should ALWAYS be asserted. Accessing the value of an errored `LogosResult` or the error of a valid `LogosResult` will result in a `LogosResultException` being thrown. + +### Example + +```cpp +LogosResult result = m_logos->my_module.someMethod(); +if (result.success) { + // Use shorthand + QString value = result.getString(); + // Or + QString value = result.getValue(); +} else { + // Use shorthand + QString error = result.getError(); + // Or + QString error = result.getError(); +} +``` + +#### Complex objects + +Let's say you need to return a complex object. In the SDK, you have to build your type with primitive like QVariantMap: + +```cpp +// Received JSON: {"cid": "QmXyz...", "filename": "photo.jpg", "size": 2048576, "mimetype": "image/jpeg"} + +QVariantMap manifest; +manifest["cid"] = "QmXyz..."; +manifest["filename"] = "photo.jpg"; +manifest["size"] = 2048576; +manifest["mimetype"] = "image/jpeg"; +return {true, manifest}; +``` + +And then to consume by using the shorthand function: + +```cpp +LogosResult result = m_logos->my_plugin.someMethod(cid); +if (result.success) { + QString cid = result.getString("cid"); + // You can define a default value as well + QString cid = result.getString("cid", "unknown"); +} +``` + +Or you can use the value directly: + +```cpp +LogosResult result = m_logos->my_plugin.someMethod(cid); +if (result.success) { + QVariantMap manifest = result.getMap(); + QString cid = manifest["cid"].toString(); +} +``` + +Same thing for a list, you can use `QVariantList`: + +```cpp +QVariantList manifests; + +QVariantMap m1; +m1["cid"] = "QmAbc..."; +m1["filename"] = "document.pdf"; +m1["size"] = 1024000; +manifests.append(m1); + +QVariantMap m2; +m2["cid"] = "QmDef..."; +m2["filename"] = "image.png"; +m2["size"] = 512000; +manifests.append(m2); + +return {true, manifests}; +``` + +To consume it using the shorthand function: + +```cpp +LogosResult result = m_logos->my_plugin.someMethod(); +if (result.success) { + for (int i = 0; i < list.size(); ++i) { + QString cid = result.getString(i, "cid"); + // You can define a default value as well + QString cid = result.getString(0, "cid", "unknown"); + } +} +``` + +Or you can use the value directly: + +```cpp +LogosResult result = m_logos->my_plugin.someMethod(); +if (result.success) { + QVariantList list = result.getList(); + for (const QVariant& item : list) { + QVariantMap manifest = item.toMap(); + QString cid = manifest["cid"].toString(); + } +} +``` + ### Requirements #### Build Tools diff --git a/cpp-generator/main.cpp b/cpp-generator/main.cpp index 5f4edd0..2fe90f6 100644 --- a/cpp-generator/main.cpp +++ b/cpp-generator/main.cpp @@ -100,7 +100,7 @@ static QString mapReturnType(const QString& qtType) const QString base = normalizeType(qtType); if (base.isEmpty() || base == "void") return QString("void"); static const QSet known = { - "bool","int","double","float","QString","QStringList","QJsonArray","QVariant" + "bool","int","double","float","QString","QStringList","QJsonArray","QVariant","LogosResult" }; if (known.contains(base)) return base; return QString("QVariant"); @@ -119,6 +119,7 @@ static QString makeHeader(const QString& moduleName, const QString& className, c s << "#include \n"; s << "#include \n"; s << "#include \n"; + s << "#include \"logos_types.h\"\n"; s << "#include \"logos_api.h\"\n"; s << "#include \"logos_api_client.h\"\n\n"; s << "class " << className << " {\n"; @@ -325,6 +326,8 @@ static QString makeSource(const QString& moduleName, const QString& className, c s << " return _result.toStringList();\n"; } else if (ret == "QJsonArray") { s << " return qvariant_cast(_result);\n"; + }else if (ret == "LogosResult") { + s << " return _result.value();\n"; } else { // QVariant s << " return _result;\n"; } diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index a216b59..e96396d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -11,6 +11,8 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core RemoteObjects) # SDK sources set(SDK_SOURCES + logos_types.cpp + logos_types.h logos_api.cpp logos_api.h logos_api_client.cpp @@ -52,6 +54,7 @@ install(TARGETS logos_sdk # Install headers install(FILES + logos_types.h logos_api.h logos_api_client.h logos_api_consumer.h diff --git a/cpp/compile.sh b/cpp/compile.sh index e7e5a72..75d7212 100755 --- a/cpp/compile.sh +++ b/cpp/compile.sh @@ -55,6 +55,7 @@ echo "Generating MOC files..." # List of headers that need MOC processing (contain Q_OBJECT) MOC_HEADERS=( + "logos_types.h" "logos_api.h" "logos_api_client.h" "logos_api_provider.h" diff --git a/cpp/logos_api.cpp b/cpp/logos_api.cpp index aae10d7..d0708e5 100644 --- a/cpp/logos_api.cpp +++ b/cpp/logos_api.cpp @@ -14,12 +14,17 @@ LogosAPI::LogosAPI(const QString& module_name, QObject *parent) // Get token manager instance m_token_manager = &TokenManager::instance(); + + // Register LogosResult as QVariant type + qRegisterMetaType("LogosResult"); } LogosAPI::~LogosAPI() { // Provider and client will be automatically deleted as child objects // Token manager is a singleton, so we don't delete it + + } LogosAPIProvider* LogosAPI::getProvider() const diff --git a/cpp/logos_api.h b/cpp/logos_api.h index 3df2229..eb9b4ae 100644 --- a/cpp/logos_api.h +++ b/cpp/logos_api.h @@ -4,6 +4,7 @@ #include #include #include +#include "logos_types.h" class LogosAPIClient; class LogosAPIProvider; @@ -57,4 +58,4 @@ private: TokenManager* m_token_manager; }; -#endif // LOGOS_API_H \ No newline at end of file +#endif // LOGOS_API_H \ No newline at end of file diff --git a/cpp/logos_types.cpp b/cpp/logos_types.cpp new file mode 100644 index 0000000..71fe721 --- /dev/null +++ b/cpp/logos_types.cpp @@ -0,0 +1,11 @@ +#include "logos_types.h" + +QDataStream& operator<<(QDataStream& out, const LogosResult& result) { + out << result.success << result.value; + return out; +} + +QDataStream& operator>>(QDataStream& in, LogosResult& result) { + in >> result.success >> result.value; + return in; +} \ No newline at end of file diff --git a/cpp/logos_types.h b/cpp/logos_types.h new file mode 100644 index 0000000..f89928f --- /dev/null +++ b/cpp/logos_types.h @@ -0,0 +1,138 @@ +#ifndef LOGOS_TYPES_H +#define LOGOS_TYPES_H + +#include +#include +#include + +class LogosResultException : public std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; + +struct LogosResult +{ + bool success; + // Error message if success is false + // Value can be retrieved like this: + // + // LogosResult result = someMethod(); + // if (result.success) { + // QString someValue = result.getValue(); + // // OR + // QString someValue = result.getString(); + // } + QVariant value; + + // LogosResult result = someMethod(); + // if (!result.success) { + // QString error = result.getError(); + // } + QVariant error; + + template + T getError() const + { + if (success) { + throw LogosResultException("Attempted to get error from a successful LogosResult"); + } + return error.value(); + } + + template + T getValue() const + { + if (!success) { + throw LogosResultException("Attempted to get value from a failed LogosResult: " + + error.toString().toStdString()); + } + return value.value(); + } + + template + T getValue(const QString &key, T defaultValue = T()) const + { + const QVariantMap &map = getValue(); + if (!map.contains(key)) { + return defaultValue; + } + return qvariant_cast(map.value(key)); + } + + template + T getValue(int index, const QString &key, T defaultValue = T()) const + { + const QVariantList &list = getValue(); + + if (index < 0 || index >= list.size()) { + return defaultValue; + } + + return qvariant_cast(list[index].toMap().value(key, defaultValue)); + } + + QString getString() const { return getValue(); } + + QString getString(const QString &key, const QString &defaultValue = "") const + { + return getValue(key, defaultValue); + } + + QString getString(int index, const QString &key, const QString &defaultValue = "") const + { + return getValue(index, key, defaultValue); + } + + bool getBool() const { return getValue(); } + + bool getBool(const QString &key) const { return getValue(key); } + + bool getBool(int index, const QString &key) const { return getValue(index, key); } + + int getInt() const { return getValue(); } + + int getInt(const QString &key, int defaultValue = 0) const + { + return getValue(key, defaultValue); + } + + int getInt(int index, const QString &key, int defaultValue = 0) const + { + return getValue(index, key, defaultValue); + } + + QVariantList getList() const { return getValue(); } + + QVariantList getList(const QString &key, const QVariantList &defaultValue = QVariantList()) const + { + return getValue(key, defaultValue); + } + + QVariantList getList(int index, + const QString &key, + const QVariantList &defaultValue = QVariantList()) const + { + return getValue(index, key, defaultValue); + } + + QVariantMap getMap() const { return getValue(); } + + QVariantMap getMap(const QString &key, const QVariantMap &defaultValue = QVariantMap()) const + { + return getValue(key, defaultValue); + } + + QVariantMap getMap(int index, + const QString &key, + const QVariantMap &defaultValue = QVariantMap()) const + { + return getValue(index, key, defaultValue); + } +}; + +// Provide (de)serialisation for being use as Remote Object +QDataStream &operator<<(QDataStream &out, const LogosResult &result); +QDataStream &operator>>(QDataStream &in, LogosResult &result); + +#endif diff --git a/cpp/module_proxy.cpp b/cpp/module_proxy.cpp index 35d7884..d29cdc1 100644 --- a/cpp/module_proxy.cpp +++ b/cpp/module_proxy.cpp @@ -94,7 +94,7 @@ namespace { ); break; } - case QMetaType::QUrl: { + case QMetaType::QUrl: { auto value = new QUrl{arg.toUrl()}; scopedArgs.emplace_back( Q_ARG(QUrl, *value), @@ -104,6 +104,16 @@ namespace { ); break; } + case QMetaType::Bool: { + auto value = new bool{arg.toBool()}; + scopedArgs.emplace_back( + Q_ARG(bool, *value), + [](const void* data) { + delete static_cast(data); + } + ); + break; + } case QMetaType::QString: default: { auto value = new QString{arg.toString()}; @@ -155,6 +165,8 @@ namespace { INVOKE_METHOD_WITH_RETURN(int, int); } else if (strcmp(returnTypeName, "QString") == 0) { INVOKE_METHOD_WITH_RETURN(QString, QString); + } else if (strcmp(returnTypeName, "LogosResult") == 0) { + INVOKE_METHOD_WITH_RETURN(LogosResult, LogosResult); } else if (strcmp(returnTypeName, "QVariant") == 0) { INVOKE_METHOD_WITH_RETURN(QVariant, QVariant); } else if (strcmp(returnTypeName, "QJsonArray") == 0) { @@ -344,7 +356,16 @@ QVariant ModuleProxy::callRemoteMethod(const QString& authToken, const QString& if (success) { result = QVariant(stringResult); } - } else if (returnType == QMetaType::fromType()) { + } + else if (returnType == QMetaType::fromType()) { + // LogosResult return type + LogosResult logosResult; + success = invokeMethodByArgCount(m_module, methodName, args, &logosResult, "LogosResult"); + if (success) { + result = QVariant::fromValue(logosResult); + } + } + else if (returnType == QMetaType::fromType()) { // QVariant return type QVariant variantResult; success = invokeMethodByArgCount(m_module, methodName, args, &variantResult, "QVariant"); diff --git a/nix/include.nix b/nix/include.nix index ecc283f..fa2be04 100644 --- a/nix/include.nix +++ b/nix/include.nix @@ -25,7 +25,7 @@ pkgs.stdenv.mkDerivation { fi # Install cpp headers and sources - for file in logos_api.cpp logos_api.h logos_api_client.cpp logos_api_client.h \ + for file in logos_types.cpp logos_types.h logos_api.cpp logos_api.h logos_api_client.cpp logos_api_client.h \ logos_api_consumer.cpp logos_api_consumer.h logos_api_provider.cpp logos_api_provider.h \ token_manager.cpp token_manager.h module_proxy.cpp module_proxy.h logos_mode.h; do if [ -f cpp/$file ]; then