Files
go/cmd/zapgen/testdata/echo_zap.hpp.golden
zeekay f47ac094fe A C++ backend for zapgen
zapgen had one backend, Go, and that single fact is why the Rust and C++
chains hand-write their wire: 2,562 lines of runtime and 3,611 lines of
accessors in C++ alone, none of which a schema could have produced.

-lang picks the emitter and nothing before it changes. parser.go, desugar.go
and schema.go stay the one front end; validate and structSize stay the one set
of rules; emit.go and emitcpp.go are two ways of printing one AST. Adding a
language is a case in backend() and an emitter beside those two, never a
second parser.

A struct becomes offsets, a class holding a zap::Object whose accessors load
from the borrowed buffer, an Input record and a New that writes one buffer. An
interface becomes the ordinal table, the channel, the typed client, the
handler contract and the dispatch — the same four pieces, the same ordinals,
the same envelope. Nothing about the wire is spelled in the emitter: offsets
come from the schema and every load and store goes through the runtime.

Two things had to be said out loud or the two languages would differ:

  The wire version. zap.NewBuilder writes version 1 and zap::Builder writes
  version 2, so the generated C++ names zap::kVersion1 rather than taking a
  default that means something else on the other side.

  The width of a fixed field. Go's [N]byte answers N zero bytes for a buffer
  too short to hold the field, because copy() into a zero array leaves it
  zero. The C++ span accessor answers a zero span of length N, so the two
  agree about every truncated message.

Classes are forward-declared and the accessors that return one are defined
after every class body, so schema declaration order carries no meaning.
2026-09-06 08:35:05 -07:00

217 lines
8.6 KiB
Plaintext

// 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 {
// 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