Skip to content

HTTP Methods

Neon32eeee edited this page Oct 17, 2026 · 1 revision

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.

Entities

  • 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 → HEAD mapping is intentional. A HEAD handler is almost always the same as the GET handler with the body dropped, and zap drops the body for you.

Function breakdown

  • 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
    null r.method is not set at all (malformed request).
    true Method matches.
    false Method does not match.

    The idiomatic use is method.isGET(r) orelse false, which collapses "no method" and "wrong method" into a single false.

  • matches
    Purpose: compare a raw method string against a Method. 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 == 0 Allow everything. Returns {}.
    r.method == null Sends 400 Bad Request with "Missing HTTP method", returns null.
    Match found Returns {}.
    No match Sends 405 Method Not Allowed with "Invalid method", returns null.

    The ?void type 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.

Examples

1. Guarding a handler

const method = Mesh.method;

fn createUser(r: Mesh.zap.Request) void {
    if (method.MethodCheck(r, &.{.POST}) == null) return;

    // Only POST reaches here.
}

2. Allowing multiple methods on one endpoint

fn usersEndpoint(r: Mesh.zap.Request) void {
    if (method.MethodCheck(r, &.{ .GET, .POST }) == null) return;

    if (method.isPOST(r) orelse false) {
        // create
    } else {
        // list
    }
}

3. Checking a method without sending an error

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");
}

4. Inspecting the raw string

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});

Notes

  • MethodCheck covers 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 example 501 Not Implemented for unknown verbs), do the check manually with matches.
  • 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.
  • HEAD requests reaching a .GET-guarded handler will be handled by your GET logic with an empty body. That's almost always what you want.

Clone this wiki locally