2 Commits

Author SHA1 Message Date
6f832cd67b validation: cover nil/None return from lua_func/py_func
Two new steps per language: function returning nothing and function
returning explicit nil/None. Both tagged $(test)_PASS — they would
have failed before the lua nil fix (Lua side reported nil result as
error). Python side already worked but is covered for parity.
2026-05-17 18:13:03 +02:00
ff46886865 lua_func: nil return is not an error
_handle_request was using the 1st pcall return as the error
discriminator, so any Lua function returning nothing (e.g. long_wait
in the example) was reported as failed. Discriminate on the 2nd
return (err) instead, and encode nil result as cjson.null so the
returned_value field stays present in the JSON-RPC response.
2026-05-17 18:04:51 +02:00
5 changed files with 51 additions and 10 deletions

View File

@@ -41,8 +41,7 @@ end
--- INTERNAL: Handle requests from the client --- INTERNAL: Handle requests from the client
function JSONRPC:_handle_request(req) function JSONRPC:_handle_request(req)
local method = self.methods[req.method] local method = self.methods[req.method]
local ok, ret local ok, ret, err
local res, err
if not method then if not method then
if req.id then self:_send_error(req.id, string.format("Method '%s' not registered in lua server")) end if req.id then self:_send_error(req.id, string.format("Method '%s' not registered in lua server")) end
return return
@@ -52,15 +51,18 @@ function JSONRPC:_handle_request(req)
-- Only send response if it's not a Notification (notifications have no ID) -- Only send response if it's not a Notification (notifications have no ID)
if req.id then if req.id then
if ok then if not ok then
res = ret -- pcall trapped a runtime error in the method itself.
if res == nil then self:_send_error(req.id, tostring(ret))
elseif err ~= nil then
-- Method ran but signaled a logical error via its 2nd return.
self:_send_error(req.id, tostring(err)) self:_send_error(req.id, tostring(err))
else else
self:_send({ jsonrpc = "2.0", result = { returned_value = res }, id = req.id }) -- Success. A user function returning nothing yields ret==nil;
end -- encode it as JSON null so "returned_value" stays present.
else local val = ret
self:_send_error(req.id, tostring(err)) if val == nil then val = json.null end
self:_send({ jsonrpc = "2.0", result = { returned_value = val }, id = req.id })
end end
end end
end end

View File

@@ -49,4 +49,12 @@ function module.test_delgd()
return 0 return 0
end end
function module.return_nothing()
-- Returns no value: ret is nil but no error.
end
function module.return_explicit_nil()
return nil
end
return module return module

View File

@@ -186,6 +186,18 @@
file: $(test_path)$(psep)lua_func.lua file: $(test_path)$(psep)lua_func.lua
func_name: test_delgd func_name: test_delgd
- lua_func:
name: function returning nothing should succeed
key: $(test)_PASS
file: $(test_path)$(psep)lua_func.lua
func_name: return_nothing
- lua_func:
name: function returning explicit nil should succeed
key: $(test)_PASS
file: $(test_path)$(psep)lua_func.lua
func_name: return_explicit_nil
- group: - group:
name: context_id tests name: context_id tests
steps: steps:

View File

@@ -54,3 +54,10 @@ def test_delgd():
tm.delgd("_py_delgd_test") tm.delgd("_py_delgd_test")
assert tm.gd("_py_delgd_test", None) is None assert tm.gd("_py_delgd_test", None) is None
return 0 return 0
def return_nothing():
# Falls off the end: implicit None return, no error.
pass
def return_explicit_none():
return None

View File

@@ -196,6 +196,18 @@
file: $(test_path)$(psep)py_func.py file: $(test_path)$(psep)py_func.py
func_name: test_delgd func_name: test_delgd
- py_func:
name: function returning nothing should succeed
key: $(test)_PASS
file: $(test_path)$(psep)py_func.py
func_name: return_nothing
- py_func:
name: function returning explicit None should succeed
key: $(test)_PASS
file: $(test_path)$(psep)py_func.py
func_name: return_explicit_none
- group: - group:
name: context_id tests name: context_id tests
steps: steps: