FlexiLib/src/main-lib.zig
2024-05-02 14:38:04 -07:00

222 lines
9.2 KiB
Zig

const std = @import("std");
const interface = @import("interface.zig");
const testing = std.testing;
const log = std.log.scoped(.@"main-lib");
// The main program will look for exports during the request lifecycle:
// zigInit (optional): called at the beginning of a request, includes pointer to an allocator
// handle_request: called with request data, expects response data
// request_deinit (optional): called at the end of a request to allow resource cleanup
//
// Setup for these is aided by the interface library as shown below
// zigInit is an optional export called at the beginning of a request. It will
// be passed an allocator (which...shh...is an arena allocator). Since the
// interface library provides a request handler that requires a built-in allocator,
// if you are using the interface's handleRequest function as shown above,
// you will need to also include this export. To customize, just do something
// like this:
//
// export fn zigInit(parent_allocator: *anyopaque) callconv(.C) void {
// // your code here, just include the next line
// interface.zigInit(parent_allocator);
// }
//
comptime {
@export(interface.zigInit, .{ .name = "zigInit", .linkage = .strong });
}
/// handle_request will be called on a single request, but due to the preservation
/// of restrictions imposed by the calling interface, it should generally be more
/// useful to call into the interface library to let it do the conversion work
/// on your behalf
export fn handle_request(request: *interface.Request) callconv(.C) ?*interface.Response {
// The interface library provides a handleRequest function that will handle
// marshalling data back and forth from the C format used for the interface
// to a more Zig friendly format. It also allows usage of zig errors. To
// use, pass in the request and the zig function used to handle the request
// (here called "handleRequest"). The function signature must be:
//
// fn (std.mem.Allocator, interface.ZigRequest, interface.ZigResponse) !void
//
return interface.handleRequest(request, handleRequest);
}
// request_deinit is an optional export and will be called a the end of the
// request. Useful for deallocating memory. Since this is zig code and the
// allocator used is an arena allocator, all allocated memory will be automatically
// cleaned up by the main program at the end of a request
//
// export fn request_deinit() void {
// }
// ************************************************************************
// Boilerplate ^^, Custom code vv
// ************************************************************************
//
// handleRequest function here is the last line of boilerplate and the
// entry to a request
fn handleRequest(allocator: std.mem.Allocator, response: *interface.ZigResponse) !void {
// setup
var response_writer = response.body.writer();
// real work
for (response.request.headers) |h| {
if (std.ascii.eqlIgnoreCase(h.name, "host")) {
if (std.mem.startsWith(u8, h.value, "iam")) {
try response_writer.print("iam response", .{});
return;
}
break;
}
}
for (response.request.headers) |h| {
if (std.ascii.eqlIgnoreCase(h.name, "x-slow")) {
std.time.sleep(std.time.ns_per_ms * (std.fmt.parseInt(usize, h.value, 10) catch 1000));
try response_writer.print("i am slow\n\n", .{});
return;
}
}
for (response.request.headers) |h| {
if (std.ascii.eqlIgnoreCase(h.name, "x-log-this")) {
try response.writeAll(h.value);
break;
}
}
for (response.request.headers) |h| {
if (std.ascii.eqlIgnoreCase(h.name, "x-status")) {
response.status = @enumFromInt(std.fmt.parseInt(u10, h.value, 10) catch 500);
break;
}
}
for (response.request.headers) |h| {
if (std.ascii.eqlIgnoreCase(h.name, "x-throw"))
return error.Thrown;
}
try response_writer.print(" {d}", .{response.request.headers.len});
var headers = std.ArrayList(std.http.Header).init(allocator);
try headers.appendSlice(response.headers);
try headers.append(.{ .name = "X-custom-foo", .value = "bar" });
response.headers = try headers.toOwnedSlice();
}
test "handle_request" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var aa = arena.allocator();
interface.zigInit(&aa);
const headers: []interface.Header = @constCast(&[_]interface.Header{.{
.name_ptr = @ptrCast(@constCast("GET".ptr)),
.name_len = 3,
.value_ptr = @ptrCast(@constCast("GET".ptr)),
.value_len = 3,
}});
var req = interface.Request{
.target = @ptrCast(@constCast("/".ptr)),
.target_len = 1,
.method = @ptrCast(@constCast("GET".ptr)),
.method_len = 3,
.content = @ptrCast(@constCast("GET".ptr)),
.content_len = 3,
.headers = headers.ptr,
.headers_len = headers.len,
};
const response = handle_request(&req).?;
try testing.expectEqualStrings(" 1", response.ptr[0..response.len]);
try testing.expectEqual(@as(usize, 1), response.headers_len);
try testing.expectEqualStrings("X-custom-foo", response.headers[0].name_ptr[0..response.headers[0].name_len]);
try testing.expectEqualStrings("bar", response.headers[0].value_ptr[0..response.headers[0].value_len]);
}
test "lib can write data directly" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var aa = arena.allocator();
interface.zigInit(&aa);
const headers: []interface.Header = @constCast(&[_]interface.Header{.{
.name_ptr = @ptrCast(@constCast("x-log-this".ptr)),
.name_len = "x-log-this".len,
.value_ptr = @ptrCast(@constCast("I am a teapot".ptr)),
.value_len = "I am a teapot".len,
}});
var req = interface.Request{
.target = @ptrCast(@constCast("/".ptr)),
.target_len = 1,
.method = @ptrCast(@constCast("GET".ptr)),
.method_len = 3,
.content = @ptrCast(@constCast("GET".ptr)),
.content_len = 3,
.headers = headers.ptr,
.headers_len = headers.len,
};
const response = handle_request(&req).?;
try testing.expectEqual(@as(usize, 1), response.headers_len);
try testing.expectEqualStrings("X-custom-foo", response.headers[0].name_ptr[0..response.headers[0].name_len]);
try testing.expectEqualStrings("bar", response.headers[0].value_ptr[0..response.headers[0].value_len]);
try testing.expectEqualStrings("I am a teapot 1", response.ptr[0..response.len]);
}
test "lib can write data directly and still throw" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var aa = arena.allocator();
interface.zigInit(&aa);
const headers: []interface.Header = @constCast(&[_]interface.Header{ .{
.name_ptr = @ptrCast(@constCast("x-log-this".ptr)),
.name_len = "x-log-this".len,
.value_ptr = @ptrCast(@constCast("I am a teapot".ptr)),
.value_len = "I am a teapot".len,
}, .{
.name_ptr = @ptrCast(@constCast("x-throw".ptr)),
.name_len = "x-throw".len,
.value_ptr = @ptrCast(@constCast("I am a teapot".ptr)),
.value_len = "I am a teapot".len,
} });
var req = interface.Request{
.target = @ptrCast(@constCast("/".ptr)),
.target_len = 1,
.method = @ptrCast(@constCast("GET".ptr)),
.method_len = 3,
.content = @ptrCast(@constCast("GET".ptr)),
.content_len = 3,
.headers = headers.ptr,
.headers_len = headers.len,
};
const response = handle_request(&req).?;
try testing.expectEqual(@as(usize, 500), response.status);
try testing.expectEqualStrings("I am a teapot", response.ptr[0..response.len]);
}
test "lib can set status, update data directly and still throw" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var aa = arena.allocator();
interface.zigInit(&aa);
const headers: []interface.Header = @constCast(&[_]interface.Header{ .{
.name_ptr = @ptrCast(@constCast("x-log-this".ptr)),
.name_len = "x-log-this".len,
.value_ptr = @ptrCast(@constCast("I am a teapot".ptr)),
.value_len = "I am a teapot".len,
}, .{
.name_ptr = @ptrCast(@constCast("x-throw".ptr)),
.name_len = "x-throw".len,
.value_ptr = @ptrCast(@constCast("I am a teapot".ptr)),
.value_len = "I am a teapot".len,
}, .{
.name_ptr = @ptrCast(@constCast("x-status".ptr)),
.name_len = "x-status".len,
.value_ptr = @ptrCast(@constCast("418".ptr)),
.value_len = "418".len,
} });
var req = interface.Request{
.target = @ptrCast(@constCast("/".ptr)),
.target_len = 1,
.method = @ptrCast(@constCast("GET".ptr)),
.method_len = 3,
.content = @ptrCast(@constCast("GET".ptr)),
.content_len = 3,
.headers = headers.ptr,
.headers_len = headers.len,
};
const response = handle_request(&req).?;
try testing.expectEqual(@as(usize, 418), response.status);
try testing.expectEqualStrings("I am a teapot", response.ptr[0..response.len]);
}