Repository navigation
Response
The Response module is the layer between your handler and the client. Every function here sets a status code and sends a body - nothing more. There is no request inspection, no header manipulation, no content negotiation. Just the primitive: "here's what happened, here's the payload."
The reason it exists as its own module is that sending a body in zap returns an error, and every handler would otherwise repeat the same catch |err| std.debug.print(...) dance. Response collapses that into one call.
-
sendError
Purpose: the primitive. Sets a status and sends a body.
Signature:pub fn sendError(r: zap.Request, status: zap.http.StatusCode, msg: []const u8) void
-
sendBadRequest
Purpose: shortcut for400 Bad Request.
Signature:pub fn sendBadRequest(r: zap.Request, msg: []const u8) void
-
sendInternalServerError
Purpose: shortcut for500 Internal Server Error.
Signature:pub fn sendInternalServerError(r: zap.Request, msg: []const u8) void
-
sendErrorPrint
Purpose: formats a message withstd.fmt.allocPrintand sends it as an error.
Signature:pub fn sendErrorPrint( r: zap.Request, status: zap.http.StatusCode, allocator: Allocator, comptime fmt: []const u8, args: anytype, ) void
-
sendBody
Purpose: sends200 OKwith a body.
Signature:pub fn sendBody(r: zap.Request, msg: []const u8) void
-
getParamStrSave
Purpose: fetches a request parameter by name, sending an error response on failure.
Signature:pub fn getParamStrSave( r: zap.Request, allocator: Allocator, name: []const u8, ) ?[]const u8
-
notFound
Purpose: sends404 Not Foundwith the requested path in the body.
Signature:pub fn notFound(r: zap.Request, allocator: std.mem.Allocator, path: []const u8) void
All senders except getParamStrSave return void. They call r.setStatus(...), then r.sendBody(...), and if the send fails, print the error to stderr and return normally.
getParamStrSave returns ?[]const u8: the parameter value on success, null after sending an error response on failure. Both failure modes (parse error, missing parameter) send 500 Internal Server Error, not 400. If your API contract requires 400 for missing parameters, call r.getParamStr directly.
const Mesh = @import("root.zig");
const response = Mesh.response;
fn ping(r: Mesh.zap.Request) void {
response.sendBody(r, "pong");
}fn findUser(r: Mesh.zap.Request) void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const id = response.getParamStrSave(r, arena.allocator(), "id") orelse return;
// If `id` didn't parse, we never reach here - getParamStrSave already replied.
_ = id;
}fn fallback(r: Mesh.zap.Request) void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
response.notFound(r, arena.allocator(), r.path orelse "/");
}fn validate(r: Mesh.zap.Request, name: []const u8) void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
if (name.len == 0) {
response.sendErrorPrint(
r,
.bad_request,
arena.allocator(),
"field `{s}` must not be empty",
.{"name"},
);
return;
}
}- The send-swallowing behavior is deliberate. Once a response is on the wire, there is nothing useful a handler can do about a failed write. The error goes to stderr for diagnostics; the handler continues normally.
-
sendErrorPrintallocates through the supplied allocator. An arena is the natural choice - the message only needs to live long enough to be copied into the socket buffer.