aboutsummaryrefslogtreecommitdiff
path: root/src/http/handler.zig
blob: 2220fd025e40faacc02c3885de24419c3c4535ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
const std = @import("std");

const Response = @import("Response.zig");
const Route = @import("Route.zig");
const Parameter = @import("Parameter.zig");

pub fn Context(comptime r: anytype) type {
    return struct {
        const route = Route.fromAny(r);
        const Params = Parameter.Struct(route);

        const Request = struct {
            base_request: *std.http.Server.Request,
            params: Params,
            arena: std.mem.Allocator,
            gpa: std.mem.Allocator,
            io: std.Io,

            /// Parses the body of the request and returns type `T`.
            /// The arena allocator will be used to do the parsing.
            pub fn json(self: @This(), T: type) !T {
                var buffer: [1024]u8 = undefined;
                var reader = std.json.Reader.init(self.arena, self.base_request.readerExpectNone(&buffer));
                return std.json.parseFromTokenSourceLeaky(T, self.arena, &reader, .{});
            }
        };

        const badRequest = struct {
            pub fn badRequest(_: Request) Response {
                return .with(.bad_request, .string("method does not exist"));
            }
        }.badRequest;

        get: *const fn (Request) Response = badRequest,
        post: *const fn (Request) Response = badRequest,
        delete: *const fn (Request) Response = badRequest,
        head: *const fn (Request) Response = badRequest,
        put: *const fn (Request) Response = badRequest,
        connect: *const fn (Request) Response = badRequest,
        options: *const fn (Request) Response = badRequest,
        trace: *const fn (Request) Response = badRequest,
        patch: *const fn (Request) Response = badRequest,

        pub fn handle(
            self: *const @This(),
            gpa: std.mem.Allocator,
            arena: std.mem.Allocator,
            io: std.Io,
            request: *std.http.Server.Request,
        ) Response {
            const req: Request = .{
                .base_request = request,
                .arena = arena,
                .gpa = gpa,
                .io = io,
                .params = Parameter.parse(Params, &route, request.head.target),
            };

            return (switch (request.head.method) {
                .GET => self.get,
                .POST => self.post,
                .DELETE => self.delete,
                .HEAD => self.head,
                .PUT => self.put,
                .CONNECT => self.connect,
                .OPTIONS => self.options,
                .TRACE => self.trace,
                .PATCH => self.patch,
            })(req);
        }

        pub fn interface(self: *const @This()) Interface {
            return .from(self, route);
        }
    };
}

pub const Interface = struct {
    route: Route,
    base: *const anyopaque,
    handler: *const fn (
        *const anyopaque,
        std.mem.Allocator,
        std.mem.Allocator,
        std.Io,
        *std.http.Server.Request,
    ) Response,

    pub fn from(instance: anytype, route: Route) @This() {
        const PtrT = @TypeOf(instance);
        const func = struct {
            pub fn handle(
                base: *const anyopaque,
                gpa: std.mem.Allocator,
                arena: std.mem.Allocator,
                io: std.Io,
                request: *std.http.Server.Request,
            ) Response {
                return @as(PtrT, @ptrCast(@alignCast(base))).handle(gpa, arena, io, request);
            }
        }.handle;

        return .{
            .base = @ptrCast(instance),
            .handler = func,
            .route = route,
        };
    }

    pub fn handle(
        self: *const @This(),
        gpa: std.mem.Allocator,
        arena: std.mem.Allocator,
        io: std.Io,
        request: std.http.Server.Request,
    ) Response {
        return self.handler(self.base, gpa, arena, io, request);
    }
};

test "interface" {
    const M = struct {
        const C = Context(.{ "api", "images", .id, "metadata" });
        const context: C = .{
            .post = post,
            .get = get,
        };

        const Query = struct {
            condition: []const u8,
            start: usize,
            length: usize,
        };

        fn post(request: C.Request) Response {
            const query = request.json(Query) catch return .with(.bad_request, .void);
            _ = query;
            _ = request.params.id;

            return .ok(.string(request.params.id.as([]const u8)));
        }

        fn get(request: C.Request) Response {
            return .ok(Response.Message.file(request.io, "./some/path.txt") catch return .with(.not_found, .void));
        }
    };

    _ = M.context.interface();
}