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