mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 01:31:10 +00:00
refactor: the LIDL codec exists once (#117)
* refactor: the LIDL codec exists once
The cdylib generator emitted its own copy of the codec — ~186 lines of
C++-emitting-C++ mirroring logos-protocol's logos_codec.h by hand. Every codec
fix had to be written twice or it silently only half-applied, which happened
twice in a row recently (routing scalars through the codec + signedness; then
accepting 3.0 while still rejecting 3.7).
It was worse than duplication. The two copies had DRIFTED — the emitted integer
decode gated on is_number() where the canonical one checked is_number_integer()
|| is_number_unsigned() — and logos_json.h's byte helpers were the same mangled
symbols with weak linkage and DIFFERENT bodies as logos_codec.h's, both reaching
one program (module TUs compiled one; liblogos_protocol.a carries TUs that
included the other). Which body won was down to link order.
logos_json.h goes back to its documented charter — "LogosMap/LogosList aliases
for impl classes", per its own CMakeLists — and loses 77 lines. jsonToBytes moves
beside its sibling jsonToStringVec in logos_lp_client.h, rebuilt on the canonical
isTaggedBytes/b64UrlDecode; it keeps its own narrow spelling because every lp
decoder is documented to yield the default-constructed value on a mismatch,
which neither bytesFromJson (throws) nor bytesFromJsonLenient (accepts more) does.
Emptying it rather than making it include logos_codec.h is deliberate: some
thirty alias-only include sites across the module repos get ZERO new includes,
and logos-cpp-sdkConfig's "only dependency is nlohmann_json" stays true.
With the clash gone the generic half is deletable. emitGeneratedCodec becomes
emitRecordCodecs: one logos::detail::Codec<::Rec, void> per declared record, and
nothing else. That residue is irreducible — a LIDL `type` is a per-contract
struct whose fields exist only in that module's header, and C++17 has no field
reflection. Nesting composes for free: Codec<std::vector<Blob>> and deeper come
from the shared half once Codec<::Blob> exists.
One asymmetry dies with it. The scalar bstr decode and the [bstr] element decode
were different functions with different strictness, so echoBytes("hi") succeeded
while echoBytesList(["hi"]) threw — inside one module, for the same type. They
are one function now.
Build wiring: ONE line, in this repo's own test CMake, using a variable
nix/tests.nix already supplies. Nothing in logos-module-builder, logos-qt-sdk, or
any module repo.
verified: cpp-sdk + protocol suites green; test_fullapi_cpp, test_fullapi_ext_cpp
and test_basic_module_cpp build; test-modules 176/176. Conformance delta is
exactly one cell, baselined first in logos-test-modules#31.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: bump logos-protocol to the path-threaded bstr decoder
logos-protocol 4359557 (#33). Required by this branch, not incidental: deleting
the emitted codec swaps its path-carrying bstr decode for the canonical one, and
without #33 the canonical one reported "at value" instead of "[0].payload" —
losing the diagnostic exactly where a malformed bstr is hardest to find.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c364133066
commit
461cfed52d
+22
-83
@@ -1,91 +1,30 @@
|
||||
#pragma once
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Semantic aliases for nlohmann::json used in universal module impl classes.
|
||||
// The code generator recognizes these names and emits QVariantMap / QVariantList
|
||||
// conversions in the Qt glue layer, so impl classes remain Qt-free.
|
||||
//
|
||||
// ALIASES ONLY, on purpose. This header also used to define b64UrlEncode,
|
||||
// b64UrlDecode, bytesToJson and jsonToBytes — second copies of functions
|
||||
// logos-protocol's logos_codec.h already owned. That was not merely duplication:
|
||||
// the two sets had the same mangled names with weak linkage and DIFFERENT bodies,
|
||||
// and both reached one program, because module TUs compiled these while
|
||||
// liblogos_protocol.a carries TUs that included logos_codec.h. Which body won was
|
||||
// down to link order.
|
||||
//
|
||||
// It also made logos_codec.h unincludable from any TU that wanted LogosMap — a
|
||||
// redefinition error, since `inline` allows one definition per translation unit,
|
||||
// not two. That is why the cdylib generator had to emit its own copy of the
|
||||
// entire codec, and why every codec fix had to be written twice.
|
||||
//
|
||||
// The byte helpers now live where they belong: the canonical ones in
|
||||
// logos-protocol's logos_codec.h, and the lenient `lp`-path jsonToBytes beside
|
||||
// its sibling jsonToStringVec in logos_lp_client.h.
|
||||
//
|
||||
// Keeping this header dependency-free (nlohmann only) is deliberate. Some thirty
|
||||
// alias-only include sites across the module repos would otherwise inherit an
|
||||
// include path they have no use for, and logos-cpp-sdkConfig.cmake's "its only
|
||||
// dependency is nlohmann_json" would stop being true.
|
||||
using LogosMap = nlohmann::json;
|
||||
using LogosList = nlohmann::json;
|
||||
|
||||
namespace logos {
|
||||
|
||||
// The canonical tagged form for binary payloads on the wire:
|
||||
//
|
||||
// {"_bytes": "<base64url, unpadded>"}
|
||||
//
|
||||
// It is what logos-protocol emits and expects (logos_json_convert.cpp,
|
||||
// implementations/plain/json_mapping.cpp), and it is lossless for arbitrary
|
||||
// bytes — including embedded NULs, which a plain JSON string would not survive.
|
||||
// The Qt side reaches this form through QByteArray::toBase64/fromBase64 with
|
||||
// Base64UrlEncoding | OmitTrailingEquals; these are the Qt-free equivalents,
|
||||
// used by the generated `lp` wrappers and by universal (Qt-free) module code.
|
||||
|
||||
inline std::string b64UrlEncode(const std::vector<uint8_t>& bytes)
|
||||
{
|
||||
static const char* alpha =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
std::string out;
|
||||
size_t i = 0;
|
||||
while (i + 3 <= bytes.size()) {
|
||||
uint32_t n = (uint32_t(bytes[i]) << 16) | (uint32_t(bytes[i + 1]) << 8)
|
||||
| uint32_t(bytes[i + 2]);
|
||||
out += alpha[(n >> 18) & 0x3f]; out += alpha[(n >> 12) & 0x3f];
|
||||
out += alpha[(n >> 6) & 0x3f]; out += alpha[n & 0x3f];
|
||||
i += 3;
|
||||
}
|
||||
if (i < bytes.size()) {
|
||||
uint32_t n = uint32_t(bytes[i]) << 16;
|
||||
if (i + 1 < bytes.size()) n |= uint32_t(bytes[i + 1]) << 8;
|
||||
out += alpha[(n >> 18) & 0x3f]; out += alpha[(n >> 12) & 0x3f];
|
||||
if (i + 1 < bytes.size()) out += alpha[(n >> 6) & 0x3f];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::vector<uint8_t> b64UrlDecode(const std::string& in)
|
||||
{
|
||||
auto idx = [](char ch) -> int {
|
||||
if (ch >= 'A' && ch <= 'Z') return ch - 'A';
|
||||
if (ch >= 'a' && ch <= 'z') return ch - 'a' + 26;
|
||||
if (ch >= '0' && ch <= '9') return ch - '0' + 52;
|
||||
if (ch == '-') return 62;
|
||||
if (ch == '_') return 63;
|
||||
return -1; // skips '=' padding and any stray character
|
||||
};
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t buf = 0;
|
||||
int bits = 0;
|
||||
for (char ch : in) {
|
||||
const int v = idx(ch);
|
||||
if (v < 0) continue;
|
||||
buf = (buf << 6) | static_cast<uint32_t>(v);
|
||||
bits += 6;
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out.push_back(static_cast<uint8_t>((buf >> bits) & 0xff));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Bytes -> the tagged JSON object.
|
||||
inline nlohmann::json bytesToJson(const std::vector<uint8_t>& bytes)
|
||||
{
|
||||
return nlohmann::json{{"_bytes", b64UrlEncode(bytes)}};
|
||||
}
|
||||
|
||||
// The tagged JSON object -> bytes. Lenient, like the rest of the `lp` decode
|
||||
// path: anything that is not a well-formed tagged-bytes object yields empty.
|
||||
inline std::vector<uint8_t> jsonToBytes(const nlohmann::json& j)
|
||||
{
|
||||
if (!j.is_object() || !j.contains("_bytes") || !j["_bytes"].is_string())
|
||||
return {};
|
||||
return b64UrlDecode(j["_bytes"].get<std::string>());
|
||||
}
|
||||
|
||||
} // namespace logos
|
||||
|
||||
+13
-3
@@ -26,7 +26,8 @@
|
||||
|
||||
#include "logos_protocol.h" // lp_* C ABI
|
||||
#include "logos_call_error.h" // logos::CallError
|
||||
#include "logos_json.h" // logos::bytesToJson / logos::jsonToBytes
|
||||
#include "logos_json.h" // LogosMap / LogosList aliases
|
||||
#include "logos_codec.h" // logos::bytesToJson, b64UrlDecode, isTaggedBytes
|
||||
#include "logos_result.h" // StdLogosResult
|
||||
|
||||
namespace logos {
|
||||
@@ -43,8 +44,17 @@ inline std::vector<std::string> jsonToStringVec(const nlohmann::json& j) {
|
||||
}
|
||||
|
||||
// Binary payloads travel in the canonical tagged form
|
||||
// {"_bytes": "<base64url, unpadded>"}; logos::bytesToJson / logos::jsonToBytes
|
||||
// live in logos_json.h and are used by the generated wrappers on both sides.
|
||||
// {"_bytes": "<base64url, unpadded>"}. Encoding is logos::bytesToJson from
|
||||
// logos_codec.h — the one canonical definition. Decoding on this path is NOT
|
||||
// the codec's: bytesFromJson throws and bytesFromJsonLenient also accepts a
|
||||
// plain string, a number and an int array, whereas every `lp` decoder here is
|
||||
// documented to yield the default-constructed value on a mismatch. So the lp
|
||||
// decode keeps its own deliberately-narrow spelling, next to jsonToStringVec
|
||||
// which has exactly the same contract.
|
||||
inline std::vector<uint8_t> jsonToBytes(const nlohmann::json& j) {
|
||||
if (!isTaggedBytes(j)) return {};
|
||||
return b64UrlDecode(j["_bytes"].get<std::string>());
|
||||
}
|
||||
|
||||
inline StdLogosResult jsonToStdResult(const nlohmann::json& j) {
|
||||
StdLogosResult r;
|
||||
|
||||
Reference in New Issue
Block a user