Generator now supports protobuf editions alongside proto2/proto3, with properly quoted file option values. A key step toward future-proofing generated proto files.
New option to maintain field tag number compatibility across regenerations — critical for wire format stability. Works on both regular fields and union structs.
Integrated buf's protovalidate framework — declare CEL validation rules directly in spreadsheet field/worksheet properties, compiled into generated proto files.
Switched to Buf for protobuf generation, linting, and BSR publishing. CI no longer requires manual protoc install steps.
Field numbers preserved on union structs. Fixed column ordering — definition order index (i+1) used as column suffix, ensuring stable Field1, Field2….
Configure different output formats (json, txtpb, binpb) for individual messagers in confgen — fine-grained control beyond the global default.
The biggest architectural change in v0.16.0 (#387 — 6,595 insertions, 119 files).
Previously tableau stopped on the first parse error. Now a hierarchical
Collector tree
accumulates multiple errors concurrently — global → book → sheet → message — each level
with its own cap. You see all errors in one run.
File option values like go_package now require explicit double-quote wrapping in YAML config.
protoFiles parsing unified across all code paths. Workflows relying on previous parser behavior may be affected. Re-run proto generation and verify output.
cross — DeprecatedThe field option cross is deprecated as of this release. Existing sheets continue to work but the option will be removed in a future version.
Replaced bytedance/sonic with valyala/fastjson for cross-platform compatibility.
Use template quote function instead of manual strconv.Quote at all call sites across the codebase.
Improved xfs.IsSamePath to resolve absolute paths, fixing subtle mismatches on Windows and symlinked directories.
Richer, more actionable error messages for field, message, list, and map violations — easier to trace issues to source cells.
Tableau converts Excel/CSV spreadsheets into protobuf configs. Sheets use a 3-row header convention: Row 1 = field names · Row 2 = field types + props · Row 3 = notes. Examples use real test-data from the repository.
Add validate: /
validate_complex: /
validate_message:
props directly in the field-type cell (Row 2). Worksheet-level rules go in the
@TABLEAU metasheet.
| A | B | C | |
|---|---|---|---|
| 1 | ID | Name | Score |
| 2 | map<uint32, Item>|{validate:"uint32:{gt:0}" validate_complex:"map:{min_pairs:1}"} | string|{validate:"string:{min_len:1 max_len:20}"} | int32|{validate:"int32:{gt:0 lte:100}"} |
| 3 | Item ID | Item Name | Item Score |
| 4 | 1 | sword | 80 |
| 5 | 2 | shield | 95 |
| A (Sheet) | B (Mode) | C (Validate) | |
|---|---|---|---|
| 2 | ValidateWorksheetLevel | cel_expression:"this.item_map.size() > 0" | |
| 3 | ValidateStructType | MODE_STRUCT_TYPE | cel_expression:"this.begin > this.end ? 'begin must be before end' : ''" |
| 4 | ValidateUnionType | MODE_UNION_TYPE | cel_expression:"this.type != 0" |
Adding a column mid-sheet silently shifts field tag numbers — breaking binary-serialized data.
Enable preserveFieldNumbers: true
to lock existing fields to their original numbers. New fields get max+1.
| A | B | C | |
|---|---|---|---|
| 1 | ID | Name | Level |
| 2 | map<uint32, Hero> | string | int32 |
| 3 | Hero ID | Hero name | Hero level |
| 4 | 1 | Arthur | 50 |
| A | B | C | D | |
|---|---|---|---|---|
| 1 | ID | Name | HP | Level |
| 2 | map<uint32, Hero> | string | int32 | int32 |
| 3 | Hero ID | Hero name | Hero HP | Hero level |
| 4 | 1 | Arthur | 500 | 50 |
.proto files in outdir to learn current field numbers — keep them before regenerating.
The bug was in the confgen parser: when reading a union member's
value fields, it used fd.Number()
(the protobuf tag number) to construct the column name to look up. If a union member struct
has non-sequential field tag numbers, the parser searched for the wrong columns and silently
produced zero values. The fix uses i+1
(definition order) instead — matching how column names are always generated.
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | ID | TargetType | TargetField1 | TargetField2 | TargetField3 |
| 2 | map<int32, Task> | {union.Target}enum<union.Target.Type> | union | union | union |
| 3 | Task ID | Target type | Target field 1 | Target field 2 | Target field 3 |
| 4 | 1 | PVP | 10 | 200 | 500 |
| 5 | 2 | PVE | 3 | 5 |
Override output formats per messager. Supported formats: json,
txtpb (text protobuf),
binpb (binary protobuf).
Useful when some messagers need compact binary for runtime performance while others stay in human-readable JSON or text protobuf.
| A | B | C | |
|---|---|---|---|
| 1 | ID | Name | Price |
| 2 | map<uint32, Item> | string | int32 |
| 3 | Item ID | Item name | Item price |
| 4 | 1 | Sword | 100 |
| A | B | C | |
|---|---|---|---|
| 1 | Level | Exp | MaxHP |
| 2 | map<int32, LevelData> | int64 | int32 |
| 3 | Level num | Exp needed | Max HP |
| 4 | 1 | 0 | 100 |
With editions support, string file options must be explicitly double-quoted in YAML config.
cross — Deprecated
cross was used on a horizontal union list field to specify how many value-field columns each list element spans.
cross:3 means every element in the list occupies exactly 3 consecutive value-field columns.
The option is deprecated as of v0.16.0 — existing sheets still work but it will be removed in a future version.
The new xerrors.Collector tree collects multiple parse errors concurrently
across the full pipeline. Each level has its own cap — parsing stops only when a level's budget is exhausted, not on the very first error.
| A | B | C | |
|---|---|---|---|
| 1 | ID | Num | Price |
| 2 | uint32 | int32 | int32 |
| 3 | Item's ID | Item's num | Item's price |
| 4 | 1 | xyz | bad_price |
| A | B | |
|---|---|---|
| 1 | ShopID | Price |
| 2 | uint32 | int32 |
| 3 | Shop's ID | Goods's price |
| 4 | 1 | bad_price |
| 5 | bad_id | 200 |