Repository navigation
HTTP Methods
The Method module handles one question: did this request use a method I accept? That's it. It doesn't parse paths, doesn't inspect bodies, doesn't set status codes on success. Its only job is the guard at the top of a handler.
The reason it exists separately from zap is that zap gives you r.method as a raw string (?[]const u8), which means every handler would otherwise do std.mem.eql(u8, m, "POST") by hand. Method collapses that into an enum, adds the HEAD-counts-as-GET rule, and provides a one-liner that sends the right error for you.
-
Method- an enum of the supported HTTP methods.pub const Method = enum { GET, POST, PUT, PATCH, DELETE };
Value Matches raw string .GET"GET"or"HEAD".POST"POST".PUT"PUT".PATCH"PATCH".DELETE"DELETE"The
.GET→HEADmapping is intentional. AHEADhandler is almost always the same as theGEThandler with the body dropped, andzapdrops the body for you.
-
isGET/isPOST/isPUT/isPATCH/isDELETE
Purpose: compare the request's method against a specific one.
Signature:pub fn isGET(r: zap.Request) ?bool pub fn isPOST(r: zap.Request) ?bool pub fn isPUT(r: zap.Request) ?bool pub fn isPATCH(r: zap.Request) ?bool pub fn isDELETE(r: zap.Request) ?bool
Return value is tri-state:
Return Meaning nullr.methodis not set at all (malformed request).trueMethod matches. falseMethod does not match. The idiomatic use is
method.isGET(r) orelse false, which collapses "no method" and "wrong method" into a singlefalse. -
matches
Purpose: compare a raw method string against aMethod. Used internally, but exported for completeness.
Signature:pub fn matches(req: []const u8, m: Method) bool
Matching is case-sensitive and empty strings match nothing.
"GET"and"HEAD"both match.GET. -
MethodCheck
Purpose: guard at the top of a handler - check the method against an allow-list and send the right error if it fails.
Signature:pub fn MethodCheck(r: zap.Request, allowed: []const Method) ?void
Situation What happens allowed.len == 0Allow everything. Returns {}.r.method == nullSends 400 Bad Requestwith"Missing HTTP method", returnsnull.Match found Returns {}.No match Sends 405 Method Not Allowedwith"Invalid method", returnsnull.The
?voidtype is a signal, not a value. Since the only two outcomes are "ok" and "already sent an error response", there's nothing useful to carry in the success case.
const method = Mesh.method;
fn createUser(r: Mesh.zap.Request) void {
if (method.MethodCheck(r, &.{.POST}) == null) return;
// Only POST reaches here.
}fn usersEndpoint(r: Mesh.zap.Request) void {
if (method.MethodCheck(r, &.{ .GET, .POST }) == null) return;
if (method.isPOST(r) orelse false) {
// create
} else {
// list
}
}If you want to branch without auto-responding - for example in a dispatcher that handles all methods itself - use the is* helpers directly:
if (method.isDELETE(r) orelse false) {
// handle delete
} else {
response.sendError(r, .method_not_allowed, "unsupported");
}For the rare case where you need the raw method string (logging, metrics, non-standard verbs):
const raw = r.method orelse "(none)";
std.debug.print("received method: {s}\n", .{raw});-
MethodCheckcovers the two common rejection cases - missing method and wrong method - with the correct status codes. If your API needs a different body or a different code (for example501 Not Implementedfor unknown verbs), do the check manually withmatches. - The empty allow-list meaning "allow all" is a deliberate default: an empty configuration in a config file usually means "no restriction", not "reject everything". If you want to reject all methods, list none of them explicitly - but at that point, why have a handler at all.
-
HEADrequests reaching a.GET-guarded handler will be handled by yourGETlogic with an empty body. That's almost always what you want.