Skip to content

Commit 39e15e1

Browse files
rustyconoverclaude
andcommitted
cpp_records: method schema table and FunctionType wire aliases
AllMethodSchemas() lists every VgiProtocol method's params / result schema factories so the extension's schema registry is generated rather than hand-maintained. ENUM_WIRE_ALIASES lets FunctionType decode the lower-case spellings vgi-go, vgi-rust and vgi-java send (plus legacy table_in_out), which the hand-written C++ parser always accepted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 40918e0 commit 39e15e1

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

‎vgi/codegen/cpp_records.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@
9191
}
9292

9393

94+
#: Extra wire spellings the C++ client accepts for an enum (C++ enum name -> {spelling: member
95+
#: NAME}). Only spellings peers really send, which the hand-written client always accepted:
96+
#: vgi-go sends ``function_type`` in lower case, and ``table_in_out`` is the legacy name of
97+
#: TABLE. Encoding always writes the canonical member name.
98+
ENUM_WIRE_ALIASES: dict[str, dict[str, str]] = {
99+
"FunctionType": {
100+
"scalar": "SCALAR",
101+
"table": "TABLE",
102+
"table_in_out": "TABLE",
103+
"table_buffering": "TABLE_BUFFERING",
104+
"aggregate": "AGGREGATE",
105+
},
106+
}
107+
108+
94109
def cpp_name(py_name: str) -> str:
95110
"""The C++ type name for a protocol type's Python name."""
96111
return CPP_NAMES.get(py_name) or cs.csharp_name(py_name)
@@ -411,6 +426,20 @@ def _render_header(model: _Model, namespace: list[str]) -> str:
411426
f" const std::string &worker_path);\n\n"
412427
)
413428

429+
out.write("// " + "-" * 76 + "\n// Methods.\n// " + "-" * 76 + "\n\n")
430+
out.write("//! One VgiProtocol method's wire schemas (vgi_protocol_schemas.hpp factories).\n")
431+
out.write("struct MethodSchemas {\n")
432+
out.write("\tconst char *method;\n")
433+
out.write("\t//! The outer request batch (`<Method>ParamsSchema`).\n")
434+
out.write("\tstd::shared_ptr<arrow::Schema> params;\n")
435+
out.write("\t//! The record a unary result carries (`<Method>ResultSchema`); null when the method\n")
436+
out.write("\t//! returns nothing, raw bytes, or is a stream.\n")
437+
out.write("\tstd::shared_ptr<arrow::Schema> result;\n")
438+
out.write("\t//! False for a unary method that returns None.\n")
439+
out.write("\tbool returns_value;\n")
440+
out.write("};\n")
441+
out.write("//! Every VgiProtocol method, by name order.\n")
442+
out.write("const std::vector<MethodSchemas> &AllMethodSchemas();\n\n")
414443
out.write("// Test hooks (test/cpp/test_protocol_records.cpp).\n")
415444
out.write("//! Every record's name with a one-row batch of a default-constructed instance.\n")
416445
out.write(
@@ -473,6 +502,16 @@ def _render_source(model: _Model, namespace: list[str]) -> str:
473502
out.write(f"std::optional<{e.name}> {e.name}FromWire(const std::string &wire) {{\n")
474503
out.write(f"\tfor (int16_t i = 0; i < k{e.name}Count; i++) {{\n")
475504
out.write(f"\t\tif (wire == k{e.name}Wire[i]) {{\n\t\t\treturn static_cast<{e.name}>(i);\n\t\t}}\n\t}}\n")
505+
aliases = ENUM_WIRE_ALIASES.get(e.name, {})
506+
if aliases:
507+
member_ident = {m.wire_name: m.ident for m in e.members}
508+
out.write(
509+
"\t// Accepted spellings beyond the canonical names (vgi.codegen.cpp_records.ENUM_WIRE_ALIASES).\n"
510+
)
511+
for alias, member in sorted(aliases.items()):
512+
if member not in member_ident:
513+
raise GeneratorError(f"{e.name}: alias {alias!r} names unknown member {member!r}")
514+
out.write(f'\tif (wire == "{alias}") {{\n\t\treturn {e.name}::{member_ident[member]};\n\t}}\n')
476515
out.write("\treturn std::nullopt;\n}\n\n")
477516
out.write(f"void ReadValue(const arrow::Array &array, int64_t i, {e.name} &out, const wire::Ctx &ctx) {{\n")
478517
out.write("\tstd::string s;\n\twire::ReadValue(array, i, s, ctx);\n")
@@ -539,6 +578,13 @@ def _render_source(model: _Model, namespace: list[str]) -> str:
539578
)
540579
out.write(f'\twire::RowReader r(batch, row, "{r.name}", worker_path);\n\tRead{r.name}(r, out);\n}}\n\n')
541580

581+
out.write("const std::vector<MethodSchemas> &AllMethodSchemas() {\n")
582+
out.write("\tstatic const std::vector<MethodSchemas> methods = {\n")
583+
for method, params, result, returns in _method_rows():
584+
result_expr = f"{result}()" if result else "nullptr"
585+
out.write(f'\t {{"{method}", {params}(), {result_expr}, {"true" if returns else "false"}}},\n')
586+
out.write("\t};\n\treturn methods;\n}\n\n")
587+
542588
factories = _factory_names()
543589
out.write(
544590
"std::vector<std::pair<std::string, std::shared_ptr<arrow::RecordBatch>>> DefaultRecordBatchesForTesting() {\n"
@@ -556,6 +602,29 @@ def _render_source(model: _Model, namespace: list[str]) -> str:
556602
return out.getvalue()
557603

558604

605+
def _method_rows() -> list[tuple[str, str, str | None, bool]]:
606+
"""(method, params factory, result factory or None, returns a value) for every VgiProtocol method."""
607+
from vgi_rpc.rpc._types import MethodType, rpc_methods # type: ignore[attr-defined]
608+
609+
from vgi.codegen._common import _resolve_inner_schema, sanitize_name
610+
from vgi.protocol import VgiProtocol
611+
612+
rows: list[tuple[str, str, str | None, bool]] = []
613+
methods = rpc_methods(VgiProtocol)
614+
for name in sorted(methods):
615+
info = methods[name]
616+
stem = sanitize_name(name)
617+
result = None
618+
if (
619+
info.method_type == MethodType.UNARY
620+
and info.has_return
621+
and _resolve_inner_schema(info.result_type, name) is not None
622+
):
623+
result = f"{stem}ResultSchema"
624+
rows.append((name, f"{stem}ParamsSchema", result, bool(info.has_return)))
625+
return rows
626+
627+
559628
def _factory_names() -> set[str]:
560629
"""Stems of the schema factories vgi_protocol_schemas.hpp emits (see cpp_schemas.emit)."""
561630
from vgi.codegen._common import EXTRA_RESPONSE_TYPES, REQUEST_TYPES, collect_schemas

0 commit comments

Comments
 (0)