Commit Graph
5 Commits
Author SHA1 Message Date
Dario Lipicar b594d1a38c fix(generator): the Qt consumer must not flatten a rejection into an empty value (#129)
* fix(generator): the Qt consumer must not flatten a rejection into an empty value

A provider that REJECTS a call answers the canonical
{"code":"dispatch_failed", "message":..., "origin":...} object as its RESULT,
not as a transport error — the same object from every provider flavour
(logos-qt-sdk dispatchFailedVariant, the generated cdylib dispatch, the Rust
provider's args::dispatch_failed).

The generated Qt consumer wrapper converted it like any other value, which
ERASES it. `_result.toList()` on that map is `[]`, `.toString()` is "",
`.toLongLong()` is 0 — so `echoUintList([1, -1, 3])`, answered `dispatch_failed`
by the provider, reached the caller as `[]`: the whole list, not the bad
element, and indistinguishable from "the provider returned nothing". Losing the
error is a consumer bug whatever the provider did.

Reported through the channel this surface already uses for a failed call: the
`logos::CallError*` out-parameter every generated sync method carries (today
"object_unavailable" when the target cannot be acquired). No signature changes,
no return value changes — a caller that passes `err` now sees
code="dispatch_failed" with the provider's diagnostic and origin; a caller that
does not gets the same default it always got, plus the qWarning the wrapper
already emits for a failed call.

  * the detector is emitted once per wrapper, in an anonymous namespace, and
    matches EXACTLY (three string fields, that code) for the same reason
    logos_rpc_status.h's isUnauthorizedSentinel matches exactly: an `any` or map
    return carrying user data must never false-match.
  * the result is now captured for `void` returns too — a void method can be
    rejected, and the rejection object is the only place that says so.
  * the async overload's callback takes the value alone and has no error
    parameter; giving it one would change the generated public surface (which
    logos-qt-sdk's veneer mirrors 1:1), so an async rejection is logged rather
    than delivered. Recorded in cpp-generator/docs/project.md.

Scope: CONSUMER side only. The provider half of registry entry Q1 (a Qt-typed
provider cannot validate the ELEMENT of a typed numeric array, because a C++
signature spells [uint] and [any] alike as QVariantList) is explicitly out of
scope and unchanged. The lp wrapper is untouched — its generated output is
byte-identical before and after.

* fix(generator): guard the emitted detector — the umbrella is one translation unit

`logos_sdk.cpp` textually `#include`s every generated `<dep>_api.cpp`, so a
module with more than one dependency compiles several copies of the detector
into ONE translation unit:

    test_fullapi_rust_api.cpp:15:6: error: redefinition of 'logosDispatchRejection'

Internal linkage covers the separate-TU case; only the preprocessor covers this
one. Found by building test_fullapi_qtproxy (3 wrappers: two concrete deps plus
the bound `full_api` interface) against this generator — a single-wrapper
contract cannot reach it.
2026-08-03 09:41:55 -03:00
Dario Lipicar e8966bf7a9 fix(codegen): correct [T]-array arg packing and any-value return in the Qt client (#105)
* fix(codegen): pack Qt client args as one element each, not a spread list

The generated Qt client wrapper packed a method's arguments with
`QVariantList{a, b, ...}` (sync) and `QVariantList{...}` / `QVariantList() << a`
(async). For a QVariantList-typed argument -- every `[T]` list type (`[any]`,
`[int]`, `[uint]`, `[float64]`, `[bool]`) -- a braced `QVariantList{v}` and
`<< v` both CONCATENATE the list's elements into the args list, so
`echoList([1,2,3])` went out as three positional args instead of one array arg.
The receiver saw an arg-count mismatch and the list round-tripped empty; through
a UI->proxy->provider 2-hop it hung the call outright. This is the long-standing
"typed arrays empty over the Qt path" bug.

Fix both generators that emit the Qt client:
- legacy `generator_lib.cpp` (the production `logos-cpp-generator`): wrap each
  arg in `QVariant::fromValue(...)` in the sync and async call sites.
- experimental `lidl_gen_client.cpp`: route both paths through the existing
  `packVariantList` helper (which already wraps with `QVariant::fromValue`), the
  same helper the event `trigger` path uses.

`QVariant::fromValue` does not double-wrap an already-QVariant (`any`) arg, and
scalars/QString/QVariantMap/QByteArray were never affected (they don't
concatenate). Empirically: `QVariantList{v}` / `<< v` give size 3 for a 3-element
list; the wrapped forms give size 1.

Tests: legacy generator_tests gain ListArgWrappedAsOneElement and update the
param-packing assertions to the wrapped form; experimental gains
ListArgIsPackedAsOneElement. 167/167 green.

* fix(codegen): pass `any` (QVariant) return through raw in the lp wrapper

The Qt-free (lp) client generator maps both `any` (QVariant) and the `{tstr:any}`
map (QVariantMap) to the same `LogosMap` std type, and decoded a `LogosMap`
return as `jv.is_object() ? jv : LogosMap::object()`. For a genuine map that is
a no-op, but for `any` it collapsed every NON-object value (a string, a number,
an array) to an empty object `{}`. A universal proxy forwarding `echoAny("x")`
through this wrapper therefore returned `{}` instead of `"x"` — the concrete
cross-version blocker (the UI's runMethods verified echoAny and got FAIL:echoAny
through the 2-hop).

`lpFromJsonExpr` still receives the original Qt type, so it can tell `any`
(mapReturnType == "QVariant") from the map (QVariantMap): pass `any` through
unchanged, keep the object coercion only for the map.

Test: MakeSourceTest.LpAnyReturnPassesThroughButMapForcesObject.

With this + the arg-spread fix, a UI drives the full method surface (incl.
echoAny and every array type) through a universal proxy 2-hop end to end.
2026-07-19 01:03:30 -03:00
Dario LipicarandClaude Opus 4.8 f0fe8cbfeb Make the base SDK Qt-free: Qt developer layer moves to logos-qt-sdk (#83)
* Extract the protocol layer into logos-protocol; consume it as a flake input

The transport/token/IPC layer (transports incl. QRO + plain TCP/TLS,
consumer core LogosAPIClient/LogosAPIConsumer with the capability
auto-requestModule flow, ModuleProxy, token manager, QVariant<->JSON
conversion, the abstract LogosProviderObject interface) now lives in the
logos-protocol repo behind the versioned lp_* C ABI.

This SDK keeps the typed C++ developer layer (LogosAPI, provider base
classes + Qt provider glue, module context, code generator) and still
compiles the protocol sources INTO liblogos_sdk.a from the flake input,
so the installed artifact (archive symbols, include/ + include/cpp
layouts, cmake config) stays byte-compatible: existing consumers need
no changes. Public headers are unchanged; logos_provider_object.h keeps
its name and now re-exports the abstract interface from
logos_provider_interface.h.

Transport/protocol component tests moved to logos-protocol with the
code; the remaining sdk/generator/experimental suites are unchanged
(432/432 green against the local protocol checkout).

* lock: add logos-protocol input

* Make the base SDK Qt-free: move the Qt developer layer to logos-qt-sdk

LogosAPI, LogosAPIProvider, LogosProviderBase/LOGOS_PROVIDER macros, the
QObject provider glue (QtProviderObject) and the legacy PluginInterface
(core/interface.h) move to the new logos-qt-sdk repo. The protocol
sources are no longer compiled into a monolithic archive — consumers
link logos-qt-sdk (which layers on logos-protocol) instead.

What remains here is header-only std C++: logos_module_context.h,
logos_result.h (StdLogosResult), logos_json.h — exported as the CMake
INTERFACE target logos-cpp-sdk::logos_headers — plus the code generator
(a build-time tool; its introspection mode now includes
logos_provider_interface.h from logos-protocol, where
LogosProviderPlugin moved).

Mechanically verified Qt-free: the logos-cpp-lib / logos-cpp-include
closures contain only nlohmann_json. Tests: 245/245 (module-context std
suite + generator + experimental).

* fix: accept the installed source-export layout in the protocol-root check

The fail-fast only tested <root>/cpp/logos_protocol.h, but the LP_SRC
selection right below (and the error message itself) support the
installed export layout <root>/include/cpp as well. Pointing
LOGOS_PROTOCOL_ROOT at an installed export tripped the FATAL_ERROR
before that fallback could apply.

Caught by Copilot review on #82.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* lock: pin logos-protocol to the qt-free-split branch head

The Qt-free SDK (and the cdylib backend stacked on it) reference
LogosProviderPlugin from protocol's logos_provider_interface.h, which
lands on feat/qt-free-split — the P1-branch pin no longer compiles
standalone. Temporary — drop when the chain PRs merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* doctest: pin the logoscore runtime via its {release} placeholder

The spec built logoscore-cli at bare master with only the cpp-sdk inputs
overridden — master's stack cannot compile against the qt-free SDK, so
the suite failed on the chain branches. With the placeholder, CI's
--release-for pins expand it to the workspace's logoscore commit (and
local runs without a pin still fall back to master, unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* doctest: override the nested module builders to {release} too

capability_module (via logoscore's lock) and the cloned accounts module
resolve module-builder from their own locks — pre-split revs whose
LogosModule.cmake still detects the SDK by logos_api.h, which the
qt-free SDK no longer ships ('logos-cpp-sdk not found'). Overriding the
builder itself to the workspace-pinned chain rev (keeping the nested
cpp-sdk override) builds both modules with the split-aware builder.
Verified end-to-end locally with the exact doctest command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* doctest: apply the {release} + nested-builder overrides to all three specs

The runtime spec got the treatment in 210eea1; the composition and
worker-thread specs have the same logoscore/module build commands and
failed identically (pre-split builders from the modules' own locks).
All executed run: blocks now pin logoscore-cli{release} and override
the nested module builders to logos-module-builder{release}; the
displayed code_block: variants stay in their generic master form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* codegen: typed wrappers throw on call failure; dispatch catches escapes

Generated sync client wrappers call the new err-out invokeRemoteMethod
overload and throw logos::LogosCallError when the call fails (e.g. the
bound module is missing) — previously the empty QVariant silently
degraded to the return type's default and a caller could not tell
failure from a legitimate 0 / "". Both generators (legacy + LIDL),
both API styles. Async paths unchanged.

Generated provider dispatch (universal qt glue + LOGOS_PROVIDER) wraps
the method body in a catch-all that logs and returns an invalid QVariant
— an escaped exception becomes an ordinary METHOD_FAILED instead of
unwinding through Qt event dispatch and killing the module process.

* codegen: CallError out-param instead of throwing wrappers

Per review, the generated sync wrappers expose the error channel as an
optional trailing parameter — add(a, b, &err) — rather than throwing:
explicit, stateless, works on temporaries, and existing call sites
compile unchanged (they keep default-on-failure, now with a qWarning so
failures are visible in the module log). The dispatch catch-all from the
previous commit stays: it contains author exceptions, it doesn't
introduce any.

* glue: fire onContextReady AFTER modules()/event wiring

The generated onInit set the context (which fires the impl's
onContextReady hook) before constructing the LogosModules aggregate and
wiring typed event emission — so an impl doing its documented one-time
setup there (typed dependency calls, event subscriptions) dereferenced
a null aggregate and crashed the module process (signal 11). Found by
the first module to subscribe to a dependency's typed event from
onContextReady. Context now goes last.

* ci: run workflows on stacked PRs + workflow_dispatch

Both workflows filtered pull_request to master-based PRs, so stacked PRs
(feat/qt-free-sdk -> feat/extract-logos-protocol, feat/cdylib-authoring
-> feat/qt-free-sdk) ran NO checks at all. Drop the base-branch filter
for pull_request and add workflow_dispatch for manual runs. Same fix as
logos-module-builder 232b8a2.

* lock: protocol at the typed-requestModule port (3de5398)

* ci: chain pins for the doc-tests (drop at merge)

In repo CI only cpp-sdk's {release} is the commit under test —
logoscore-cli and module-builder expanded to master, which doesn't link
against the chain SDK the specs override in ('Build the CLI with the SDK
override' failed on every run since the stacked-PR triggers were
enabled). Pin both to the extraction-chain heads; the workspace pipeline
is unaffected (it pins every repo itself).

* generator: distribute the LIDL frontend for external generators

First step of moving ALL Qt glue emission out of this repo into
logos-qt-sdk's logos-qt-generator (cpp-sdk's generator keeps only the
Qt-free outputs: std typed wrappers, logos_sdk umbrella, cdylib
impl-exports, LIDL derivation).

- Shared emit helpers (lidlToPascalCase, lidlTypeToQt, lidlTypeToStd,
  lidlIsStdConvertible) move to a new lidl_emit_common.{h,cpp} unit, used
  by both generators.
- The frontend set (AST, lexer, parser, serializer, validator,
  impl-header parser, emit-common) is installed under
  share/lidl-frontend/ — the qt generator compiles these sources in
  directly, so the two tools share one frontend without a binary ABI.

* lock: protocol#3 merged — pin advances to protocol master

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 19:56:19 -03:00
Dario Lipicar 01221559b7 add support for QVariantList and QVariantMap (#31) 2026-03-26 10:12:18 -03:00
Iuri Matias 39b0a9acce Add tests; CI (#27)
* add tests for cpp-generator and sdk

* add tests for new api, module_proxy and factories

* add CI tests

* add tests for a module definition
2026-03-24 10:14:17 -04:00