mirror of
https://github.com/zap-proto/go.git
synced 2026-10-30 15:05:00 +00:00
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.
217 lines
8.6 KiB
Plaintext
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
|