jetzig/src/jetzig.zig
Bob Farrell 31927cdb6b Static routes
Generate views defined with `request: *jetzig.http.StaticRequest` as
static content into `static/` directory so that exported JSON and HTML
can be rendered direct from disk, skipping runtime rendering.
2024-02-17 15:28:27 +00:00

188 lines
5.7 KiB
Zig

const std = @import("std");
pub const zmpl = @import("zmpl").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 const config = struct {
pub const max_bytes_request_body: usize = std.math.pow(usize, 2, 16);
pub const max_bytes_static_content: usize = std.math.pow(usize, 2, 16);
};
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 routes: anytype) []views.Route {
var size: usize = 0;
for (routes.dynamic) |_| {
size += 1;
}
for (routes.static) |_| {
size += 1;
}
var detected: [size]views.Route = undefined;
var index: usize = 0;
for (routes.dynamic) |dynamic_route| {
const view = views.Route.ViewType{
.dynamic = @unionInit(
views.Route.DynamicViewType,
dynamic_route.action,
dynamic_route.function,
),
};
detected[index] = .{
.name = dynamic_route.name,
.action = @field(views.Route.Action, dynamic_route.action),
.view = view,
.static = false,
.uri_path = dynamic_route.uri_path,
.template = dynamic_route.template,
};
index += 1;
}
for (routes.static) |static_route| {
const view = views.Route.ViewType{
.static = @unionInit(
views.Route.StaticViewType,
static_route.action,
static_route.function,
),
};
detected[index] = .{
.name = static_route.name,
.action = @field(views.Route.Action, static_route.action),
.view = view,
.static = true,
.uri_path = static_route.uri_path,
.template = static_route.template,
};
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.refAllDecls(@This());
}