mirror of
https://github.com/logos-co/logos-plugin-qt.git
synced 2026-08-27 08:51:07 +00:00
* fix(tokens): follow logos-protocol's inbound/outbound store split TokenManager now keeps INBOUND (caller -> what I issued them) and OUTBOUND (callee -> what I present) in separate maps, with the trust anchor as a scalar credential rather than a map entry. Call sites here move to the accessor that names the direction they meant. The pre-split single map made a grant one way a grant BOTH ways: a token minted so M could call B was found by B's client when B called M, so requestModule was skipped and the access policy never ran. See logos-protocol's companion change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(glue): route informModuleToken through the INBOUND door The emitted <Provider>::informModuleToken wrote the same value through both doors: LogosProviderBase::informModuleToken (inbound, correct) and logos_module_accept_token (which is the OUTBOUND door). The value is a CALLER's token — capability_module saying "moduleName may call you" — so the second write filed a caller's inbound token as an outbound credential inside the cdylib's own protocol copy. That is the one-way-grant bypass, reproduced one image deeper. It now calls logos_module_accept_inbound_token (protocol 0.8). onInit's anchor seeding keeps logos_module_accept_token, because THAT one is genuinely outbound: it is the module's own credential for calling core and capability_module. The comment says so on both sides — the two paths look interchangeable and are not. Below 0.8 the old write stays in an #else: dropping it would break the module's outbound calls to that peer, which is a regression, not a fix. Guards are expanded MAJOR-aware arithmetic, unifdef-resolvable. Requires logos-protocol fix/token-direction-key-namespace (59b27ef). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps): relock logos-protocol to 0.8, and raise the consumer bound with it WHAT MOVED. logos-protocol b37a2e9f -> 42460e5b (0.7 -> 0.8), the single node in flake.lock; nothing else in the lock changed. WHY IT HAD TO. This branch emits the informModuleToken glue through logos_module_accept_inbound_token, which joins the module-impl C ABI at protocol 0.8 and does not exist before it (21 -> 23 logos_module_* lines in logos_module_impl.h). The emission sits behind an expanded MINOR >= 8 guard, so at the old pin the door was simply compiled away: the qt-host-generator check's `grep -q logos_module_accept_inbound_token inform-0.8` had nothing to find. The lock was the whole of the failure -- no code defect underneath it. Note the pin this moves is b37a2e9f and not the 6c24fcb1 this branch forked from: master merged #27 in between, and its lock had already moved. That merge is the commit below this one. Without it, the relocked flake.lock conflicts with master on the same three lines and CI -- which builds the PR MERGE ref -- cannot check the branch out at all. THE BOUND. #27 shipped cpp/logos_consumer.h with the fleet's only UPPER bound, `MINOR > 7` spelled as an #error, precisely so that a protocol bump past the consumer-admission contract stops the build instead of silently emptying every isolated identity's token store. Relocking to 0.8 fires it by design. The review it asks for, carried out rather than assumed: * bootstrapKeys(), adoptCredential() and adoptCredentialFor() are signature- and semantics-identical across b37a2e9f -> 42460e5b. 0.8 moved direction into the KEY NAMESPACE -- inbound a reserved-prefix key, outbound the bare peer name -- and deliberately left TokenManager's layout byte-identical. * credential() became DERIVED from bootstrapKeys() rather than cached, which strengthens this path: a cached field read empty on a store another image wrote and then refused every push. * 0.8's own adoptCredential() contract documents both halves admitConsumer depends on -- outbound, capability_module's proxy resolves the presented credential from the caller-keyed INBOUND record rather than an anchor key, so the caller is named as the identity and not as the host; inbound, capability_module pushes with getToken(moduleName), which IS that credential, so informModuleToken's trusted-channel gate still passes. So the bound is raised 7 -> 8, with that reasoning recorded at the guard. The oracle is the consumer-admission check and not the argument: it runs a real ModuleProxy in Local mode, and its "NO LOCKOUT: the consumer's own credential authorizes at capability_module" / "and it is NAMED as itself, not as the host" assertions are exactly the failure the #error exists to prevent. Both pass at 0.8. It is negative-validated upstream (removing the adopt step fails 5 checks, swapping the order fails 2), so its green is worth something. ALL 12 CHECKS BUILT INDIVIDUALLY, x86_64-linux, from source against cache.nixos.org only (cache.nix.logos.co is returning 502): PASS vanilla-plugin /nix/store/ab6l4a0czh4nd4h153i8hz82qayb4ah4-logos-plugin-qt-vanilla-test-0.0.1 PASS header-generator-guard /nix/store/1dxrvzk4d1r3babyfx6hfnv9va1im5v6-logos-plugin-qt-header-generator-guard-test PASS headers-emitter-routing /nix/store/yrjzx24bq010m3z0xm0djvmr3n1wn1fx-logos-plugin-qt-headers-emitter-routing-test PASS consumer-api-style-gate /nix/store/r5h073ygr1zfy763dmhxssgw9mcl96y8-logos-plugin-qt-consumer-api-style-gate-test PASS qt-host /nix/store/mndrdrxcad6kq056jcrxf946iycp5yqg-logos-qt-host-0.1.0 PASS shared-runtime-layering /nix/store/npzah7n5zj7gdlbs1d59ry0idd9vp6si-logos-qt-host-shared-runtime-layering PASS qt-host-generator /nix/store/xqlhpbz7bnfvz7c17x1aanfassglmrq6-logos-qt-host-generator-test PASS unload-contract /nix/store/2y64h1im2biyqpbmg0bi591rznl860yx-logos-qt-host-unload-contract-test PASS caller-contract /nix/store/w1lz78kfy91xcyfd35i277f030jjf0ag-logos-qt-host-caller-contract-test PASS caller-invokable /nix/store/68b83pvv90xsqszihgshpb5g3fikfmj4-logos-qt-host-caller-invokable-test-0.1.0 PASS consumer-admission /nix/store/hiwnlafxhh5gz0f1pkdi53glw66qm3rq-logos-qt-host-consumer-admission-test-0.1.0 PASS glue-compiles /nix/store/bx9qfp520yazhmmc1in37frsscs6iii8-logos-qt-host-glue-compiles-test-0.1.0 The qt-host closure references logos-protocol-lib-0.8.0, so the relock is in the artefact and not merely in the lock file. CI itself runs only 4 of these 12. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
547 lines
24 KiB
C++
547 lines
24 KiB
C++
#include "qt_provider_object.h"
|
|
#include "../core/interface.h"
|
|
#include "logos_api.h"
|
|
#include "token_manager.h"
|
|
#include "logos_types.h"
|
|
#include "logos_qt_arg_decode.h"
|
|
#include <QDebug>
|
|
#include <QMetaObject>
|
|
#include <QMetaMethod>
|
|
#include <QMetaType>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QStringList>
|
|
#include <QUrl>
|
|
|
|
// ── QMetaObject dispatch helpers (moved from module_proxy.cpp) ──────────────
|
|
|
|
#define INVOKE_METHOD_WITH_RETURN(returnType, castType) \
|
|
do { \
|
|
castType* result = static_cast<castType*>(returnValue); \
|
|
switch (args.size()) { \
|
|
case 0: \
|
|
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result)); \
|
|
case 1: \
|
|
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg); \
|
|
case 2: \
|
|
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg); \
|
|
case 3: \
|
|
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg); \
|
|
case 4: \
|
|
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg); \
|
|
case 5: \
|
|
return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, Q_RETURN_ARG(returnType, *result), scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg); \
|
|
default: \
|
|
qWarning() << "QtProviderObject: Currently supports 0-5 arguments. Got:" << args.size(); \
|
|
return false; \
|
|
} \
|
|
} while(0)
|
|
|
|
namespace {
|
|
class ScopedQArg {
|
|
public:
|
|
ScopedQArg(QMetaMethodArgument a, std::function<void(const void*)> d)
|
|
: arg(a), deleter(std::move(d)) {}
|
|
|
|
~ScopedQArg() {
|
|
if (deleter) {
|
|
deleter(arg.data);
|
|
}
|
|
}
|
|
|
|
ScopedQArg(ScopedQArg&& other)
|
|
: arg(std::move(other.arg)), deleter(std::move(other.deleter)) {
|
|
other.deleter = nullptr;
|
|
}
|
|
ScopedQArg& operator=(ScopedQArg&&) = delete;
|
|
ScopedQArg(const ScopedQArg&) = delete;
|
|
ScopedQArg& operator=(const ScopedQArg&) = delete;
|
|
|
|
QMetaMethodArgument arg;
|
|
|
|
private:
|
|
std::function<void(const void*)> deleter;
|
|
};
|
|
|
|
// Every `new T(...)` below is a copy-construction from a value that is
|
|
// ALREADY a T, and every one of them uses parentheses deliberately.
|
|
//
|
|
// With braces, `new QVariantList{arg.toList()}` is list-initialization of a
|
|
// QList<QVariant> from a single QVariantList — and because QVariantList is
|
|
// implicitly convertible to QVariant, QList's initializer_list<QVariant>
|
|
// constructor is also viable. Which one wins is exactly the question CWG
|
|
// 2137 covers, and the compilers answer it differently: Clang picks the
|
|
// copy constructor, GCC picks the initializer-list one and WRAPS the value,
|
|
// so a two-element [1,2] reached the method body as [[1,2]], size 1. That
|
|
// is why this only ever failed on Linux CI.
|
|
//
|
|
// Parentheses cannot select an initializer-list constructor, so they mean
|
|
// the same thing on both compilers. QVariantList was the only type here the
|
|
// divergence could reach — QStringList and QVariantMap are safe only
|
|
// because QString and std::pair are not constructible from their own
|
|
// container — which is precisely why the rule is applied uniformly rather
|
|
// than to the one case that bit.
|
|
auto toScopedQArgs(const QVariantList& args)
|
|
{
|
|
auto scopedArgs = std::vector<ScopedQArg>{};
|
|
for (const auto& arg : args) {
|
|
switch (arg.typeId()) {
|
|
case QMetaType::Int: {
|
|
auto value = new int(arg.toInt());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(int, *value),
|
|
[](const void* data) { delete static_cast<const int*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::LongLong: {
|
|
auto value = new qlonglong(arg.toLongLong());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(qlonglong, *value),
|
|
[](const void* data) { delete static_cast<const qlonglong*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::ULongLong: {
|
|
auto value = new qulonglong(arg.toULongLong());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(qulonglong, *value),
|
|
[](const void* data) { delete static_cast<const qulonglong*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
// A typed-numeric array ([int]/[uint]/[float64]/[bool]) maps to
|
|
// QVariantList (only [tstr] maps to QStringList). Without this
|
|
// case such an arg fell to the QString default below, stringified
|
|
// to "" and failed the typed invokeMethod → an EMPTY list arrived.
|
|
case QMetaType::QVariantList: {
|
|
auto value = new QVariantList(arg.toList());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(QVariantList, *value),
|
|
[](const void* data) { delete static_cast<const QVariantList*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::QVariantMap: {
|
|
auto value = new QVariantMap(arg.toMap());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(QVariantMap, *value),
|
|
[](const void* data) { delete static_cast<const QVariantMap*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::QStringList: {
|
|
auto value = new QStringList(arg.toStringList());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(QStringList, *value),
|
|
[](const void* data) { delete static_cast<const QStringList*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::QByteArray: {
|
|
auto value = new QByteArray(arg.toByteArray());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(QByteArray, *value),
|
|
[](const void* data) { delete static_cast<const QByteArray*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::QUrl: {
|
|
auto value = new QUrl(arg.toUrl());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(QUrl, *value),
|
|
[](const void* data) { delete static_cast<const QUrl*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::Bool: {
|
|
auto value = new bool(arg.toBool());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(bool, *value),
|
|
[](const void* data) { delete static_cast<const bool*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::Double: {
|
|
auto value = new double(arg.toDouble());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(double, *value),
|
|
[](const void* data) { delete static_cast<const double*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::Float: {
|
|
auto value = new float(arg.toFloat());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(float, *value),
|
|
[](const void* data) { delete static_cast<const float*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
case QMetaType::QString:
|
|
default: {
|
|
auto value = new QString(arg.toString());
|
|
scopedArgs.emplace_back(
|
|
Q_ARG(QString, *value),
|
|
[](const void* data) { delete static_cast<const QString*>(data); }
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return scopedArgs;
|
|
}
|
|
|
|
bool invokeMethodByArgCount(QObject *module, const QString& methodName, const QVariantList& args, void* returnValue, const char* returnTypeName)
|
|
{
|
|
QByteArray methodNameBytes = methodName.toUtf8();
|
|
const char* methodNameCStr = methodNameBytes.constData();
|
|
|
|
auto scopedArgs = toScopedQArgs(args);
|
|
|
|
if (returnValue == nullptr) {
|
|
switch (args.size()) {
|
|
case 0: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection);
|
|
case 1: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg);
|
|
case 2: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg);
|
|
case 3: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg);
|
|
case 4: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg);
|
|
case 5: return QMetaObject::invokeMethod(module, methodNameCStr, Qt::DirectConnection, scopedArgs[0].arg, scopedArgs[1].arg, scopedArgs[2].arg, scopedArgs[3].arg, scopedArgs[4].arg);
|
|
default:
|
|
qWarning() << "QtProviderObject: Currently supports 0-5 arguments. Got:" << args.size();
|
|
return false;
|
|
}
|
|
} else if (strcmp(returnTypeName, "bool") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(bool, bool);
|
|
} else if (strcmp(returnTypeName, "int") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(int, int);
|
|
} else if (strcmp(returnTypeName, "double") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(double, double);
|
|
} else if (strcmp(returnTypeName, "float") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(float, float);
|
|
} 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) {
|
|
INVOKE_METHOD_WITH_RETURN(QJsonArray, QJsonArray);
|
|
} else if (strcmp(returnTypeName, "QVariantList") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(QVariantList, QVariantList);
|
|
} else if (strcmp(returnTypeName, "QVariantMap") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(QVariantMap, QVariantMap);
|
|
} else if (strcmp(returnTypeName, "QStringList") == 0) {
|
|
INVOKE_METHOD_WITH_RETURN(QStringList, QStringList);
|
|
} else {
|
|
qWarning() << "QtProviderObject: Unsupported return type:" << returnTypeName;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── QtProviderObject implementation ─────────────────────────────────────────
|
|
|
|
QtProviderObject::QtProviderObject(QObject* module, QObject* parent)
|
|
: QObject(parent)
|
|
, m_module(module)
|
|
{
|
|
if (m_module) {
|
|
connect(m_module, SIGNAL(eventResponse(QString, QVariantList)),
|
|
this, SLOT(onWrappedEventResponse(QString, QVariantList)));
|
|
qDebug() << "[LogosProviderObject] QtProviderObject: connected to QObject eventResponse signal";
|
|
}
|
|
}
|
|
|
|
QtProviderObject::~QtProviderObject()
|
|
{
|
|
qDebug() << "[LogosProviderObject] QtProviderObject: destroyed";
|
|
}
|
|
|
|
void QtProviderObject::onWrappedEventResponse(const QString& eventName, const QVariantList& data)
|
|
{
|
|
if (m_eventCallback) {
|
|
m_eventCallback(eventName, data);
|
|
}
|
|
}
|
|
|
|
void QtProviderObject::init(void* apiInstance)
|
|
{
|
|
if (!m_module) return;
|
|
|
|
LogosAPI* api = static_cast<LogosAPI*>(apiInstance);
|
|
|
|
int methodIndex = m_module->metaObject()->indexOfMethod("initLogos(LogosAPI*)");
|
|
if (methodIndex != -1) {
|
|
qDebug() << "[LogosProviderObject] QtProviderObject: calling initLogos on wrapped QObject";
|
|
QMetaObject::invokeMethod(m_module, "initLogos",
|
|
Qt::DirectConnection,
|
|
Q_ARG(LogosAPI*, api));
|
|
} else {
|
|
qDebug() << "[LogosProviderObject] QtProviderObject: wrapped QObject has no initLogos, skipping";
|
|
}
|
|
}
|
|
|
|
QString QtProviderObject::providerName() const
|
|
{
|
|
PluginInterface* pi = qobject_cast<PluginInterface*>(m_module);
|
|
return pi ? pi->name() : QString();
|
|
}
|
|
|
|
QString QtProviderObject::providerVersion() const
|
|
{
|
|
PluginInterface* pi = qobject_cast<PluginInterface*>(m_module);
|
|
return pi ? pi->version() : QString();
|
|
}
|
|
|
|
void QtProviderObject::setEventListener(EventCallback callback)
|
|
{
|
|
m_eventCallback = std::move(callback);
|
|
}
|
|
|
|
QVariant QtProviderObject::callMethod(const QString& methodName, const QVariantList& args)
|
|
{
|
|
if (!m_module) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: null module";
|
|
return QVariant();
|
|
}
|
|
|
|
if (methodName.isEmpty()) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: empty method name";
|
|
return QVariant();
|
|
}
|
|
|
|
// Special-case getPluginMethods (framework-level, not on the wrapped plugin)
|
|
if (methodName == "getPluginMethods" && args.isEmpty()) {
|
|
return QVariant(getMethods());
|
|
}
|
|
|
|
// Special-case getPluginEvents / getPluginInterface. Legacy Qt modules have
|
|
// no logos_events: section, so getMethods() (built here from QMetaObject)
|
|
// only ever contains methods: events are always empty and the interface is
|
|
// just the methods list.
|
|
if (methodName == "getPluginEvents" && args.isEmpty()) {
|
|
return QVariant(QJsonArray());
|
|
}
|
|
|
|
if (methodName == "getPluginInterface" && args.isEmpty()) {
|
|
return QVariant(getMethods());
|
|
}
|
|
|
|
// No auth check here by design: this adapter has no token parameter and is
|
|
// only ever reached through ModuleProxy::callRemoteMethod, which authorizes
|
|
// the caller's token before dispatching (see ModuleProxy::isAuthorized).
|
|
// The checks below are sanity guards on the wrapped plugin, not authz.
|
|
PluginInterface* pluginInterface = qobject_cast<PluginInterface*>(m_module);
|
|
if (!pluginInterface) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: module is not a PluginInterface";
|
|
return QVariant();
|
|
}
|
|
|
|
LogosAPI* api = pluginInterface->logosAPI;
|
|
if (!api) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::callMethod: LogosAPI not available";
|
|
return QVariant();
|
|
}
|
|
|
|
// Find method via QMetaObject
|
|
const QMetaObject* metaObject = m_module->metaObject();
|
|
int methodIndex = -1;
|
|
for (int i = 0; i < metaObject->methodCount(); ++i) {
|
|
QMetaMethod method = metaObject->method(i);
|
|
if (method.name() == methodName && method.parameterCount() == args.size()) {
|
|
methodIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (methodIndex == -1) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject: method not found:" << methodName
|
|
<< "with" << args.size() << "arguments";
|
|
return QVariant();
|
|
}
|
|
|
|
QMetaMethod method = metaObject->method(methodIndex);
|
|
QMetaType returnType = method.returnMetaType();
|
|
|
|
// Decode args against the types the method actually declares.
|
|
//
|
|
// This used to be a bare QVariant::convert(paramType), which COERCES: an
|
|
// argument the declared type cannot represent silently became one it can
|
|
// (echoInt(3.7) ran the body with 4; echoBool("hello") with true;
|
|
// stringLength(42) with "42"), so the author's own precondition checks
|
|
// never saw the value the caller sent. logos::qtArgDecode routes the value
|
|
// through the canonical codec instead — the same rule the cdylib dispatch,
|
|
// the Rust provider and the plain wire apply — and a mismatch becomes the
|
|
// canonical {"code":"dispatch_failed", ...} answer rather than a plausible
|
|
// wrong value.
|
|
//
|
|
// A type the codec has no rule for (QUrl, an enum, a pointer) reports
|
|
// Unchecked and keeps the old conversion verbatim: inventing a rule for it
|
|
// here would reject arguments that work today.
|
|
QVariantList coercedArgs = args;
|
|
for (int i = 0; i < method.parameterCount() && i < coercedArgs.size(); ++i) {
|
|
QMetaType paramType = method.parameterMetaType(i);
|
|
|
|
QVariant decoded;
|
|
std::string reason;
|
|
const logos::QtArgVerdict verdict = logos::qtArgDecode(
|
|
coercedArgs[i], paramType, "arg" + std::to_string(i), decoded, reason);
|
|
|
|
if (verdict == logos::QtArgVerdict::Rejected) {
|
|
const QString message = QString::fromStdString(reason);
|
|
qWarning() << "[LogosProviderObject] QtProviderObject: rejected"
|
|
<< methodName << "-" << message;
|
|
return logos::dispatchFailedVariant(providerName(), message);
|
|
}
|
|
if (verdict == logos::QtArgVerdict::Ok) {
|
|
coercedArgs[i] = decoded;
|
|
continue;
|
|
}
|
|
if (coercedArgs[i].metaType() != paramType) {
|
|
QVariant converted = coercedArgs[i];
|
|
if (converted.convert(paramType)) {
|
|
coercedArgs[i] = converted;
|
|
} else {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject: could not convert arg" << i
|
|
<< "from" << coercedArgs[i].typeName()
|
|
<< "to" << paramType.name()
|
|
<< "for method" << methodName;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool success = false;
|
|
QVariant result;
|
|
|
|
if (returnType == QMetaType::fromType<void>()) {
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, nullptr, nullptr);
|
|
if (success) result = QVariant(true);
|
|
} else if (returnType == QMetaType::fromType<bool>()) {
|
|
bool v = false;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "bool");
|
|
if (success) result = QVariant(v);
|
|
} else if (returnType == QMetaType::fromType<int>()) {
|
|
int v = 0;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "int");
|
|
if (success) result = QVariant(v);
|
|
} else if (returnType == QMetaType::fromType<double>()) {
|
|
double v = 0.0;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "double");
|
|
if (success) result = QVariant(v);
|
|
} else if (returnType == QMetaType::fromType<float>()) {
|
|
float v = 0.0f;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "float");
|
|
if (success) result = QVariant(v);
|
|
} else if (returnType == QMetaType::fromType<QString>()) {
|
|
QString v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "QString");
|
|
if (success) result = QVariant(v);
|
|
} else if (returnType == QMetaType::fromType<LogosResult>()) {
|
|
LogosResult v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "LogosResult");
|
|
if (success) result = QVariant::fromValue(v);
|
|
} else if (returnType == QMetaType::fromType<QVariant>()) {
|
|
QVariant v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "QVariant");
|
|
if (success) result = v;
|
|
} else if (returnType == QMetaType::fromType<QJsonArray>()) {
|
|
QJsonArray v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "QJsonArray");
|
|
if (success) result = QVariant(v);
|
|
} else if (returnType == QMetaType::fromType<QVariantList>()) {
|
|
QVariantList v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "QVariantList");
|
|
if (success) result = QVariant::fromValue(v);
|
|
} else if (returnType == QMetaType::fromType<QVariantMap>()) {
|
|
QVariantMap v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "QVariantMap");
|
|
if (success) result = QVariant::fromValue(v);
|
|
} else if (returnType == QMetaType::fromType<QStringList>()) {
|
|
QStringList v;
|
|
success = invokeMethodByArgCount(m_module, methodName, coercedArgs, &v, "QStringList");
|
|
if (success) result = QVariant(v);
|
|
} else {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject: unsupported return type:"
|
|
<< returnType.name() << "for method:" << methodName;
|
|
return QVariant();
|
|
}
|
|
|
|
if (!success) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject: failed to invoke" << methodName;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool QtProviderObject::informModuleToken(const QString& moduleName, const QString& token)
|
|
{
|
|
PluginInterface* pluginInterface = qobject_cast<PluginInterface*>(m_module);
|
|
if (!pluginInterface) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: not a PluginInterface";
|
|
return false;
|
|
}
|
|
|
|
LogosAPI* api = pluginInterface->logosAPI;
|
|
if (!api) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: LogosAPI not available";
|
|
return false;
|
|
}
|
|
|
|
TokenManager* tokenManager = api->getTokenManager();
|
|
if (!tokenManager) {
|
|
qWarning() << "[LogosProviderObject] QtProviderObject::informModuleToken: TokenManager not available";
|
|
return false;
|
|
}
|
|
|
|
// The INBOUND door, for the same reason as LogosProviderBase::
|
|
// informModuleToken above: `moduleName` is the CALLER, not a callee.
|
|
qDebug() << "[LogosProviderObject] QtProviderObject: saving inbound token for caller:" << moduleName;
|
|
tokenManager->saveInboundToken(moduleName, token);
|
|
return true;
|
|
}
|
|
|
|
QJsonArray QtProviderObject::getMethods()
|
|
{
|
|
if (!m_module) return QJsonArray();
|
|
|
|
QJsonArray methodsArray;
|
|
const QMetaObject* metaObject = m_module->metaObject();
|
|
|
|
for (int i = 0; i < metaObject->methodCount(); ++i) {
|
|
QMetaMethod method = metaObject->method(i);
|
|
|
|
if (method.enclosingMetaObject() != metaObject) {
|
|
continue;
|
|
}
|
|
|
|
QJsonObject methodObj;
|
|
methodObj["signature"] = QString::fromUtf8(method.methodSignature());
|
|
methodObj["name"] = QString::fromUtf8(method.name());
|
|
methodObj["returnType"] = QString::fromUtf8(method.typeName());
|
|
methodObj["isInvokable"] = method.isValid() &&
|
|
(method.methodType() == QMetaMethod::Method || method.methodType() == QMetaMethod::Slot);
|
|
|
|
if (method.parameterCount() > 0) {
|
|
QJsonArray params;
|
|
for (int p = 0; p < method.parameterCount(); ++p) {
|
|
QJsonObject paramObj;
|
|
paramObj["type"] = QString::fromUtf8(method.parameterTypeName(p));
|
|
QByteArrayList paramNames = method.parameterNames();
|
|
if (p < paramNames.size() && !paramNames.at(p).isEmpty()) {
|
|
paramObj["name"] = QString::fromUtf8(paramNames.at(p));
|
|
} else {
|
|
paramObj["name"] = QString("param%1").arg(p);
|
|
}
|
|
params.append(paramObj);
|
|
}
|
|
methodObj["parameters"] = params;
|
|
}
|
|
|
|
methodsArray.append(methodObj);
|
|
}
|
|
|
|
return methodsArray;
|
|
}
|
|
|
|
#include "moc_qt_provider_object.cpp"
|