mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
`any` (LogosMap/LogosList) was the only way to express a heterogeneous shape, so
~104 slots across openmetrics, package_manager, package_downloader, storage and
logoscore-cli describe their payloads with an untyped map. LIDL has carried
TypeDecl/FieldDecl all along — chat_module's hand-written .lidl declares records
and notes they "document the shape and light up if the generators gain struct
support". This is the impl-header path finally producing them.
parser - a plain `struct Foo { T a; U b; };` ahead of the impl class becomes a
TypeDecl. Field types go through the same cppTypeToLidl, so a record
field is subject to the same 64-bit / bstr / composition rules.
gate - a Named type is supported when the module DECLARES it. An undeclared
name is still a build error naming the type: records are opt-in, not
a reopening of the opaque fallback.
emitter - one Codec<Foo> specialisation per record. Fields are addressed
through decltype, so no C++ type name is spelled (same trick as
JsonArg), and because the specialisation plugs into
logos::detail::Codec, a record nested in [T] or {tstr: T} needs
nothing further emitted — the existing recursion handles it.
A missing field decodes as null and the leaf codec rejects it with the field's
path, so the diagnostics compose too.
Verified end to end, not just as emitted text — a module declaring
`struct Status { uint64_t port; std::string name; std::vector<uint8_t> blob; }`
used as param, return and inside a vector, over the real transport:
param {"port":9099,"name":"ok","blob":{"_bytes":"gAE"}} -> "9099|ok|2:128:1:129"
return {"blob":{"_bytes":"gAE"},"name":"ok","port":9099}
[record] [{...},{"blob":{"_bytes":""},"name":"b","port":1}]
missing field {"code":"dispatch_failed",
"message":"expected string at arg0.name, got null"}
Note the bytes field: tagged inside the record, and inside a record inside an
array, with no record-specific bytes handling anywhere.
Tests: 175/175, including that an undeclared Named type is still rejected.
Next for records: emit the `type Foo {...}` decls into the published .lidl so
consumers generate typed wrappers, then the Rust and Qt sides. Until then a
record is a provider-side contract that consumers still see as an object.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>