diff --git a/cmd/protoc-gen-tarantool/internal/gen/gen.go b/cmd/protoc-gen-tarantool/internal/gen/gen.go index c7484b38accede9f604f20c0cf860fe718c8df51..cbdc8be6c4bdc53579655152b9dc64213e1d438a 100644 --- a/cmd/protoc-gen-tarantool/internal/gen/gen.go +++ b/cmd/protoc-gen-tarantool/internal/gen/gen.go @@ -239,6 +239,12 @@ w.line("})") if opts := w.renderOpts(e.Desc.Options()); opts != "" { w.line("M.%s_descriptor.options = %s", name, opts) } + // Proto2 enums are closed: unknown numeric values must be rejected at + // JSON/text decode time and on wire they round-trip as unknown fields. + // Proto3 enums are open. Surface the flag so codecs can branch. + if e.Desc.IsClosed() { + w.line("M.%s_descriptor.closed = true", name) + } emitEnumValueOptions(w, name, e) // Convenience aliases the user can reach via `M.MyEnum.RED`, etc. w.line("M.%s = M.%s_descriptor.by_name", name, name) diff --git a/examples/expected/full/proto2_basic/proto2_basic_pb.lua b/examples/expected/full/proto2_basic/proto2_basic_pb.lua index b5b2d7e55b035b950a5ae5c61edf03570a68600d..185b7ef43c6d4740f4dffef1d5826355491a6b86 100644 --- a/examples/expected/full/proto2_basic/proto2_basic_pb.lua +++ b/examples/expected/full/proto2_basic/proto2_basic_pb.lua @@ -16,6 +16,7 @@ RED = 0, GREEN = 1, BLUE = 2, }) +M.Defaults_Color_descriptor.closed = true M.Defaults_Color = M.Defaults_Color_descriptor.by_name -- Pre-declare message descriptors so cross-references resolve. diff --git a/examples/expected/full/protobuf_test_messages/proto2/test_messages_proto2_pb.lua b/examples/expected/full/protobuf_test_messages/proto2/test_messages_proto2_pb.lua index d5455cff79aba4accf8437d39021675ac6f917d5..d9eebc6e4bdb6ed4d98f19f90747459b7082cbfc 100644 --- a/examples/expected/full/protobuf_test_messages/proto2/test_messages_proto2_pb.lua +++ b/examples/expected/full/protobuf_test_messages/proto2/test_messages_proto2_pb.lua @@ -16,6 +16,7 @@ FOREIGN_FOO = 0, FOREIGN_BAR = 1, FOREIGN_BAZ = 2, }) +M.ForeignEnumProto2_descriptor.closed = true M.ForeignEnumProto2 = M.ForeignEnumProto2_descriptor.by_name -- Enum: protobuf_test_messages.proto2.TestAllTypesProto2.NestedEnum @@ -25,6 +26,7 @@ BAR = 1, BAZ = 2, NEG = -1, }) +M.TestAllTypesProto2_NestedEnum_descriptor.closed = true M.TestAllTypesProto2_NestedEnum = M.TestAllTypesProto2_NestedEnum_descriptor.by_name -- Enum: protobuf_test_messages.proto2.EnumOnlyProto2.Bool @@ -32,6 +34,7 @@ M.EnumOnlyProto2_Bool_descriptor = pb.enum("protobuf_test_messages.proto2.EnumOnlyProto2.Bool", { kFalse = 0, kTrue = 1, }) +M.EnumOnlyProto2_Bool_descriptor.closed = true M.EnumOnlyProto2_Bool = M.EnumOnlyProto2_Bool_descriptor.by_name -- Enum: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.NestedEnum @@ -41,6 +44,7 @@ BAR = 1, BAZ = 2, NEG = -1, }) +M.TestAllRequiredTypesProto2_NestedEnum_descriptor.closed = true M.TestAllRequiredTypesProto2_NestedEnum = M.TestAllRequiredTypesProto2_NestedEnum_descriptor.by_name -- Pre-declare message descriptors so cross-references resolve. diff --git a/examples/expected/runtime/proto2_basic/proto2_basic_pb.lua b/examples/expected/runtime/proto2_basic/proto2_basic_pb.lua index 7100d8420cf41f9939bda04747a86ab1df51dd77..d8b7fc0565daaa148fc119b8dcaff622cae5f02b 100644 --- a/examples/expected/runtime/proto2_basic/proto2_basic_pb.lua +++ b/examples/expected/runtime/proto2_basic/proto2_basic_pb.lua @@ -16,6 +16,7 @@ RED = 0, GREEN = 1, BLUE = 2, }) +M.Defaults_Color_descriptor.closed = true M.Defaults_Color = M.Defaults_Color_descriptor.by_name -- Pre-declare message descriptors so cross-references resolve. diff --git a/examples/expected/runtime/protobuf_test_messages/proto2/test_messages_proto2_pb.lua b/examples/expected/runtime/protobuf_test_messages/proto2/test_messages_proto2_pb.lua index 4d577b04c34678f69224e5248a37237436160ae2..b82091d0a19c9b0107907c88dcc78899a2444cf1 100644 --- a/examples/expected/runtime/protobuf_test_messages/proto2/test_messages_proto2_pb.lua +++ b/examples/expected/runtime/protobuf_test_messages/proto2/test_messages_proto2_pb.lua @@ -16,6 +16,7 @@ FOREIGN_FOO = 0, FOREIGN_BAR = 1, FOREIGN_BAZ = 2, }) +M.ForeignEnumProto2_descriptor.closed = true M.ForeignEnumProto2 = M.ForeignEnumProto2_descriptor.by_name -- Enum: protobuf_test_messages.proto2.TestAllTypesProto2.NestedEnum @@ -25,6 +26,7 @@ BAR = 1, BAZ = 2, NEG = -1, }) +M.TestAllTypesProto2_NestedEnum_descriptor.closed = true M.TestAllTypesProto2_NestedEnum = M.TestAllTypesProto2_NestedEnum_descriptor.by_name -- Enum: protobuf_test_messages.proto2.EnumOnlyProto2.Bool @@ -32,6 +34,7 @@ M.EnumOnlyProto2_Bool_descriptor = pb.enum("protobuf_test_messages.proto2.EnumOnlyProto2.Bool", { kFalse = 0, kTrue = 1, }) +M.EnumOnlyProto2_Bool_descriptor.closed = true M.EnumOnlyProto2_Bool = M.EnumOnlyProto2_Bool_descriptor.by_name -- Enum: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.NestedEnum @@ -41,6 +44,7 @@ BAR = 1, BAZ = 2, NEG = -1, }) +M.TestAllRequiredTypesProto2_NestedEnum_descriptor.closed = true M.TestAllRequiredTypesProto2_NestedEnum = M.TestAllRequiredTypesProto2_NestedEnum_descriptor.by_name -- Pre-declare message descriptors so cross-references resolve. diff --git a/runtime/pb/text.lua b/runtime/pb/text.lua index 275448d12db0c26517e5f600d5ee78908ca7a18b..1ecc1d6f5c9abb64f0bbd492e8b50abcd2dc42d0 100644 --- a/runtime/pb/text.lua +++ b/runtime/pb/text.lua @@ -1079,8 +1079,15 @@ end advance(S) return num end - -- Numeric enum value: signed int32-shape. - return parse_int_value(S, 'int32') + -- Numeric enum value: signed int32-shape. Closed enums (proto2) reject + -- numbers that don't map to a declared value; open enums (proto3) accept + -- any int32 to preserve forward-compatibility on the wire. + local n = parse_int_value(S, 'int32') + if enum_desc.closed and enum_desc.by_value[n] == nil then + err(S, ('unknown enum number %d for closed enum %s'): + format(n, enum_desc.name)) + end + return n end local function parse_scalar_value(S, proto_type) @@ -1209,7 +1216,7 @@ end if f.kind == 'enum' then return parse_enum_value(S, f.enum) end - if f.kind == 'message' then + if f.kind == 'message' or f.kind == 'group' then return parse_message_field_value(S, f.message, depth) end if f.kind == 'map' then @@ -1325,6 +1332,24 @@ local field = nil if desc ~= nil then if field_name ~= nil then field = desc.field_by_name and desc.field_by_name[field_name] + if field == nil then + -- Proto2 group reference: text format uses the group's + -- submessage simple name (e.g. `Data` or `MultiWordGroupField`) + -- or its lowercase ASCII fold instead of the field name. + -- The submessage name lookup is rare enough to do a linear + -- sweep over fields rather than precomputing an index. + local lc = field_name:lower() + for _, gf in ipairs(desc.fields) do + if gf.kind == 'group' then + local label = gf.message and gf.message.name or '' + label = label:match('[^%.]+$') or label + if field_name == label or lc == label:lower() then + field = gf + break + end + end + end + end elseif numeric_id ~= nil then field = desc.field_by_id and desc.field_by_id[numeric_id] end @@ -1353,7 +1378,7 @@ -- Map / message / list / scalar handling. local kind = field.kind -- `:` is required before scalar/enum, optional before message/map. - if kind == 'message' or kind == 'map' then + if kind == 'message' or kind == 'map' or kind == 'group' then accept_punct(S, ':') else expect_punct(S, ':') @@ -1410,7 +1435,7 @@ format(field.name)) end local v = parse_value_for_field(S, field, depth) clear_oneof_siblings(result, field) - if kind == 'message' and result[field.name] ~= nil then + if (kind == 'message' or kind == 'group') and result[field.name] ~= nil then -- text-format spec: repeated singular sub-messages merge. We -- approximate by shallow-merging fields; sufficient for the -- conformance corpus shapes.