text: render captured unknown fields, tolerate SGROUP in skip
Two changes close the proto3 text-format conformance suite:
1. `wire.skip_field` learns SGROUP/EGROUP. Wire 3 recurses through
inner tags until a matching EGROUP, with field_id checked against
the SGROUP's id. Callers (codec.lua, lazy.lua, wkt.lua, generated
full-mode `_pb.lua`) now pass the tag's field_id so groups inside
unknown-field skips don't error.
2. `pb.text.encode` walks the captured `_unknown_fields` buffer when
`opts.print_unknown_fields=true` and emits each entry in
TextFormat numeric-field form:
VARINT -> "<id>: <uint64>"
I64/I32 -> "<id>: 0x<hex>"
LEN -> speculative "<id> { <recurse> }"; rolls back to
byte-string form if the inner bytes don't parse as a
sub-message
SGROUP -> "<id> { <recurse> }" through matching EGROUP
`cmd/conformance/core.lua` threads `req.print_unknown_fields` into
`pb.text.encode` so `_Drop` tests drop unknowns and `_Print` tests
render them.
Conformance: text-format suite goes from 2 ✓ / 6 expected fails to
8 ✓ / 0 expected fails. All eight regression tests in
`conformance_test.lua` (one per upstream test, plus the fixed field-1011
tag bytes that were miscomputed earlier) now assert the target output.
json: strict proto3 JSON conformance — close all Required failures
The conformance suite's Required JSON failures collapse to zero with
this pass. Local tests grow from 456 to 497 to pin every fix.
Decode side — `runtime/pb/json.lua`:
* Strict scalar validators per type. Numeric strings must match the
JSON number grammar (no leading whitespace, no partial numerics);
out-of-range values, NaN/Inf surrogates from JSON literals, and
type mismatches all become parse_error.
* Quoted exponential ints ("1e5" -> 100000) are accepted via the
JSON-number grammar path, matching Int32FieldQuotedExponentialValue.
* Timestamp parser: strict RFC 3339 (uppercase T/Z, ±HH:MM offset,
≤9 frac digits, range check). Output uses a portable Hinnant-style
epoch_to_ymdhms so year 0001 zero-pads correctly — glibc's POSIX
%Y emits "1" for that year, breaking round-trip.
* Duration parser/formatter: mandatory `s` suffix, ±10000-year range,
sign-matching nanos, 0/3/6/9-digit fractional output.
* Any: WKT-aware nesting under "value"; empty Any -> {}; Empty WKT
inside Any omits "value" (reference parser rejects {"value":{}});
@type URL without `/` rejected; empty @type with sibling fields
rejected.
* Reject NaN/Infinity in google.protobuf.Value.number_value (no JSON
literal for these).
* Duplicate oneof branches rejected; a null oneof branch does NOT
count as set, so a sibling non-null branch is unambiguous.
* Top-level JSON null rejected for messages; preserved for Value.
* Repeated/map values must be JSON array/object (not bare scalar).
Encode side — hand-rolled JSON emitter:
Tarantool's `json.encode` uses a fixed global precision so doubles
like 0.1 don't round-trip and we can't change it per-value without
polluting other users. Replace with a minimal emitter that picks the
shortest-round-tripping precision (15 -> 16 -> 17) per double and
handles NaN/Inf as quoted sentinel strings.
`runtime/pb/wkt.lua`: `timestamp_decode` now keeps invalid Timestamps as
a raw {seconds, nanos} table when `datetime.new` rejects them (negative
nanos, year > 9999, ...) so the JSON encoder can produce
serialize_error rather than the binary decoder raising parse_error.
Required by the Timestamp conformance suite.
`test/conformance_test.lua`: 41 new regression tests grouped under
Fixes 12-19, pinning every code path touched. Strict scalar
validators (one per rejection shape per type), Timestamp/Duration
strict parsing and canonical output, Any WKT/non-WKT/Empty handling,
Value NaN/Inf rejection, ValueAcceptNull round-trip, LuaJIT
NaN-boxing collision cases (0x7FFBCBA987654321, all-ones), shortest
double round-trip, oneof-null semantics.
`test/conformance/known_failures.txt`: refreshed. 15 Recommended-only
failures remain (FieldMask round-trip quirks, duplicate-field-name
detection, unknown-enum-string rejection, null-element-in-list,
NullValue oneof validator).
json: canonical lowerCamelCase + NullValue WKT descriptor
Two unrelated JSON-decoder gaps captured under the conformance triage:
1. to_camel's gsub pattern '_(%w)' didn't match consecutive underscores
and didn't drop trailing underscores, so proto names like
__field_name13 / field__name4_ / field_name17__ generated JSON keys
that didn't match what protoc produces. The fix strips trailing _+
and collapses '_+%w' to a capitalized letter; a leading underscore
thus capitalizes the next character, matching the spec.
2. pb.wkt didn't export a descriptor for google.protobuf.NullValue, so
any enum field whose type is NullValue (oneof_null_value, or the
implicit one inside Value) crashed decode_enum with "attempt to
index a nil value." Adds a minimal {by_name, by_value} descriptor.
Drops 3 entries from test/conformance/known_failures.txt
(FieldNameInSnakeCase, FieldNameWithDoubleUnderscores,
NullValueInOtherOneofOldFormat). Adds 4 regression tests covering
double underscore, leading underscore, trailing underscore, and the
NullValue enum decode path.
wkt: auto-register WKT descriptors so Any JSON decode resolves @type
pb.wkt exported Timestamp/Duration/Empty/FieldMask/Any/Struct/Value/
ListValue and the nine Wrapper descriptors, but the REGISTRY they live in
was empty until callers manually invoked pb.register. json_to_any looked
up @type in that empty registry, fell through to the opaque base64
fallback, and errored on every Any-of-WKT JSON payload.
Iterates M for every *_descriptor entry at module load and self-registers
it. Also calls pb.register on TestAllTypesProto3 in the conformance runner
so Any tests that embed the user message type also resolve.
Drops 10 entries from test/conformance/known_failures.txt.
Initial commit: protoc-gen-tarantool plugin + pb runtime
A protoc plugin (Go) and a pure-Lua + LuaJIT-FFI runtime that give
Tarantool a complete proto3 + gRPC stack. Two codegen modes (full
inline / runtime descriptor), 226-test luatest suite, 18-fixture
mainline-protoc interop corpus, JSON codec, well-known types,
gRPC client/server factories, runtime .proto parser, microbench
harness with allocation regression gate.
Covers PLAN.md M1-M5. Module is `pb` (not `protobuf`) to avoid
colliding with Tarantool's built-in encode-only `protobuf` module.