mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-27 15:51:10 +00:00
feat(sdk): make the by-name call path a supported API
The dynamic (by-name) invoke path already existed and was already ungated at every layer — lp_client_create / lp_invoke in the C ABI, logos::LpClient above it, and the Qt client above that. Nothing checked a host service; there was no gate to open. What was missing was the ERGONOMICS, which is what turned a supported capability into something callers reached around the umbrella to get. Three additive pieces, no gate touched: 1. LogosModuleContext::moduleName() — the module's own registry name, i.e. the origin it authenticates as. The typed wrappers bake their origin in at codegen time; a by-name call has to state one, and a wrong origin authenticates as nobody and fails far from the call site. Set through a NEW `_logosCoreSetModuleName_`, deliberately not a fourth parameter on `_logosCoreSetContext_`: every generated provider calls that signature, so widening it would break each one until regenerated, for a value the generator knows statically. Set before the context, so moduleName() is live inside onContextReady(). 2. LogosModules::dynamic(target) on the origin-bound umbrella — the untyped client, with the origin baked in exactly as the typed members' is, and cached per target because LpClient owns a connection. The typed members over metadata.json#dependencies stay the ordinary way to call another module; this is for the cases whose target is a runtime value (a proxy, a router). 3. LpClient::getMethods() over the already-exported lp_get_methods. Invoke without introspect is guessing — a caller that cannot ask what exists can only hardcode, and a wrong guess fails at runtime like a typo. Verified: #default builds, and both checks pass (tests, generator-cli). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1afcf46f4d
commit
1be71bbbeb
@@ -716,6 +716,9 @@ QString lidlMakeModuleImplExports(const ModuleDecl& module,
|
||||
// modules() was already wired by lidlEnsureModulesWired() above (before this
|
||||
// context-gated early return), so onContextReady can safely call
|
||||
// modules().<dep>... / subscribe to dependency events from the hook.
|
||||
// The module's own registry name, which the generator knows statically.
|
||||
// Set BEFORE the context so moduleName() is live inside onContextReady().
|
||||
s << " _logos_codegen_::maybeSetModuleName(lidlImpl(), \"" << module.name << "\");\n";
|
||||
s << " _logos_codegen_::maybeSetContext(lidlImpl(), path, id, persist);\n";
|
||||
s << "}\n\n";
|
||||
|
||||
|
||||
@@ -1649,6 +1649,31 @@ QString makeUmbrellaHeaderFromDeps(const QJsonArray& deps, const QStringList& in
|
||||
s << " std::map<std::string, std::unique_ptr<" << className << "::State>> m_"
|
||||
<< ifaceName << "_bound;\n";
|
||||
}
|
||||
|
||||
// Untyped, BY-NAME access to a module this umbrella does not wrap.
|
||||
//
|
||||
// The typed members above cover `metadata.json#dependencies`, which is
|
||||
// the right default and stays the ordinary way to call another module.
|
||||
// But the by-name path already exists at every layer beneath this one
|
||||
// (lp_client_create / lp_invoke, logos::LpClient), so a consumer that
|
||||
// genuinely needs it — a proxy, a router, anything whose target is a
|
||||
// runtime value — has been reaching around the umbrella to get it.
|
||||
// Exposing it here is what makes that a supported surface rather than
|
||||
// an accident.
|
||||
//
|
||||
// The origin is baked in, exactly as the typed members' is: an origin
|
||||
// is asserted, never borrowed, and a wrong one authenticates as nobody
|
||||
// and fails far from the call. Clients are cached per target, mirroring
|
||||
// the bind_ state map above, because LpClient owns a connection.
|
||||
//
|
||||
// Pair it with LpClient::getMethods() — invoke without introspect is
|
||||
// guessing.
|
||||
s << " logos::LpClient& dynamic(const std::string& target) {\n";
|
||||
s << " auto& _c = m_dynamic[target];\n";
|
||||
s << " if (!_c) _c = std::make_unique<logos::LpClient>(target, \"" << originName << "\");\n";
|
||||
s << " return *_c;\n";
|
||||
s << " }\n";
|
||||
s << " std::map<std::string, std::unique_ptr<logos::LpClient>> m_dynamic;\n";
|
||||
s << "};\n";
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -164,6 +164,20 @@ public:
|
||||
&LpClient::resultTrampoline, box);
|
||||
}
|
||||
|
||||
// The target's method list, as the JSON the host reports. Empty on
|
||||
// failure. Invoke-without-introspect is what makes a by-name call an
|
||||
// escape hatch rather than an API: a caller that cannot ask what exists
|
||||
// can only guess, and a wrong guess fails at runtime like a typo.
|
||||
nlohmann::json getMethods() {
|
||||
lp_client* c = ensure();
|
||||
if (!c) return nlohmann::json();
|
||||
char* out = lp_get_methods(c);
|
||||
if (!out) return nlohmann::json();
|
||||
auto parsed = nlohmann::json::parse(out, nullptr, /*allow_exceptions=*/false);
|
||||
lp_string_free(out);
|
||||
return parsed.is_discarded() ? nlohmann::json() : parsed;
|
||||
}
|
||||
|
||||
// Subscribe to `event`. The payload is delivered as a JSON array. The
|
||||
// returned handle owns the subscription — keep it alive (the generated
|
||||
// wrapper stores it) for as long as you want the callback to fire.
|
||||
|
||||
@@ -85,6 +85,14 @@ public:
|
||||
// resources bundled next to the plugin (icons, qml/, schema files…).
|
||||
const std::string& modulePath() const { return m_modulePath; }
|
||||
|
||||
// This module's own registry name — the name other modules address it by,
|
||||
// and the `origin` it authenticates as. Needed by any BY-NAME call: the
|
||||
// typed wrappers bake their origin in at codegen time, but a dynamic call
|
||||
// has to state it, and a wrong origin authenticates as nobody and fails far
|
||||
// from the call site. Empty outside a framework-provisioned context, like
|
||||
// the getters below.
|
||||
const std::string& moduleName() const { return m_moduleName; }
|
||||
|
||||
// Short ID the host assigns to this instance. Stable across restarts
|
||||
// for the same on-disk persistence directory; multiple side-by-side
|
||||
// instances of the same module get distinct IDs.
|
||||
@@ -152,6 +160,16 @@ public:
|
||||
onContextReady();
|
||||
}
|
||||
|
||||
// Framework-only — sets moduleName(). Separate from
|
||||
// `_logosCoreSetContext_` on purpose: that signature is called by every
|
||||
// generated provider, so widening it would break each one until
|
||||
// regenerated, for a value the generator knows statically anyway. Called
|
||||
// BEFORE the context setter, so moduleName() is already populated when
|
||||
// onContextReady() fires.
|
||||
void _logosCoreSetModuleName_(std::string moduleName) {
|
||||
m_moduleName = std::move(moduleName);
|
||||
}
|
||||
|
||||
// Framework-only — sets the typed `LogosModules` pointer that
|
||||
// `logos<T>()` dereferences. Untyped (void*) at this layer because
|
||||
// the SDK header is shared by every module; the codegen-generated
|
||||
@@ -194,6 +212,7 @@ protected:
|
||||
virtual void onContextReady() {}
|
||||
|
||||
private:
|
||||
std::string m_moduleName;
|
||||
std::string m_modulePath;
|
||||
std::string m_instanceId;
|
||||
std::string m_instancePersistencePath;
|
||||
@@ -230,6 +249,19 @@ private:
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace _logos_codegen_ {
|
||||
|
||||
template<class T>
|
||||
inline auto maybeSetModuleName(T& impl, std::string moduleName)
|
||||
-> std::enable_if_t<std::is_base_of_v<LogosModuleContext, T>>
|
||||
{
|
||||
static_cast<LogosModuleContext&>(impl)._logosCoreSetModuleName_(std::move(moduleName));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline auto maybeSetModuleName(T&, std::string)
|
||||
-> std::enable_if_t<!std::is_base_of_v<LogosModuleContext, T>>
|
||||
{
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline auto maybeSetContext(T& impl,
|
||||
std::string modulePath,
|
||||
|
||||
Reference in New Issue
Block a user