Files
zeekay 6439541c91 Prove the two backends agree, on bytes neither of them wrote
Generating C++ is a claim. This runs it.

Both probes build the same cases from the same inputs — every type the dialect
has, plus the request and response envelopes the generated client and dispatch
produce — and print the hex; the two outputs are the same file. Then each
probe reads both probes' bytes with its own generated accessors, four
crossings, all agreeing. Then both read a corpus of real P-chain transactions
produced by the Go chain implementation, 134 vectors of which 44 are truncated
or malformed on purpose, and answer the same fields in the same places.

Neither probe decides anything: every value printed comes from a generated
accessor or a generated builder, so a disagreement between the probes is a
disagreement between the backends and nowhere else.

It earned its keep immediately. Five corpus vectors separated a Go [N]byte
accessor answering zeros from a C++ span answering empty, and the round trip
found the zero Object panicking in the Go runtime where C++ answered zero.
Both are fixed; neither was visible by reading.

list<T> carries a pointer and a count and nothing about elements — the dialect
cannot state a stride — so both languages agree on the count, which is all
either can say. That gap is in the dialect, in both backends at once.
2026-09-06 08:35:14 -07:00

306 lines
11 KiB
C++

// Code generated by zapgen; DO NOT EDIT.
// source: echo.zap
// SPDX-License-Identifier: BSD-3-Clause-Eco
#pragma once
#include <cstdint>
#include <expected>
#include <span>
#include <string>
#include <vector>
#include <zap/zap.hpp>
#include <zap/rpc.hpp>
namespace echo {
class Ping;
class Pong;
// Field byte offsets for Ping, and the size of its fixed section.
inline constexpr std::int64_t kPingSeqOff = 0;
inline constexpr std::int64_t kPingSize = 8;
// Ping is a zero-copy view into a ZAP-encoded Ping message. It borrows the
// bytes it was built over, which must outlive it, and copies no field.
class Ping {
public:
Ping() = default;
explicit Ping(zap::Object o) : o_(o) {}
bool is_null() const { return o_.is_null(); }
zap::Object object() const { return o_; }
std::uint64_t Seq() const { return o_.u64(kPingSeqOff); }
private:
zap::Object o_;
};
// WrapPing parses b and returns a typed view over it, or says why b is not a
// message. The view borrows b.
inline std::expected<Ping, zap::Error> WrapPing(std::span<const std::uint8_t> b) {
auto m = zap::Message::parse(b);
if (!m) return std::unexpected(m.error());
return Ping(m->root());
}
// PingInput collects the field values for NewPing. A borrowed field is a view:
// it must outlive the call, not the buffer the call returns.
struct PingInput {
std::uint64_t Seq{};
};
// NewPing writes a ZAP-encoded Ping message into a fresh buffer and returns it.
inline std::vector<std::uint8_t> NewPing(const PingInput& in) {
zap::Builder b(256, zap::kVersion1);
auto ob = b.start_object(kPingSize);
ob.set_u64(kPingSeqOff, in.Seq);
ob.finish_as_root();
return b.finish();
}
// Field byte offsets for Pong, and the size of its fixed section.
inline constexpr std::int64_t kPongSeqOff = 0;
inline constexpr std::int64_t kPongSize = 8;
// Pong is a zero-copy view into a ZAP-encoded Pong message. It borrows the
// bytes it was built over, which must outlive it, and copies no field.
class Pong {
public:
Pong() = default;
explicit Pong(zap::Object o) : o_(o) {}
bool is_null() const { return o_.is_null(); }
zap::Object object() const { return o_; }
std::uint64_t Seq() const { return o_.u64(kPongSeqOff); }
private:
zap::Object o_;
};
// WrapPong parses b and returns a typed view over it, or says why b is not a
// message. The view borrows b.
inline std::expected<Pong, zap::Error> WrapPong(std::span<const std::uint8_t> b) {
auto m = zap::Message::parse(b);
if (!m) return std::unexpected(m.error());
return Pong(m->root());
}
// PongInput collects the field values for NewPong. A borrowed field is a view:
// it must outlive the call, not the buffer the call returns.
struct PongInput {
std::uint64_t Seq{};
};
// NewPong writes a ZAP-encoded Pong message into a fresh buffer and returns it.
inline std::vector<std::uint8_t> NewPong(const PongInput& in) {
zap::Builder b(256, zap::kVersion1);
auto ob = b.start_object(kPongSize);
ob.set_u64(kPongSeqOff, in.Seq);
ob.finish_as_root();
return b.finish();
}
// Method ordinals for the Echo service (stable 1-based wire ids).
inline constexpr std::uint32_t kEchoPingOrdinal = 1;
inline constexpr std::uint32_t kEchoNotifyOrdinal = 2;
inline constexpr std::uint32_t kEchoHealthOrdinal = 3;
inline constexpr std::uint32_t kEchoShutdownOrdinal = 4;
// EchoChannel ships one Call envelope and awaits its correlated Response.
class EchoChannel {
public:
virtual ~EchoChannel() = default;
virtual std::expected<zap::rpc::Response, std::string> Call(
std::span<const std::uint8_t> envelope) = 0;
};
// EchoAnswer is one call's result: the call's own promise, so a later call can
// pipeline off it, and the response body.
struct EchoAnswer {
zap::rpc::Promise promise{};
std::vector<std::uint8_t> body{};
};
// EchoClient is a typed client for the Echo service over a ZAP call channel.
// Each call takes a fresh promise id from its session; the "On" form of a
// method targets a prior call's promise, so the server chains them and the
// dependent call ships without waiting for the first to round-trip.
class EchoClient {
public:
EchoClient(EchoChannel* ch, std::span<const std::uint8_t> capability)
: ch_(ch), cap_(capability.begin(), capability.end()) {}
std::expected<EchoAnswer, std::string> Ping(std::span<const std::uint8_t> req) {
return invokePing(zap::rpc::kNoTarget, req);
}
// PingOn issues Ping as a dependent call pipelined on the answer of on: the
// server substitutes on's resolved result for this call's payload before
// dispatch, so it carries none of its own.
std::expected<EchoAnswer, std::string> PingOn(zap::rpc::Promise on) {
return invokePing(on.id, {});
}
std::expected<zap::rpc::Promise, std::string> Notify(std::span<const std::uint8_t> req) {
return invokeNotify(zap::rpc::kNoTarget, req);
}
// NotifyOn issues Notify as a dependent call pipelined on the answer of on: the
// server substitutes on's resolved result for this call's payload before
// dispatch, so it carries none of its own.
std::expected<zap::rpc::Promise, std::string> NotifyOn(zap::rpc::Promise on) {
return invokeNotify(on.id, {});
}
std::expected<EchoAnswer, std::string> Health() {
return invokeHealth(zap::rpc::kNoTarget, {});
}
// HealthOn issues Health as a dependent call pipelined on the answer of on: the
// server substitutes on's resolved result for this call's payload before
// dispatch, so it carries none of its own.
std::expected<EchoAnswer, std::string> HealthOn(zap::rpc::Promise on) {
return invokeHealth(on.id, {});
}
std::expected<zap::rpc::Promise, std::string> Shutdown() {
return invokeShutdown(zap::rpc::kNoTarget, {});
}
// ShutdownOn issues Shutdown as a dependent call pipelined on the answer of on: the
// server substitutes on's resolved result for this call's payload before
// dispatch, so it carries none of its own.
std::expected<zap::rpc::Promise, std::string> ShutdownOn(zap::rpc::Promise on) {
return invokeShutdown(on.id, {});
}
private:
std::expected<EchoAnswer, std::string> invokePing(std::uint32_t target, std::span<const std::uint8_t> payload) {
const zap::rpc::Promise p = sess_.next();
zap::rpc::Call c;
c.method = kEchoPingOrdinal;
c.promise_id = p.id;
c.target = target;
c.cap = cap_;
c.payload = payload;
auto resp = ch_->Call(zap::rpc::build_request(c));
if (!resp) return std::unexpected(resp.error());
if (resp->status != zap::rpc::kStatusOK) {
return std::unexpected(std::string("Echo.Ping: status ") +
std::to_string(resp->status));
}
return EchoAnswer{p, std::vector<std::uint8_t>(resp->body.begin(), resp->body.end())};
}
std::expected<zap::rpc::Promise, std::string> invokeNotify(std::uint32_t target, std::span<const std::uint8_t> payload) {
const zap::rpc::Promise p = sess_.next();
zap::rpc::Call c;
c.method = kEchoNotifyOrdinal;
c.promise_id = p.id;
c.target = target;
c.cap = cap_;
c.payload = payload;
auto resp = ch_->Call(zap::rpc::build_request(c));
if (!resp) return std::unexpected(resp.error());
if (resp->status != zap::rpc::kStatusOK) {
return std::unexpected(std::string("Echo.Notify: status ") +
std::to_string(resp->status));
}
return p;
}
std::expected<EchoAnswer, std::string> invokeHealth(std::uint32_t target, std::span<const std::uint8_t> payload) {
const zap::rpc::Promise p = sess_.next();
zap::rpc::Call c;
c.method = kEchoHealthOrdinal;
c.promise_id = p.id;
c.target = target;
c.cap = cap_;
c.payload = payload;
auto resp = ch_->Call(zap::rpc::build_request(c));
if (!resp) return std::unexpected(resp.error());
if (resp->status != zap::rpc::kStatusOK) {
return std::unexpected(std::string("Echo.Health: status ") +
std::to_string(resp->status));
}
return EchoAnswer{p, std::vector<std::uint8_t>(resp->body.begin(), resp->body.end())};
}
std::expected<zap::rpc::Promise, std::string> invokeShutdown(std::uint32_t target, std::span<const std::uint8_t> payload) {
const zap::rpc::Promise p = sess_.next();
zap::rpc::Call c;
c.method = kEchoShutdownOrdinal;
c.promise_id = p.id;
c.target = target;
c.cap = cap_;
c.payload = payload;
auto resp = ch_->Call(zap::rpc::build_request(c));
if (!resp) return std::unexpected(resp.error());
if (resp->status != zap::rpc::kStatusOK) {
return std::unexpected(std::string("Echo.Shutdown: status ") +
std::to_string(resp->status));
}
return p;
}
EchoChannel* ch_;
std::vector<std::uint8_t> cap_;
zap::rpc::Session sess_;
};
// EchoHandler is the server contract for the Echo service. Implement each
// method, then route envelopes to it with DispatchEcho.
class EchoHandler {
public:
virtual ~EchoHandler() = default;
virtual std::expected<std::vector<std::uint8_t>, std::string> Ping(std::span<const std::uint8_t> req) = 0;
virtual std::expected<void, std::string> Notify(std::span<const std::uint8_t> req) = 0;
virtual std::expected<std::vector<std::uint8_t>, std::string> Health() = 0;
virtual std::expected<void, std::string> Shutdown() = 0;
};
// DispatchEcho decodes a Call envelope, routes it by method ordinal to h and
// returns the response envelope. An unknown ordinal answers StatusNotFound;
// a handler that fails answers StatusInternal.
inline std::expected<std::vector<std::uint8_t>, std::string> DispatchEcho(
EchoHandler& h, std::span<const std::uint8_t> envelope) {
auto call = zap::rpc::parse_request(envelope);
if (!call) return std::unexpected(std::string(zap::describe(call.error())));
switch (call->method) {
case kEchoPingOrdinal: {
auto body = h.Ping(call->payload);
if (!body) {
return zap::rpc::build_response(zap::rpc::kStatusInternal, call->promise_id, {});
}
return zap::rpc::build_response(zap::rpc::kStatusOK, call->promise_id, *body);
}
case kEchoNotifyOrdinal: {
if (!h.Notify(call->payload)) {
return zap::rpc::build_response(zap::rpc::kStatusInternal, call->promise_id, {});
}
return zap::rpc::build_response(zap::rpc::kStatusOK, call->promise_id, {});
}
case kEchoHealthOrdinal: {
auto body = h.Health();
if (!body) {
return zap::rpc::build_response(zap::rpc::kStatusInternal, call->promise_id, {});
}
return zap::rpc::build_response(zap::rpc::kStatusOK, call->promise_id, *body);
}
case kEchoShutdownOrdinal: {
if (!h.Shutdown()) {
return zap::rpc::build_response(zap::rpc::kStatusInternal, call->promise_id, {});
}
return zap::rpc::build_response(zap::rpc::kStatusOK, call->promise_id, {});
}
default:
return zap::rpc::build_response(zap::rpc::kStatusNotFound, call->promise_id, {});
}
}
} // namespace echo