aboutsummaryrefslogtreecommitdiff
path: root/src/routes/handler-info.zig
blob: bac7612850575999f3a8c5a555c6f9b1211e36d0 (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
const std = @import("std");
const Context = @import("context.zig");
const Storage = @import("../storage/root.zig");

const log = std.log.scoped(.handler_info);

handler: *const fn (*Context) anyerror![]const u8,
needs_auth: bool,
method: std.http.Method,

pub fn handle(
    self: *const @This(),
    request: *std.http.Server.Request,
    storage: *Storage,
    allocator: std.mem.Allocator,
) !void {
    if (request.head.method != self.method) {
        return request.respond(
            "{ \"error\": \"Bad Request\" }",
            .{ .status = .bad_request }
        );
    }

    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();
    var context: Context = .{
        .request = request,
        .storage = storage,
        .allocator = arena.allocator(),
    };

    const response = self.handler(&context) catch |err| {
        const response, const status_code: std.http.Status = switch (err) {
            error.BadRequest => .{ "{ \"error\": \"Bad Request\" }", .bad_request },
            error.Unauthorized => .{ "{ \"error\": \"Unauthorized\" }", .unauthorized },
            error.Forbidden => .{ "{ \"error\": \"Forbidden\" }", .forbidden },
            error.NotFound => .{ "{ \"error\": \"Not Found\" }", .not_found },
            else => blk: {
                log.err("handler for '{s}' returned {}", .{request.head.target, err});
                break :blk .{ "{ \"error\": \"Internal Server Error\" }", .internal_server_error };
            },
        };

        return request.respond(response, .{ .status = status_code });
    };

    try request.respond(response, .{
        .extra_headers = &.{
            .{
                .name = "Content-Type",
                .value = context.response.headers.content_type
            },
        },
    });
}

pub fn from_type(T: type) @This() {
    const info = @typeInfo(@TypeOf(T.handler));
    const return_type = info.@"fn".return_type orelse void;
    const payload_type = @typeInfo(return_type).error_union.payload;

    const Handler = type: {
            break :type struct {
                pub fn call(ctx: *Context) anyerror![]const u8 {
                    const args = args: {
                        const tuple = std.meta.fields(std.meta.ArgsTuple(@TypeOf(T.handler)));

                        if (tuple.len == 1) {
                            break :args .{ ctx };
                        } else if (tuple.len == 2) {
                            const Body = tuple[1].@"type";

                            var writer = std.Io.Writer.Allocating.init(ctx.allocator);
                            const interface = &writer.writer;

                            var buffer: [1024]u8 = undefined;
                            const reader = try ctx.request.readerExpectContinue(&buffer);

                            try reader.streamExact64(interface, ctx.request.head.content_length orelse 0);

                            const body = std.json.parseFromSliceLeaky(
                                Body,
                                ctx.allocator,
                                writer.written(),
                                .{}
                            ) catch return error.BadRequest;
                            break :args .{ ctx, body };
                        } else {
                            @compileError("invalid amount of arguments for request function");
                        }
                    };

                    if (payload_type == []const u8) {
                        return @call(.auto, T.handler, args);
                    } else if (payload_type == void) {
                        try @call(.auto, T.handler, args);
                        return "";
                    } else {
                        var writer = std.Io.Writer.Allocating.init(ctx.allocator);
                        var stringify = std.json.Stringify { .writer = &writer.writer };
                        try stringify.write(try @call(.auto, T.handler, args));
                        return writer.written();
                    }
                }
            };
    };

    return @This() {
        .handler = Handler.call,
        .needs_auth = T.needs_auth,
        .method = T.method,
    };
}