Controller helper refactor
This commit is contained in:
parent
76b9018297
commit
85f57df0cb
1 changed files with 86 additions and 97 deletions
|
@ -58,6 +58,41 @@ const routes = .{
|
|||
web.index,
|
||||
};
|
||||
|
||||
fn parseRouteArgs(comptime route: []const u8, comptime Args: type, path: []const u8) !Args {
|
||||
var args: Args = undefined;
|
||||
var path_iter = util.PathIter.from(path);
|
||||
comptime var route_iter = util.PathIter.from(route);
|
||||
inline while (comptime route_iter.next()) |route_segment| {
|
||||
const path_segment = path_iter.next() orelse return error.RouteMismatch;
|
||||
if (route_segment.len > 0 and route_segment[0] == ':') {
|
||||
const A = @TypeOf(@field(args, route_segment[1..]));
|
||||
@field(args, route_segment[1..]) = try parseRouteArg(A, path_segment);
|
||||
} else {
|
||||
if (!std.ascii.eqlIgnoreCase(route_segment, path_segment)) return error.RouteMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
if (path_iter.next() != null) return error.RouteMismatch;
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
fn parseRouteArg(comptime T: type, segment: []const u8) !T {
|
||||
if (T == []const u8) return segment;
|
||||
if (comptime std.meta.trait.isContainer(T) and std.meta.trait.hasFn("parse")(T)) return T.parse(segment);
|
||||
|
||||
@compileError("Unsupported Type " ++ @typeName(T));
|
||||
}
|
||||
|
||||
fn parseBody(comptime T: type, reader: anytype, alloc: std.mem.Allocator) !T {
|
||||
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
||||
defer alloc.free(buf);
|
||||
const body = try json_utils.parse(T, buf, alloc);
|
||||
defer json_utils.parseFree(body, alloc);
|
||||
|
||||
return try util.deepClone(alloc, body);
|
||||
}
|
||||
|
||||
pub fn Context(comptime Route: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
@ -87,38 +122,58 @@ pub fn Context(comptime Route: type) type {
|
|||
// TODO
|
||||
body_buf: ?[]const u8 = null,
|
||||
|
||||
fn parseArgs(path: []const u8) ?Args {
|
||||
var args: Args = undefined;
|
||||
var path_iter = util.PathIter.from(path);
|
||||
comptime var route_iter = util.PathIter.from(Route.path);
|
||||
inline while (comptime route_iter.next()) |route_segment| {
|
||||
const path_segment = path_iter.next() orelse return null;
|
||||
if (route_segment.len > 0 and route_segment[0] == ':') {
|
||||
const A = @TypeOf(@field(args, route_segment[1..]));
|
||||
@field(args, route_segment[1..]) = parseArg(A, path_segment) catch return null;
|
||||
} else {
|
||||
if (!std.ascii.eqlIgnoreCase(route_segment, path_segment)) return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (path_iter.next() != null) return null;
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
fn parseArg(comptime T: type, segment: []const u8) !T {
|
||||
if (T == []const u8) return segment;
|
||||
if (comptime std.meta.trait.hasFn("parse")(T)) return T.parse(segment);
|
||||
|
||||
@compileError("Unsupported Type " ++ @typeName(T));
|
||||
}
|
||||
|
||||
pub fn matchAndHandle(api_source: *api.ApiSource, req: *http.Request, res: *Response, alloc: std.mem.Allocator) bool {
|
||||
if (req.method != Route.method) return false;
|
||||
var path = std.mem.sliceTo(std.mem.sliceTo(req.uri, '#'), '?');
|
||||
var args: Args = parseArgs(path) orelse return false;
|
||||
var args = parseRouteArgs(Route.path, Args, path) catch return false;
|
||||
|
||||
var self = Self{
|
||||
std.log.debug("Matched route {s}", .{Route.path});
|
||||
|
||||
handle(api_source, req, res, alloc, args) catch |err| {
|
||||
std.log.err("{}", .{err});
|
||||
if (!res.opened) res.err(.internal_server_error, "", {}) catch {};
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn handle(
|
||||
api_source: *api.ApiSource,
|
||||
req: *http.Request,
|
||||
res: *Response,
|
||||
alloc: std.mem.Allocator,
|
||||
args: Args,
|
||||
) !void {
|
||||
const body = if (Body != void) blk: {
|
||||
var stream = req.body orelse return error.NoBody;
|
||||
break :blk try parseBody(Body, stream.reader(), alloc);
|
||||
} else {};
|
||||
defer if (Body != void) util.deepFree(alloc, body);
|
||||
|
||||
const query = if (Query != void) blk: {
|
||||
const path = std.mem.sliceTo(req.uri, '?');
|
||||
const q = req.uri[path.len..];
|
||||
|
||||
break :blk try query_utils.parseQuery(Query, q);
|
||||
};
|
||||
|
||||
var api_conn = conn: {
|
||||
const host = req.headers.get("Host") orelse return error.NoHost;
|
||||
const auth_header = req.headers.get("Authorization");
|
||||
const token = if (auth_header) |header| blk: {
|
||||
const prefix = "bearer ";
|
||||
if (header.len < prefix.len) break :blk null;
|
||||
if (!std.ascii.eqlIgnoreCase(prefix, header[0..prefix.len])) break :blk null;
|
||||
break :blk header[prefix.len..];
|
||||
} else null;
|
||||
|
||||
if (token) |t| break :conn try api_source.connectToken(host, t, alloc);
|
||||
|
||||
break :conn try api_source.connectUnauthorized(host, alloc);
|
||||
};
|
||||
defer api_conn.close();
|
||||
|
||||
const self = Self{
|
||||
.allocator = alloc,
|
||||
.base_request = req,
|
||||
|
||||
|
@ -127,15 +182,11 @@ pub fn Context(comptime Route: type) type {
|
|||
.headers = req.headers,
|
||||
|
||||
.args = args,
|
||||
.body = undefined,
|
||||
.query = undefined,
|
||||
.body = body,
|
||||
.query = query,
|
||||
};
|
||||
|
||||
std.log.debug("Matched route {s}", .{path});
|
||||
|
||||
self.prepareAndHandle(api_source, req, res);
|
||||
|
||||
return true;
|
||||
try Route.handler(self, res, &api_conn);
|
||||
}
|
||||
|
||||
fn errorHandler(response: *Response, status: http.Status, err: anytype) void {
|
||||
|
@ -149,68 +200,6 @@ pub fn Context(comptime Route: type) type {
|
|||
std.log.err("Error printing response: {}", .{err2});
|
||||
};
|
||||
}
|
||||
|
||||
fn prepareAndHandle(self: *Self, api_source: anytype, req: *http.Request, response: *Response) void {
|
||||
self.parseBody(req) catch |err| return errorHandler(response, .bad_request, err);
|
||||
defer self.freeBody();
|
||||
|
||||
self.parseQuery() catch |err| return errorHandler(response, .bad_request, err);
|
||||
|
||||
var api_conn = self.getApiConn(api_source) catch |err| return errorHandler(response, .internal_server_error, err);
|
||||
defer api_conn.close();
|
||||
|
||||
self.handle(response, &api_conn);
|
||||
}
|
||||
|
||||
fn parseBody(self: *Self, req: *http.Request) !void {
|
||||
if (Body != void) {
|
||||
var stream = req.body orelse return error.NoBody;
|
||||
const body = try stream.reader().readAllAlloc(self.allocator, 1 << 16);
|
||||
errdefer self.allocator.free(body);
|
||||
self.body = try json_utils.parse(Body, body, self.allocator);
|
||||
self.body_buf = body;
|
||||
}
|
||||
}
|
||||
|
||||
fn freeBody(self: *Self) void {
|
||||
if (Body != void) {
|
||||
json_utils.parseFree(self.body, self.allocator);
|
||||
self.allocator.free(self.body_buf.?);
|
||||
}
|
||||
}
|
||||
|
||||
fn parseQuery(self: *Self) !void {
|
||||
if (Query != void) {
|
||||
const path = std.mem.sliceTo(self.uri, '?');
|
||||
const q = std.mem.sliceTo(self.uri[path.len..], '#');
|
||||
|
||||
self.query = try query_utils.parseQuery(Query, q);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(self: Self, response: *Response, api_conn: anytype) void {
|
||||
Route.handler(self, response, api_conn) catch |err| switch (err) {
|
||||
else => {
|
||||
std.log.err("{}", .{err});
|
||||
if (!response.opened) response.err(.internal_server_error, "", {}) catch {};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn getApiConn(self: *Self, api_source: anytype) !api.ApiSource.Conn {
|
||||
const host = self.headers.get("Host") orelse return error.NoHost;
|
||||
const auth_header = self.headers.get("Authorization");
|
||||
const token = if (auth_header) |header| blk: {
|
||||
const prefix = "bearer ";
|
||||
if (header.len < prefix.len) break :blk null;
|
||||
if (!std.ascii.eqlIgnoreCase(prefix, header[0..prefix.len])) break :blk null;
|
||||
break :blk header[prefix.len..];
|
||||
} else null;
|
||||
|
||||
if (token) |t| return try api_source.connectToken(host, t, self.allocator);
|
||||
|
||||
return try api_source.connectUnauthorized(host, self.allocator);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue