Exception handling of lapis
Types of errors
Lapis distinguishes two types of errors: recoverable and unrecoverable. Errors thrown or invoked during execution by Lua's runtime are considered unrecoverable. (This also includes Lua's built-in function assert)
Because unrecoverable errors are not captured by users, Lapis captures them and prints an exception message to the browser. Any operation that has been run may be aborted, and Lapis will print a special view to display the stack and set status to 500.
These types of errors are usually a bug or other serious problem and should be fixed.
A recoverable error is the way in which the user controls the suspension of execution of a handler to run the specified error handler. They are implemented using a coroutine rather than Lua's error system.
For example, invalid input from users or missing records in the database.
Capture recoverable errors
The capture_errors helper is used to wrap an operation so that it can catch errors and run error handlers.
It does not catch runtime errors. If you want to catch runtime errors, you should use pcall, as you usually do in Lua.
Lua has no notion of exceptions in most other languages. Instead, Lapis uses collaborators to create an exception handling system. We use capture_errors helper to define the range of errors that we must catch. Then we can use yield_error to throw an original error.
local lapis = require("lapis") local app_helpers = require("lapis.application") local capture_errors, yield_error = app_helpers.capture_errors, app_helpers.yield_error local app = lapis.Application() app:match("/do_something", capture_errors(function(self) yield_error("something bad happened") return "Hello!" end))
What happens when something goes wrong? This operation stops at the first error and then runs the error handler. The default error handler sets an array-like table in self.errors and returns {render = true}. In your view, you can display these error messages. This means that if you have a named route, the view of that route will be rendered. Then when an error table appears, you should write your own view.
If you want to have a custom error handler, you can pass in a table to call capture_errors: (Note that self.errors is set before the custom handler)
app:match("/do_something", capture_errors({ on_error = function(self) log_erorrs(self.errors) -- you would supply the log_errors function return { render = "my_error_page", status = 500 } end, function(self) if self.params.bad_thing then yield_error("something bad happened") end return { render = true } end }))
When the capture_errors handler is called, the first position value of the incoming table is used as the operation.
If you are building a JSON API, lapis provides another method capture_errors_json, which renders errors in JSON objects, as follows:
local lapis = require("lapis") local app_helpers = require("lapis.application") local capture_errors_json, yield_error = app_helpers.capture_errors_json, app_helpers.yield_error local app = lapis.Application() app:match("/", capture_errors_json(function(self) yield_error("something bad happened") end))
Then the following error will appear (use the correct content-type)
{ errors: ["something bad happened"] }
assert_error
In lua, when a function fails to execute, it is customary to return nil and an error message. For this purpose, an assert_error help program is provided. If the first parameter is false (nil or false), the second parameter is thrown as an error, otherwise all parameters will be returned from the function.
The use of assert_error is very convenient for database methods.
local lapis = require("lapis") local app_helpers = require("lapis.application") local capture_errors, assert_error = app_helpers.capture_errors, app_helpers.assert_error local app = lapis.Application() app:match("/", capture_errors(function(self) local user = assert_error(Users:find({id = "leafo"})) return "result: " .. user.id end))