mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
f3369faca4e06ac9f89895b11fdcc202f86f00c2
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f3369faca4 |
feat(generator): async callers can see the error, sync callers can set a deadline (#132)
The two consumer surfaces had complementary holes:
sync : T foo(params…, logos::CallError* err = nullptr) error yes, timeout NO
async: void fooAsync(params…, cb, Timeout = Timeout()) timeout yes, error NO
so an async caller could not tell a failed remote call from a provider that
legitimately returned 0 / "" / false — the exact ambiguity the sync path's
CallError* was added to resolve — and a sync caller could not say how long it
was willing to wait, even though the transport overload the generator already
calls takes both.
Both fixes are additive:
T foo(params…, logos::CallError* err = nullptr, Timeout timeout = Timeout());
void fooAsync(params…, std::function<void(T)> cb, Timeout timeout = Timeout()); // unchanged
void fooAsyncResult(params…, std::function<void(logos::AsyncResult<T>)> cb,
Timeout timeout = Timeout()); // new
logos::AsyncResult<T> (new, Qt-free, cpp/logos_async_result.h) is {value, error}
plus ok(); AsyncResult<void> carries only the error so every fooAsyncResult has
the same callback shape. The name is distinct rather than an overload because
std::function<void(AsyncResult<T>)> next to std::function<void(T)> is ambiguous
for a generic lambda.
Applied to both emitters that produce this surface — legacy/generator_lib.cpp
(the module-builder path) and experimental/lidl_gen_client.cpp (`--lidl
--module-only`, from a published contract) — since a consumer can reach either
for the same contract.
The Qt-free (ApiStyle::Lp) surface gets the sync timeout (spelled `int
timeout_ms`; `Timeout` lives behind a Qt header) but NOT fooAsyncResult:
logos-protocol's lp_invoke_async hard-codes `cb(1, …)`, so an AsyncResult there
would report ok() on a failed call. Measured, not assumed. See the note in
makeHeaderLp.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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> |
||
|
|
01221559b7 | add support for QVariantList and QVariantMap (#31) | ||
|
|
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 |