jetzig/src/jetzig.zig
2024-01-18 21:05:17 +00:00

162 lines
5.1 KiB
Zig

const std = @import("std");
pub const zmpl = @import("zmpl");
pub const http = @import("jetzig/http.zig");
pub const loggers = @import("jetzig/loggers.zig");
pub const data = @import("jetzig/data.zig");
pub const caches = @import("jetzig/caches.zig");
pub const views = @import("jetzig/views.zig");
pub const colors = @import("jetzig/colors.zig");
pub const App = @import("jetzig/App.zig");
pub fn init(allocator: std.mem.Allocator) !App {
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
const host: []const u8 = if (args.len > 1)
try allocator.dupe(u8, args[1])
else
try allocator.dupe(u8, "127.0.0.1");
// TODO: Fix this up with proper arg parsing
const port: u16 = if (args.len > 2) try std.fmt.parseInt(u16, args[2], 10) else 8080;
const use_cache: bool = args.len > 3 and std.mem.eql(u8, args[3], "--cache");
const server_cache = switch (use_cache) {
true => caches.Cache{ .memory_cache = caches.MemoryCache.init(allocator) },
false => caches.Cache{ .null_cache = caches.NullCache.init(allocator) },
};
const root_path = std.fs.cwd().realpathAlloc(allocator, "src/app") catch |err| {
switch (err) {
error.FileNotFound => {
std.debug.print("Unable to find base directory: ./app\nExiting.\n", .{});
std.os.exit(1);
},
else => return err,
}
};
var logger = loggers.Logger{ .development_logger = loggers.DevelopmentLogger.init(allocator) };
const secret = try generateSecret(allocator);
logger.debug(
"Running in development mode, using auto-generated cookie encryption key:\n {s}",
.{secret},
);
const server_options = http.Server.ServerOptions{
.cache = server_cache,
.logger = logger,
.root_path = root_path,
.secret = secret,
};
return .{
.server_options = server_options,
.allocator = allocator,
.host = host,
.port = port,
.root_path = root_path,
};
}
// Receives an array of imported modules and detects functions defined on them.
// Each detected function is stored as a Route which can be accessed at runtime to route requests
// to the appropriate View.
pub fn route(comptime modules: anytype) []views.Route {
var size: usize = 0;
for (modules) |module| {
const decls = @typeInfo(module).Struct.decls;
for (decls) |decl| {
if (@hasField(views.Route.ViewType, decl.name)) size += 1;
}
}
var detected: [size]views.Route = undefined;
var index: usize = 0;
for (modules) |module| {
const decls = @typeInfo(module).Struct.decls;
for (decls) |decl| {
if (!@hasField(views.Route.ViewType, decl.name)) {
// TODO: Figure out how to log a warning here (comptime issues).
continue;
}
const view = @unionInit(views.Route.ViewType, decl.name, @field(module, decl.name));
detected[index] = .{
.name = @typeName(module),
.action = @field(views.Route.Action, decl.name),
.view = view,
};
index += 1;
}
}
return &detected;
}
// Receives a type (an imported module). All pub const declarations are considered as compiled
// Zmpl templates, each implementing a `render` function.
pub fn loadTemplates(comptime module: type) []TemplateFn {
var size: u16 = 0;
const decls = @typeInfo(module).Struct.decls;
for (decls) |_| size += 1;
var detected: [size]TemplateFn = undefined;
for (decls, 0..) |decl, decl_index| {
detected[decl_index] = .{
.render = @field(module, decl.name).render,
.name = decl.name,
};
}
return &detected;
}
pub const TemplateFn = struct {
name: []const u8,
render: *const fn (*zmpl.Data) anyerror![]const u8,
};
pub fn generateSecret(allocator: std.mem.Allocator) ![]const u8 {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var secret: [64]u8 = undefined;
for (0..64) |index| {
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
}
return try allocator.dupe(u8, &secret);
}
pub fn base64Encode(allocator: std.mem.Allocator, string: []const u8) ![]const u8 {
const encoder = std.base64.Base64Encoder.init(
std.base64.url_safe_no_pad.alphabet_chars,
std.base64.url_safe_no_pad.pad_char,
);
const size = encoder.calcSize(string.len);
const ptr = try allocator.alloc(u8, size);
_ = encoder.encode(ptr, string);
return ptr;
}
pub fn base64Decode(allocator: std.mem.Allocator, string: []const u8) ![]const u8 {
const decoder = std.base64.Base64Decoder.init(
std.base64.url_safe_no_pad.alphabet_chars,
std.base64.url_safe_no_pad.pad_char,
);
const size = try decoder.calcSizeForSlice(string);
const ptr = try allocator.alloc(u8, size);
try decoder.decode(ptr, string);
return ptr;
}
test {
@import("std").testing.refAllDeclsRecursive(@This());
}