Skip to content

HTTP Methods Reference — Zig source

A searchable reference for every HTTP request method - GET, POST, PUT, PATCH, DELETE, and more. See at a glance which are safe, idempotent, and cacheable, then compare any two methods side by side.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Source: zig.zig
// Language: Zig (Zig 0.13, standard library only)
//
// HTTP request-method reference — pure, deterministic data + lookups.
//
// CosmoDev polyglot showcase port of the `http-methods` tool, ported from
// src/lib/http-methods.ts. Functionally equivalent to the TypeScript original:
// identical inputs yield identical outputs (case-insensitive lookup, flag +
// free-text filtering, and a human-readable semantic comparison report).
//
// Property flags (safe / idempotent / cacheable / hasBody) follow RFC 9110
// and the MDN reference table.
//
// License: display source — part of CosmoDev's polyglot tool pages.

//! HTTP request-method reference — pure data + lookups. The canonical
//! `methods` table holds the nine RFC 9110 / 9111 methods with their
//! semantic property flags. All helpers are read-only and return pointers
//! into that single source of truth.

const std = @import("std");

/// One HTTP request method and its semantic properties.
///
/// Fields use `[]const u8` so the canonical table can live as a comptime
/// constant with no allocation.
pub const MethodEntry = struct {
    /// Uppercase method name, e.g. "GET".
    method: []const u8,
    /// Read-only semantics — no server state change.
    safe: bool,
    /// Repeating the call has the same effect as a single call.
    idempotent: bool,
    /// Responses may be stored by a cache (RFC 9110 / MDN).
    cacheable: bool,
    /// The method conventionally carries a request body.
    has_body: bool,
    description: []const u8,
    typical_use: []const u8,
};

/// The nine HTTP request methods (RFC 9110 / 9111), in canonical order.
///
/// A `const` array — the single source of truth the helpers hand out
/// pointers into.
pub const methods = [_]MethodEntry{
    .{ .method = "GET", .safe = true, .idempotent = true, .cacheable = true, .has_body = false,
       .description = "Retrieves a representation of the target resource; a read-only request.",
       .typical_use = "Fetching a web page, reading an API resource, loading an image." },
    .{ .method = "POST", .safe = false, .idempotent = false, .cacheable = true, .has_body = true,
       .description = "Submits data to be processed, typically creating a new resource or triggering an action.",
       .typical_use = "Submitting a form, creating a record, publishing a message." },
    .{ .method = "PUT", .safe = false, .idempotent = true, .cacheable = false, .has_body = true,
       .description = "Replaces the target resource entirely with the request body.",
       .typical_use = "Updating a full record at a known URL, uploading a file by its path." },
    .{ .method = "PATCH", .safe = false, .idempotent = false, .cacheable = false, .has_body = true,
       .description = "Applies a partial modification to the target resource.",
       .typical_use = "Updating one field of a record, toggling a flag." },
    .{ .method = "DELETE", .safe = false, .idempotent = true, .cacheable = false, .has_body = false,
       .description = "Removes the target resource.",
       .typical_use = "Deleting a record or file by its URL." },
    .{ .method = "HEAD", .safe = true, .idempotent = true, .cacheable = true, .has_body = false,
       .description = "Identical to GET but returns only the response headers, no body.",
       .typical_use = "Checking existence, size, or freshness before downloading." },
    .{ .method = "OPTIONS", .safe = true, .idempotent = true, .cacheable = false, .has_body = false,
       .description = "Describes the communication options for the target resource.",
       .typical_use = "CORS preflight requests, discovering allowed methods." },
    .{ .method = "CONNECT", .safe = false, .idempotent = false, .cacheable = false, .has_body = false,
       .description = "Establishes a tunnel to the server (used with TLS/HTTPS proxies).",
       .typical_use = "Proxying encrypted connections through an intermediary." },
    .{ .method = "TRACE", .safe = true, .idempotent = true, .cacheable = false, .has_body = false,
       .description = "Performs a message loop-back test along the path to the target (debugging only).",
       .typical_use = "Diagnosing request transformations by intermediaries." },
};

/// Filter options. Each boolean is `?bool` so callers can distinguish
/// "constrain to `false`" from "no constraint" (`null`) — a tri-state a
/// bare `bool` cannot express.
pub const MethodFilter = struct {
    safe: ?bool = null,
    idempotent: ?bool = null,
    cacheable: ?bool = null,
    /// Free text matched case-insensitively against method, description,
    /// and typical_use. Ignored when empty after trimming.
    query: ?[]const u8 = null,
};

