more robust hello world handler, using helpers
All checks were successful
AWS-Zig Build / build-zig-0.11.0-amd64-host (push) Successful in 1m2s

This commit is contained in:
Emil Lerch 2023-10-20 23:08:23 -07:00
parent 389b6690b0
commit 5351480102
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 128 additions and 5 deletions

116
src/helpers.zig Normal file
View File

@ -0,0 +1,116 @@
//! This consists of helper functions to provide simple access using standard
//! patterns.
const std = @import("std");
const universal_lambda = @import("universal_lambda_handler");
const Option = struct {
short: []const u8,
long: []const u8,
};
const target_option: Option = .{ .short = "t", .long = "target" };
const header_option: Option = .{ .short = "h", .long = "header" };
/// Finds the "target" for this request. In a web request, this is the path
/// used for the request (e.g. "/" vs "/admin"). In a non-web environment,
/// this is determined by a command line option -t or --target. Note that
/// AWS API Gateway is not supported here...this is a configured thing in
/// API Gateway, and so is pretty situational. It also would be presented in
/// event data rather than context
pub fn findTarget(allocator: std.mem.Allocator, context: universal_lambda.Context) ![]const u8 {
switch (context) {
.web_request => |res| return res.request.target,
.flexilib => |ctx| return ctx.request.target,
.none => return findTargetWithoutContext(allocator),
}
}
fn findTargetWithoutContext(allocator: std.mem.Allocator) ![]const u8 {
// without context, we have environment variables (but for this, I think not),
// possibly event data (API Gateway does this if so configured),
// or the command line. For now we'll just look at the command line
var argIterator = try std.process.argsWithAllocator(allocator);
_ = argIterator.next();
var is_target_option = false;
while (argIterator.next()) |arg| {
if (is_target_option) {
if (std.mem.startsWith(u8, arg, "-") or
std.mem.startsWith(u8, arg, "--"))
{
// bad user input, but we're not returning errors here
return "/";
}
return arg;
}
if (std.mem.startsWith(u8, arg, "-" ++ target_option.short) or
std.mem.startsWith(u8, arg, "--" ++ target_option.long))
{
// We'll search for --target=blah style first
var split = std.mem.splitSequence(u8, arg, "=");
_ = split.next();
if (split.next()) |s| return s; // found it
is_target_option = true;
}
}
return "/";
}
pub fn getFirstHeaderValue(allocator: std.mem.Allocator, context: universal_lambda.Context, header_name: []const u8) !?[]const u8 {
switch (context) {
.web_request => |res| return res.request.headers.getFirstValue(header_name),
.flexilib => |ctx| {
for (ctx.request.headers) |hdr| {
if (std.ascii.eqlIgnoreCase(hdr.name_ptr[0..hdr.name_len], header_name)) {
return hdr.value_ptr[0..hdr.value_len];
}
}
return null;
},
.none => return findHeaderWithoutContext(allocator, header_name),
}
}
fn findHeaderWithoutContext(allocator: std.mem.Allocator, header_name: []const u8) !?[]const u8 {
// without context, we have environment variables
// possibly event data (API Gateway does this if so configured),
// or the command line. For headers, we'll prioritize command line options
// with a fallback to environment variables
var argIterator = try std.process.argsWithAllocator(allocator);
_ = argIterator.next();
var is_header_option = false;
while (argIterator.next()) |arg| {
if (is_header_option) {
if (std.mem.startsWith(u8, arg, "-") or
std.mem.startsWith(u8, arg, "--"))
{
return error.CommandLineError;
}
var split = std.mem.splitSequence(u8, arg, "=");
const name = split.next().?;
if (!std.ascii.eqlIgnoreCase(name, header_name)) continue;
if (split.next()) |s| return s; // found it
continue; // bad format, but we're not returning errors. We can cope with this one though
}
if (std.mem.startsWith(u8, arg, "-" ++ header_option.short) or
std.mem.startsWith(u8, arg, "--" ++ header_option.long))
{
// header option forms on command line:
// -h name=value
// --header name=value
is_header_option = true;
}
}
// not found on command line. Let's check environment
var map = try std.process.getEnvMap(allocator);
defer map.deinit();
var it = map.iterator();
while (it.next()) |kvp| {
// TODO: This is the only place where allocation is necessary. This
// will work, because in reality there is always an area allocator passed
// to us. But if there's not....
if (std.ascii.eqlIgnoreCase(kvp.key_ptr.*, header_name))
return try allocator.dupe(u8, kvp.value_ptr.*);
}
return null; // nowhere to be found
}

View File

@ -1,13 +1,20 @@
const std = @import("std"); const std = @import("std");
const universal_lambda = @import("universal_lambda_handler"); const universal_lambda = @import("universal_lambda_handler");
const helpers = @import("helpers.zig"); // not necessary, but these functions provide common access to common things
pub fn main() !void { pub fn main() !void {
try universal_lambda.run(null, handler); try universal_lambda.run(null, handler);
} }
pub fn handler(allocator: std.mem.Allocator, event_data: []const u8, context: universal_lambda.Context) ![]const u8 { pub fn handler(allocator: std.mem.Allocator, event_data: []const u8, context: universal_lambda.Context) ![]const u8 {
_ = event_data; // our allocator is an area, so yolo
_ = allocator; const target = try helpers.findTarget(allocator, context);
_ = context; var al = std.ArrayList(u8).init(allocator);
return "hello world"; var writer = al.writer();
var args = try std.process.argsWithAllocator(allocator);
while (args.next()) |arg| {
try writer.print("\tcalled with arg: {s}\n", .{arg});
}
try writer.print("(target: {s}) Event data, from you, to me, to you: {s}\n", .{ target, event_data });
try writer.print("Value for header 'Foo' is: {s}\n", .{try helpers.getFirstHeaderValue(allocator, context, "foo") orelse "undefined"});
return al.items;
} }