~bigbes/tarantool

tarantool-protobuf

798e8db974f929988200e49ada81076b406a50bd — Eugene Blikh 3 months ago 4db0366
json,text: presence-tracked fields skip proto3 default elision

Both codecs gated default-value elision on `f.optional or f.oneof` —
the proto3 implicit-presence shape. That elides proto2 `required`
fields set to zero and any other presence-tracked descriptor that
doesn't carry the proto3 explicit-optional flag.

Extend the bypass to `f.required` so a Cardinality{r=0} survives
the round-trip through pb.json.encode / pb.text.encode. 4 new
luatest cases pin the rule.
3 files changed, 47 insertions(+), 2 deletions(-)

M runtime/pb/json.lua
M runtime/pb/text.lua
M test/proto2_test.lua
M runtime/pb/json.lua => runtime/pb/json.lua +7 -1
@@ 900,7 900,13 @@ encode_message = function(desc, t)
                end
            else
                local emit = true
                if not emit_defaults and not (f.optional or f.oneof) then
                -- Default-elision only applies to proto3 implicit-presence
                -- shapes. Anything with explicit presence (`optional`,
                -- oneof, proto2 `required`) emits its value verbatim — even
                -- when it equals the type's zero/default — because the
                -- absence of the field is observably distinct from being
                -- set-to-default.
                if not emit_defaults and not (f.optional or f.oneof or f.required) then
                    if f.kind == 'scalar' then
                        local pt = f.proto_type
                        if pt == 'string' or pt == 'bytes' then

M runtime/pb/text.lua => runtime/pb/text.lua +4 -1
@@ 99,7 99,10 @@ local function enum_token(enum_desc, v)
end

local function is_proto3_default(f, v)
    if f.optional or f.oneof then return false end
    -- Skip elision for any presence-tracked field — proto3 explicit
    -- `optional`, oneof branches, and proto2 `optional`/`required` all
    -- carry observable absence-vs-set-to-default distinctions.
    if f.optional or f.oneof or f.required then return false end
    if f.kind == 'scalar' then
        local pt = f.proto_type
        if pt == 'string' or pt == 'bytes' then return v == '' end

M test/proto2_test.lua => test/proto2_test.lua +36 -0
@@ 239,6 239,42 @@ do
    end
end

-- JSON and text format: proto2 fields are all presence-tracked, so the
-- elision rules built around proto3's implicit zero defaults must not
-- fire on required-set-to-zero or optional-set-to-default.
do
    local g    = t.group('proto2_basic.codecs')
    local pb   = require('pb')
    local full = require('full.proto2_basic.proto2_basic_pb')

    g.test_json_required_zero_emitted = function()
        local s = pb.json.encode(full.Cardinality_descriptor, {r = 0})
        local d = pb.json.decode(full.Cardinality_descriptor, s)
        t.assert_equals(d.r, 0)
        t.assert(s:find('"r"%s*:%s*0'), 'required int32=0 must appear in JSON: ' .. s)
    end

    g.test_json_optional_default_emitted_when_set = function()
        local s = pb.json.encode(full.Defaults_descriptor, {i = 17, b = false})
        t.assert(s:find('"i"'), 'optional int32 at declared default must be emitted')
        t.assert(s:find('"b"%s*:%s*false'), 'optional bool=false must be emitted')
    end

    g.test_json_absent_optional_not_emitted = function()
        -- Even with emit_defaults, proto2 presence-tracked fields stay
        -- absent if the user didn't set them. We don't auto-materialize
        -- declared defaults into JSON output.
        local s = pb.json.encode(full.Defaults_descriptor, {}, {emit_defaults = true})
        t.assert_not(s:find('"i"'), 'absent optional must not be emitted')
        t.assert_not(s:find('"color"'), 'absent enum optional must not be emitted')
    end

    g.test_text_required_zero_emitted = function()
        local s = pb.text.encode(full.Cardinality_descriptor, {r = 0})
        t.assert(s:find('r:%s*0'), 'required int32=0 must appear in text format: ' .. s)
    end
end

-- Parity: full and runtime modes must produce byte-identical output for
-- the same input. Equivalent to the existing parity.full_vs_runtime group.
do