/// Case-insensitive single-method lookup.
///
/// Returns a pointer into the canonical table, or null when unknown — the
/// Zig analogue of TS's `MethodEntry | null`.
pub fn getMethod(name: []const u8) ?*const MethodEntry {
    // ASCII method names: eqlIgnoreCase matches TS toUpperCase for the
    // alphabetic tokens without paying for full Unicode case-folding.
    const n = std.mem.trim(u8, name, " \t\r\n");
    for (&methods) |*m| {
        if (std.ascii.eqlIgnoreCase(m.method, n)) {
            return m;
        }
    }
    return null;
}

/// Returns the methods satisfying every present flag and, when `query` is
/// non-empty after trimming, matching it in at least one of
/// {method, description, typical_use}.
///
/// `out` receives up to `out.len` pointers into the canonical table; the
/// return value is the total number of matches (which may exceed `out.len`
/// when the buffer is too small).
pub fn filterMethods(opts: MethodFilter, out: []*const MethodEntry) usize {
    const q = if (opts.query) |raw| std.mem.trim(u8, raw, " \t\r\n") else "";

    var total: usize = 0;
    for (&methods) |*m| {
        // Each present flag is an AND constraint; null means skip.
        if (opts.safe) |want| {
            if (m.safe != want) continue;
        }
        if (opts.idempotent) |want| {
            if (m.idempotent != want) continue;
        }
        if (opts.cacheable) |want| {
            if (m.cacheable != want) continue;
        }

        if (q.len > 0) {
            const matchesQuery = std.ascii.indexOfIgnoreCase(m.method, q) != null or
                std.ascii.indexOfIgnoreCase(m.description, q) != null or
                std.ascii.indexOfIgnoreCase(m.typical_use, q) != null;
            if (!matchesQuery) continue;
        }

        if (total < out.len) out[total] = m;
        total += 1;
    }
    return total;
}

/// Semantic diff between two methods.
pub const MethodComparison = struct {
    /// Both methods share the `safe` flag.
    same_safety: bool,
    /// Both methods share the `idempotent` flag.
    same_idempotence: bool,
    /// One sentence per mismatched property (safe, idempotent, cacheable,
    /// has_body); the first `difference_count` entries are set. Each
    /// sentence is allocated from the caller's allocator — free each slice
    /// when done.
    differences: [4][]const u8 = undefined,
    difference_count: usize = 0,
};

/// Surfaces where two methods' semantics agree and differ.
///
/// `differences` lists every mismatched property as a human-readable
/// sentence, reproducing the canonical phrasing verbatim so output stays
/// identical to the TypeScript original and the other polyglot ports.
pub fn compareMethods(
    allocator: std.mem.Allocator,
    a: *const MethodEntry,
    b: *const MethodEntry,
) std.mem.Allocator.Error!MethodComparison {
    var out = MethodComparison{
        .same_safety = a.safe == b.safe,
        .same_idempotence = a.idempotent == b.idempotent,
    };
    // Free the sentences already rendered if a later allocPrint fails.
    errdefer {
        for (out.differences[0..out.difference_count]) |sentence| {
            allocator.free(sentence);
        }
    }

    if (a.safe != b.safe) {
        out.differences[out.difference_count] = try std.fmt.allocPrint(
            allocator,
            "{s} is {s}, {s} is {s}.",
            .{ a.method, safeWord(a.safe), b.method, safeWord(b.safe) },
        );
        out.difference_count += 1;
    }
    if (a.idempotent != b.idempotent) {
        out.differences[out.difference_count] = try std.fmt.allocPrint(
            allocator,
            "{s} is {s}, {s} is {s}.",
            .{ a.method, idempotentWord(a.idempotent), b.method, idempotentWord(b.idempotent) },
        );
        out.difference_count += 1;
    }
    if (a.cacheable != b.cacheable) {
        out.differences[out.difference_count] = try std.fmt.allocPrint(
            allocator,
            "{s} is {s}, {s} is {s}.",
            .{ a.method, cacheableWord(a.cacheable), b.method, cacheableWord(b.cacheable) },
        );
        out.difference_count += 1;
    }
    if (a.has_body != b.has_body) {
        out.differences[out.difference_count] = try std.fmt.allocPrint(
            allocator,
            "{s} {s}, {s} {s}.",
            .{ a.method, bodyWord(a.has_body), b.method, bodyWord(b.has_body) },
        );
        out.difference_count += 1;
    }

    return out;
}

// Phrasing helpers keep the difference-sentence wording in one place, so the
// output stays in lock-step across every polyglot port.
fn safeWord(v: bool) []const u8 {
    return if (v) "safe" else "not safe";
}

fn idempotentWord(v: bool) []const u8 {
    return if (v) "idempotent" else "not idempotent";
}

fn cacheableWord(v: bool) []const u8 {
    return if (v) "cacheable" else "not cacheable";
}

fn bodyWord(v: bool) []const u8 {
    return if (v) "takes a body" else "does not take a body";
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →