M runtime/pb/_types.lua => runtime/pb/_types.lua +24 -9
@@ 81,8 81,8 @@
---@field codec table # internal: shared with generated code
---@field wkt pb.Wkt
---@field NULL userdata # google.protobuf.Value null sentinel
----@field register fun(full_name: string, desc: pb.Descriptor)
----@field lookup fun(full_name: string): pb.Descriptor?
+---@field register fun(desc: pb.Descriptor): pb.Descriptor
+---@field lookup fun(name_or_url: string): pb.Descriptor?
---@field any pb.Any
---@field grpc pb.Grpc
---@field c_runtime? table # set when PB_ENABLE_C=1 and load succeeds
@@ 104,18 104,33 @@
---@field finalize_message fun(desc: pb.Descriptor): pb.Descriptor
---@field register_extension fun(extendee_desc: pb.Descriptor, ext: pb.Field)
+---@class pb.AnyMessage
+---@field type_url string
+---@field value string
+
---@class pb.Any
----@field pack fun(t: table, type_url: string): table # returns google.protobuf.Any shape
----@field unpack fun(any_msg: table): table?, pb.Descriptor?
+---@field pack fun(desc: pb.Descriptor, t: table, type_url_prefix?: string): pb.AnyMessage
+---@field unpack fun(any_msg: pb.AnyMessage, desc_override?: pb.Descriptor): table
---@class pb.Wkt
----@field NULL userdata
----@field register fun(full_name: string, desc: pb.Descriptor)
----@field lookup fun(full_name: string): pb.Descriptor?
+---@field NULL userdata
+---@field register fun(desc: pb.Descriptor): pb.Descriptor # key is desc.name; also indexes the default type.googleapis.com/ URL
+---@field lookup fun(name_or_url: string): pb.Descriptor?
+---@field any_pack fun(desc: pb.Descriptor, t: table, type_url_prefix?: string): pb.AnyMessage
+---@field any_unpack fun(any_msg: pb.AnyMessage, desc_override?: pb.Descriptor): table
+
+---@class pb.JsonEncodeOpts
+---@field use_proto_names? boolean emit snake_case field names instead of camelCase
+---@field emit_defaults? boolean emit fields equal to proto3 defaults (alias: always_emit_zero_value)
+---@field always_emit_zero_value? boolean deprecated alias of emit_defaults
+---@field indent? string non-empty string ⇒ pretty-print with that indent unit
+
+---@class pb.JsonDecodeOpts
+---@field ignore_unknown_fields? boolean silently drop unknown JSON fields instead of erroring
---@class pb.Json
----@field encode fun(desc: pb.Descriptor, t: table, opts?: table): string
----@field decode fun(desc: pb.Descriptor, s: string, opts?: table): table
+---@field encode fun(desc: pb.Descriptor, t: table, opts?: pb.JsonEncodeOpts): string
+---@field decode fun(desc: pb.Descriptor, s: string, opts?: pb.JsonDecodeOpts): table
---@class pb.Text
---@field encode fun(desc: pb.Descriptor, t: table, opts?: pb.TextOpts): string
M runtime/pb/codec.lua => runtime/pb/codec.lua +15 -0
@@ 80,6 80,9 @@ local decode_group
-- - sub-message fields: recursive merge
-- Sub-messages whose descriptor carries a custom decode (WKT) are replaced
-- wholesale because their decoded value is not a generic Lua table.
+---@param desc pb.Descriptor
+---@param prev table decoded message being accumulated
+---@param decoded table newly-decoded copy to merge into `prev` in place
local function merge_message(desc, prev, decoded)
for i = 1, #desc.fields do
local f = desc.fields[i]
@@ 625,12 628,17 @@ end
-- without walking a full message (e.g. lazy passthrough re-encode, which
-- splices original wire segments for untouched fields and calls
-- encode_field for the dirty ones).
+---@param field pb.Field
+---@param value any
+---@param out string[] table.concat-friendly chunk buffer; encoded bytes are appended
+---@param force? boolean bypass proto3 default-value elision (used for extensions and inside oneofs)
M.encode_field = function(field, value, out, force)
return encode_field(field, value, out, force)
end
-- compile_writers attaches `f._writer` to each field where the shape is
-- specialized. Called from pb.finalize_message after the oneof flatten.
+---@param desc pb.Descriptor
function M.compile_writers(desc)
for _, f in ipairs(desc.fields) do
f._writer = build_writer(f, desc.name)
@@ 863,12 871,16 @@ local function build_reader(f)
return nil
end
+---@param desc pb.Descriptor
function M.compile_readers(desc)
for _, f in ipairs(desc.fields) do
f._reader = build_reader(f)
end
end
+---@param desc pb.Descriptor
+---@param data table message contents keyed by proto field name
+---@return string wire-format bytes (proto3 / proto2)
encode_message = function(desc, data)
if type(data) ~= 'table' then
error(("expected table for message %s, got %s"):format(desc.name, type(data)), 0)
@@ 1129,6 1141,9 @@ decode_extension = function(ext, buf, pos, wt, result)
error("decode_extension: unknown kind " .. tostring(kind), 0)
end
+---@param desc pb.Descriptor
+---@param buf string wire-format bytes
+---@return table decoded message; unknown fields go in `_unknown_fields`, extensions in `_extensions`
decode_message = function(desc, buf)
if type(buf) ~= 'string' then
error(("expected string for decode of %s, got %s"):format(desc.name, type(buf)), 0)
M runtime/pb/grpc.lua => runtime/pb/grpc.lua +19 -0
@@ 65,6 65,10 @@ end
-- loopback to bridge an in-process server fiber with a client caller.
-- The returned `internal_state` is exposed so the transport (not the
-- caller) can flag errors and trigger close.
+---@param buf_size? integer fiber.channel capacity; defaults to DEFAULT_BUFFER
+---@return table client speaks send / close_send / recv / cancel
+---@return table server speaks recv / send / _finish / _force_close_recv
+---@return table internal_state shared {canceled, server_err} accessed by the transport
function M.new_stream_pair(buf_size)
buf_size = buf_size or DEFAULT_BUFFER
local c2s = fiber.channel(buf_size) -- client -> server
@@ 189,6 193,8 @@ end
-- loopback(server) bridges an in-process M.<Service>_server(impl) result
-- into the transport contract. Streaming methods run their handler on a
-- worker fiber and communicate via fiber.channel.
+---@param server pb.GrpcServer output of `M.<Service>_server(impl)`
+---@return pb.GrpcTransport
function M.loopback(server)
if type(server) ~= 'table' or type(server.methods) ~= 'table' then
error("pb.grpc.loopback: expected a server table from M.<Service>_server()", 0)
@@ 213,6 219,8 @@ end
-- multiplex({server1, server2, ...}) merges several servers' methods +
-- streams under a single transport. Errors on duplicate paths.
+---@param servers pb.GrpcServer[]
+---@return pb.GrpcTransport
function M.multiplex(servers)
local methods, streams = {}, {}
for _, srv in ipairs(servers) do
@@ 254,6 262,9 @@ end
-- means we can refactor the streaming surface without re-running protoc.
-- Wrap a server-streaming call: caller calls stream:recv() until nil.
+---@param raw table transport-side stream view (bytes)
+---@param output_decode fun(bytes: string): table per-message decoder for the typed view
+---@return table {recv(self): msg?, err?; cancel(self)}
function M.wrap_server_stream(raw, output_decode)
return {
recv = function(_)
@@ 266,6 277,10 @@ function M.wrap_server_stream(raw, output_decode)
end
-- Wrap a client-streaming or bidi call: caller sends + recvs.
+---@param raw table transport-side stream view (bytes)
+---@param input_encode fun(msg: table): string per-message encoder for the typed view
+---@param output_decode fun(bytes: string): table per-message decoder for the typed view
+---@return table {send, close_send, recv, cancel}
function M.wrap_call(raw, input_encode, output_decode)
return {
send = function(_, msg)
@@ 284,6 299,10 @@ end
-- Wrap a server-side stream view for the generated server handler:
-- the user-supplied impl is called with a stream that speaks decoded
-- messages, hiding the per-message encode/decode boundary.
+---@param raw table server-side stream view (bytes)
+---@param input_decode? fun(bytes: string): table decoder for inbound messages (nil ⇒ server_stream: no inbound)
+---@param output_encode? fun(msg: table): string encoder for outbound messages (nil ⇒ client_stream: no outbound)
+---@return table {recv?, send?, close_send?, cancel}
function M.wrap_server_view(raw, input_decode, output_encode)
local wrapped = {}
if input_decode ~= nil then
M runtime/pb/json.lua => runtime/pb/json.lua +8 -0
@@ 967,6 967,10 @@ end
to_json_value = encode_message
+---@param desc pb.Descriptor
+---@param t table
+---@param opts? pb.JsonEncodeOpts
+---@return string
function M.encode(desc, t, opts)
if opts ~= nil and type(opts) ~= 'table' then
error('pb.json.encode: opts must be a table, got ' .. type(opts), 0)
@@ 1485,6 1489,10 @@ local function find_duplicate_json_keys(s)
end
end
+---@param desc pb.Descriptor
+---@param s string
+---@param opts? pb.JsonDecodeOpts
+---@return table
function M.decode(desc, s, opts)
local dup_err = find_duplicate_json_keys(s)
if dup_err ~= nil then error(dup_err, 0) end
M runtime/pb/wkt.lua => runtime/pb/wkt.lua +11 -0
@@ 546,6 546,8 @@ local function type_url_full_name(url)
return url:match('([^/]+)$') or url
end
+---@param desc pb.Descriptor
+---@return pb.Descriptor
M.register = function(desc)
if type(desc) ~= 'table' or desc.name == nil then
error('pb.register: expected a descriptor with a `name` field', 0)
@@ 555,11 557,17 @@ M.register = function(desc)
return desc
end
+---@param name_or_url string bare full name (`pkg.Foo`) or a type URL (`type.googleapis.com/pkg.Foo`)
+---@return pb.Descriptor?
M.lookup = function(name_or_url)
return REGISTRY[name_or_url] or REGISTRY[type_url_full_name(name_or_url)]
end
-- Pack a Lua message table into an opaque Any form.
+---@param desc pb.Descriptor
+---@param t table
+---@param type_url_prefix? string defaults to `type.googleapis.com/`
+---@return pb.AnyMessage
M.any_pack = function(desc, t, type_url_prefix)
if desc == nil or desc.name == nil then
error('pb.any.pack: descriptor must have a `name`', 0)
@@ 571,6 579,9 @@ M.any_pack = function(desc, t, type_url_prefix)
end
-- Unpack an Any table. `desc_or_nil` overrides the registry lookup.
+---@param any_t pb.AnyMessage google.protobuf.Any-shaped table {type_url=..., value=...}
+---@param desc_or_nil? pb.Descriptor override the registry lookup
+---@return table
M.any_unpack = function(any_t, desc_or_nil)
if type(any_t) ~= 'table' then
error('pb.any.unpack: expected Any table', 0)