Task: Build wire_codec abstraction and startup config in ores.nats
Table of Contents
This page documents a task in the Make NATS wire format configurable: JSON/MessagePack, decided once at startup story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Build the shared foundation the rest of the story depends on:
- A
wire_formatenum (json,msgpack) and a.env-driven config value (e.g.ORES_NATS_WIRE_FORMAT, defaultjsonfor backward compatibility), parsed once at process startup alongside existing NATS connection config. - A
wire_codecclass (inores.nats, alongside the existingcompressionmodule it mirrors architecturally): constructed once with the resolvedwire_format, exposing templateencode<T>(const T&) -> std::vector<std::byte>anddecode<T>(std::span<const std::byte>) -> rfl::Result<T>methods that dispatch internally torfl::jsonorrfl::msgpackbased on the format fixed at construction. Non-polymorphic (no virtual dispatch for a decision made once and never changing at runtime); trivially copyable/cheap to hold by value or reference wherever needed. - Round-trip unit tests for both formats, plus a test confirming
std::vector<uint8_t>fields round-trip correctly undermsgpack(this is the property the whole story exists to exploit).
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Make NATS wire format configurable: JSON/MessagePack, decided once at startup |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-29 |
Acceptance
[X]wire_codeccompiles and round-trips a representative reflectable struct (including a byte-vector field) under bothjsonandmsgpack.[X]Config value is read once at startup from.env, with a documented default that preserves today's behaviour if unset.[X]No per-call branching cost beyond the single format check already implied by having two supported formats – no header parsing, no per-message format inspection.
Plan
Built the shared foundation exactly as scoped in * Goal, mirroring
ores.nats::compression's existing module layout:
domain/wire_format.hpp=/.cpp=: thewire_formatenum (json,msgpack) plusparse_wire_format=/=to_stringfree functions for the =.env=/CLI string spelling, case-insensitive on parse.domain/wire_codec.hpp: the non-polymorphicwire_codecvalue type (header-only –encode=/=decodeare templates, so there is no non-template code to put in a.cpp).encode<T>dispatches torfl::json::write=/=rfl::msgpack::write;decode<T>dispatches torfl::json::read=/=rfl::msgpack::read, both taking the fixedformat_as a single branch, not a per-message inspection.rfl::msgpack::read<T>accepts astd::span<const std::byte>directly (it satisfies reflect-cpp'sContiguousByteContainerconcept forstd::byte);rfl::json::read<T>needs astd::string_view, built via areinterpret_castover the same span.config/nats_options.hpp: added awire_formatfield, defaulting towire_format::json(preserves pre-existing behaviour for any process that doesn't set it).config/nats_configuration.cpp: added the--nats-wire-formatoption (default\"json\", envORES_NATS_WIRE_FORMATvia the per-serviceenvironment_mapper_factoryprefix, same convention as the existing TLS options), parsed viaparse_wire_formatwith a thrownstd::invalid_argumenton an unrecognised value – fails fast at startup rather than silently falling back.src/CMakeLists.txt: addedreflectcpp::reflectcppas a public link dependency (previouslyores.nats.libonly pulled it in transitively and privately throughores.utility.lib), sincewire_codec.hppis a public header that every consumer including it needsrfl/json.hpp=/=rfl/msgpack.hppfor.
Round-trip unit tests (new tests/domain_wire_codec_tests.cpp,
tests/domain_wire_format_tests.cpp, plus additions to
tests/config_nats_configuration_tests.cpp) cover: json round-trip,
msgpack round-trip, a msgpack-vs-json size comparison confirming the
byte-vector field is carried as native binary (smaller than its json/
base64-equivalent, not larger), format() accessor, a malformed-input
decode error case, parse_wire_format=/=to_string spelling and
case-insensitivity, and the new CLI option's default/override/
rejection paths. Full ores.nats.tests suite: 47 assertions, 24 test
cases, all green – no regressions.
Notes
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1745 | [ores.nats] Build wire_codec abstraction and startup config |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Symmetric malformed-msgpack decode test missing (only json path covered) | domain_wire_codec_tests.cpp | Fixed | Added "wire_codec decode surfaces a parse error for malformed msgpack input" |
| 2 | nats_options::wire_format member shadows the ores::nats::wire_format enum type name |
nats_options.hpp | Fixed | Renamed field to format (call sites: nats_configuration.cpp, tests) |
| 3 | Error message built via + concatenation instead of std::format, inconsistent with ores.logging's equivalent |
nats_configuration.cpp | Fixed | Switched to std::format |
Only the CLI flag path is tested for wire format, not the ORES_NATS_WIRE_FORMAT env var directly |
nats_configuration.cpp | Declined | Consistent with how the pre-existing TLS options are tested (CLI only); not a regression this task introduces |
Result
Shared foundation complete: ores::nats::wire_format (json=/
=msgpack, with parse_wire_format=/=to_string), ores::nats::wire_codec
(non-polymorphic, template encode=/=decode), a new
--nats-wire-format=/=ORES_NATS_WIRE_FORMAT config value (default
json) on nats_options=/=nats_configuration, and round-trip unit
tests for both formats including the byte-vector-as-native-binary
property. ores.nats.lib now links reflectcpp::reflectcpp publicly
so downstream consumers of wire_codec.hpp get the reflect-cpp
headers transitively. This is the shared foundation the remaining
implementation tasks (service handler_helpers, ClientManager,
ores.shell request helpers, end-to-end verification) build on.