Lua 5.1's API provides an error() function, which accepts a string (the error message) and a "level".
My understanding is that level
, lets you move up the call stack, so you can provide nicer error reporting, especially when delivering a module as an API.
For example, imagine the user calls api_function(x)
with x = nil
. This will be an error, but the API doesn't know until it's quite a bit into it's code.
It may result in this call stack:
api_function(x) : user_file.lua:30
-> api_function : api.lua:20
-> some_function : api.lua:250
-> handle_when_x_string : api_string.lua:20
-> error("value is nil") : api_string.lua:66
As written, the user will see something like api_string.lua:66 error: value is nil
, when what they really want to see the "nice" error, user_file.lua:30 error: value is nil
. ("Is that error my fault or a bug in the API?")
Now, we can change the code to "pop the call stack",
api_function(x) : user_file.lua:30
-> api_function : api.lua:20
-> some_function : api.lua:250
-> handle_when_x_string : api_string.lua:20
-> error("value is nil", 5) : api_string.lua:66
Which will return the "nice" error, but, imagine you can also call handle_when_x_string
more directly (poor API design aside),
another_api_fn(x) : user_file.lua:44
-> another_api_fn : api.lua:11
-> handle_when_x_string : api_string.lua:20
-> error("value is nil", 5) : api_string.lua:66
Now our "pop level" is incorrect. Perhaps in this example, it would simply pop to the top and stop trying, but the principle of "incorrect level" remains at least uncomfortable, it may even pop "out" of where the user caused the error.
I can see a few solutions:
api_function
& another_api_fn
) in a pcall, catch any error and re-bubble with a known "good" level value.return nil, error
or some similar pattern, then check for that in api_function
and act as required.My questions are:
First, you need to differentiate from errors due to a bad API call, and actual bugs in your code.
If the purpose of the error
call is to tell the API user that they passed the wrong arguments, you should validate the arguments in every API function, so that the error level will be knowable, and so the rest of your library knows it's working with valid arguments. If you end up with a complicated hierarchy of validating functions, they can take parameters for the function name and error level. Here's a very contrived example for how you can use error levels:
local function lessThan100(x, funcName, errorLevel)
if x >=100 then
error(funcName .. ' needs a number less than 100', errorLevel)
end
end
local function numLessThan100(x, funcName, errorLevel)
if type(x) ~= 'number' then
error(funcName .. ' needs a number', errorLevel)
end
lessThan100(x, funcName, errorLevel + 1)
end
-- API function
local function printNum(x)
numLessThan100(x, 'printNum', 3)
print(x)
end
If the error
call represents a bug in your code, then don't use a level, because you can't know what triggers the bug.