    const std = @import("std");
    const zap = @import("zap.zig");

    const Allocator = std.mem.Allocator;
    const RouterError = error{
        AlreadyExists,
        EmptyPath,
    };

    const Self = @This();

    pub const Options = struct {
        not_found: ?zap.HttpRequestFn = null,
    };

    routes: std.StringHashMap(zap.HttpRequestFn),
    not_found: ?zap.HttpRequestFn,

    pub fn init(allocator: Allocator, options: Options) Self {
        return .{
            .routes = std.StringHashMap(zap.HttpRequestFn).init(allocator),

            .not_found = options.not_found,
        };
    }

    pub fn deinit(self: *Self) void {
        self.routes.deinit();
    }

    pub fn handle_func(self: *Self, path: []const u8, h: zap.HttpRequestFn) !void {
        if (path.len == 0) {
            return RouterError.EmptyPath;
        }

        const route = self.routes.get(path);

        if (route != null) {
            return RouterError.AlreadyExists;
        }

        try self.routes.put(path, h);
    }

    pub fn serve(self: *Self, r: zap.Request) void {
        const path = if (r.path) |p| p else "/";

        const route = self.routes.get(path);

        if (route) |handler| {
            handler(r);
        } else if (self.not_found) |handler| {
            // not found handler

            handler(r);
        } else {
            // default 404 output

            r.setStatus(.not_found);
            r.sendBody("404 Not Found") catch return;
        }
    }