Compare commits

..

19 Commits

Author SHA1 Message Date
Bob Farrell
1feb18fb74 Add note to README about Zig compatibility 2025-04-26 18:14:50 +01:00
Bob Farrell
86d82026ab Fix incorrect email address format 2025-04-21 14:46:53 +01:00
bobf
01d0862043
Merge pull request #190 from PotatoesMaster/htmx-middleware-vary-header
HtmxMiddleware: add Vary header to prevent caching issues
2025-04-20 15:41:26 +01:00
Emanuel Guével
95d3f10bc9 HtmxMiddleware: add Vary header to prevent caching issues
See https://htmx.org/docs/#caching
2025-04-20 16:06:55 +02:00
Bob Farrell
dee5701b4a Never sync stdout/stderr
Detecting if stdout/stderr is tty seems to cause issues when running in
Docker (in particular with fly.io)
2025-04-19 21:41:27 +01:00
Bob Farrell
b7c3c0045a Fix missing @tagName after nameCast changes 2025-04-19 20:16:31 +01:00
bobf
6d9fa8bff4
Merge pull request #189 from jetzig-framework/zig-alignment-changes
Use Alignment arg instead of integer
2025-04-19 19:57:34 +01:00
Bob Farrell
4bd9d8b990 Eradicate std.enums.nameCast
Deprecated here: 530228d953
2025-04-19 19:35:55 +01:00
bobf
8171ab5b5d
Merge pull request #188 from uzyn/custom-session-name
Overriding of default session cookie name
2025-04-17 07:01:16 +01:00
Bob Farrell
13907aad7c Use Alignment arg instead of integer
See f32a5d349d
2025-04-16 19:59:05 +01:00
Bob Farrell
aae9fd182b Use Server.env.vars to get session cookie name 2025-04-16 19:29:44 +01:00
U-Zyn Chua
4c568f2606
Minor typo fix 2025-04-16 15:59:45 +08:00
U-Zyn Chua
94cf122847
Allow setting of custom session cookie name. 2025-04-16 15:59:18 +08:00
bobf
b32d24ad80
Merge pull request #187 from uzyn/ci-use-minver
CI now uses minimum_zig_version as defined in build.zig.zon
2025-04-16 06:09:42 +01:00
U-Zyn Chua
3fd42f2ebb
CI now uses minimum_zig_version as defined in build.zig.zon 2025-04-15 17:15:29 +08:00
bobf
ace82985ef
Merge pull request #186 from uzyn/min-zig-ver
Defines minimum supported Zig version.
2025-04-15 08:36:57 +01:00
U-Zyn Chua
e96a3d36b6
Defines specific version at CI. 2025-04-15 15:10:46 +08:00
U-Zyn Chua
dc982cf125
Defines minimum supported zig version. 2025-04-15 14:52:47 +08:00
bobf
6d522c07c5
Merge pull request #185 from jetzig-framework/zig-0.15
Zig 0.15 compatibility
2025-04-09 20:15:03 +01:00
23 changed files with 157 additions and 74 deletions

View File

@ -28,9 +28,7 @@ jobs:
submodules: true
- name: Setup Zig
uses: mlugg/setup-zig@v1
with:
version: master
uses: mlugg/setup-zig@main
- run: zig version
- run: zig env

View File

@ -6,6 +6,8 @@ _Jetzig_ is a web framework written in 100% pure [Zig](https://ziglang.org) :liz
Official website: [jetzig.dev](https://www.jetzig.dev/)
Please note that _Jetzig_'s `main` branch aims to be compatible with the latest [Zig nightly master build](https://ziglang.org/download/) and older versions of _Zig_ are not supported.
_Jetzig_ aims to provide a rich set of user-friendly tools for building modern web applications quickly. See the checklist below.
Join us on Discord ! [https://discord.gg/eufqssz7X6](https://discord.gg/eufqssz7X6).

View File

@ -447,7 +447,7 @@ fn generateMarkdownFragments(b: *std.Build) ![]const u8 {
}
};
const stat = try file.stat();
const source = try file.readToEndAllocOptions(b.allocator, @intCast(stat.size), null, @alignOf(u8), 0);
const source = try file.readToEndAllocOptions(b.allocator, @intCast(stat.size), null, .of(u8), 0);
if (try getMarkdownFragmentsSource(b.allocator, source)) |markdown_fragments_source| {
return try std.fmt.allocPrint(b.allocator,
\\const std = @import("std");

View File

@ -2,6 +2,7 @@
.name = .jetzig,
.version = "0.0.0",
.fingerprint = 0x93ad8bfa2d209022,
.minimum_zig_version = "0.15.0-dev.355+206bd1ced",
.dependencies = .{
.jetcommon = .{
.url = "https://github.com/jetzig-framework/jetcommon/archive/fb4edc13759d87bfcd9b1f5fcefdf93f8c9c62dd.tar.gz",

View File

@ -7,4 +7,4 @@
<input type="submit" value="Submit Spam" />
</form>
<div>Try clearing `_jetzig_session` cookie before clicking "Submit Spam"</div>
<div>Try clearing `_jetzig-session` cookie before clicking "Submit Spam"</div>

View File

@ -376,7 +376,7 @@ fn generateRoutesForView(self: *Routes, dir: std.fs.Dir, path: []const u8) !Rout
path,
@intCast(stat.size),
null,
@alignOf(u8),
.of(u8),
0,
);
defer self.allocator.free(source);

View File

@ -33,7 +33,7 @@ pub fn main() !void {
const Repo = jetzig.jetquery.Repo(jetzig.database.adapter, Schema);
var repo = try Repo.loadConfig(
allocator,
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
@field(jetzig.jetquery.Environment, @tagName(jetzig.environment)),
.{ .env = try jetzig.database.repoEnv(env), .context = .cli },
);
defer repo.deinit();
@ -60,7 +60,7 @@ pub fn main() !void {
const email = args[2];
try repo.insert(std.enums.nameCast(std.meta.DeclEnum(Schema), model), .{
try repo.insert(@field(std.meta.DeclEnum(Schema), model), .{
.email = email,
.password_hash = try hashPassword(allocator, password),
});

View File

@ -133,7 +133,7 @@ const MigrationsRepo = jetquery.Repo(config.adapter, MigrateSchema);
fn migrationsRepo(action: Action, allocator: std.mem.Allocator, repo_env: anytype) !MigrationsRepo {
return try MigrationsRepo.loadConfig(
allocator,
std.enums.nameCast(jetquery.Environment, environment),
@field(jetquery.Environment, @tagName(environment)),
.{
.admin = switch (action) {
.migrate, .rollback, .update => false,
@ -154,7 +154,7 @@ fn reflectSchema(allocator: std.mem.Allocator, repo_env: anytype) !void {
const Repo = jetquery.Repo(config.adapter, Schema);
var repo = try Repo.loadConfig(
allocator,
std.enums.nameCast(jetquery.Environment, environment),
@field(jetquery.Environment, @tagName(environment)),
.{ .context = .migration, .env = repo_env },
);
const reflect = @import("jetquery_reflect").Reflect(config.adapter, Schema).init(

View File

@ -33,7 +33,10 @@ pub const Date = jetcommon.types.Date;
pub const authenticity_token_name = config.get([]const u8, "authenticity_token_name");
pub const build_options = @import("build_options");
pub const environment = std.enums.nameCast(Environment.EnvironmentName, build_options.environment);
pub const environment = @field(
Environment.EnvironmentName,
@tagName(build_options.environment),
);
/// The primary interface for a Jetzig application. Create an `App` in your application's
/// `src/main.zig` and call `start` to launch the application.

View File

@ -196,15 +196,15 @@ pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions
const env_file = std.fs.cwd().openFile(options.options.@"env-file", .{}) catch |err|
switch (err) {
error.FileNotFound => null,
else => return err,
};
error.FileNotFound => null,
else => return err,
};
const vars = try Vars.init(allocator, env_file);
var launch_logger = LaunchLogger{
.stdout = stdout,
.stderr = stderr,
.stdout = stdout.file,
.stderr = stderr.file,
.silent = env_options.silent,
};
@ -213,8 +213,8 @@ pub fn init(parent_allocator: std.mem.Allocator, env_options: EnvironmentOptions
.development_logger = jetzig.loggers.DevelopmentLogger.init(
allocator,
resolveLogLevel(options.options.@"log-level", jetzig.environment),
stdout,
stderr,
stdout.file,
stderr.file,
),
},
.production => jetzig.loggers.Logger{
@ -304,23 +304,26 @@ pub fn deinit(self: Environment) void {
self.parent_allocator.destroy(self.arena);
}
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
fn getLogFile(stream: enum { stdout, stderr }, options: Options) !jetzig.loggers.LogFile {
const path = switch (stream) {
.stdout => options.log,
.stderr => options.@"log-error",
};
if (std.mem.eql(u8, path, "-")) return switch (stream) {
.stdout => std.io.getStdOut(),
.stdout => .{ .file = std.io.getStdOut(), .sync = false },
.stderr => if (std.mem.eql(u8, options.log, "-"))
std.io.getStdErr()
.{ .file = std.io.getStdErr(), .sync = false }
else
try std.fs.createFileAbsolute(options.log, .{ .truncate = false }),
.{
.file = try std.fs.createFileAbsolute(options.log, .{ .truncate = false }),
.sync = true,
},
};
const file = try std.fs.createFileAbsolute(path, .{ .truncate = false });
try file.seekFromEnd(0);
return file;
return .{ .file = file, .sync = true };
}
fn getSecret(

View File

@ -14,7 +14,7 @@ pub fn getUserId(comptime id_type: IdType, request: *jetzig.Request) !?switch (i
} {
const session = try request.session();
return session.getT(std.enums.nameCast(jetzig.data.ValueType, id_type), "_jetzig_user_id");
return session.getT(@field(jetzig.data.ValueType, @tagName(id_type)), "_jetzig_user_id");
}
pub fn signIn(request: *jetzig.Request, user_id: anytype) !void {

View File

@ -18,7 +18,10 @@ pub const mail = @import("mail.zig");
pub const kv = @import("kv.zig");
pub const db = @import("database.zig");
pub const Environment = @import("Environment.zig");
pub const environment = std.enums.nameCast(Environment.EnvironmentName, build_options.environment);
pub const environment = @field(
Environment.EnvironmentName,
@tagName(build_options.environment),
);
pub const build_options = @import("build_options");
const root = @import("root");

View File

@ -2,9 +2,9 @@ const std = @import("std");
const jetzig = @import("../jetzig.zig");
pub const adapter = std.enums.nameCast(
pub const adapter = @field(
jetzig.jetquery.adapters.Name,
@field(jetzig.jetquery.config.database, @tagName(jetzig.environment)).adapter,
@tagName(@field(jetzig.jetquery.config.database, @tagName(jetzig.environment)).adapter),
);
pub const Schema = jetzig.config.get(type, "Schema");
@ -25,7 +25,7 @@ pub fn repo(allocator: std.mem.Allocator, app: anytype) !Repo {
return try Repo.loadConfig(
allocator,
std.enums.nameCast(jetzig.jetquery.Environment, jetzig.environment),
@field(jetzig.jetquery.Environment, @tagName(jetzig.environment)),
.{
.eventCallback = Callback.callbackFn,
.lazy_connect = switch (jetzig.environment) {

View File

@ -497,12 +497,14 @@ pub fn cookies(self: *Request) !*jetzig.http.Cookies {
/// `jetzig.http.Session`.
pub fn session(self: *Request) !*jetzig.http.Session {
if (self._session) |capture| return capture;
const cookie_name = self.server.env.vars.get("JETZIG_SESSION_COOKIE") orelse
jetzig.http.Session.default_cookie_name;
const local_session = try self.allocator.create(jetzig.http.Session);
local_session.* = jetzig.http.Session.init(
self.allocator,
try self.cookies(),
self.server.env.secret,
.{ .cookie_name = cookie_name },
);
local_session.parse() catch |err| {
switch (err) {

View File

@ -2,12 +2,12 @@ const std = @import("std");
const jetzig = @import("../../jetzig.zig");
pub const cookie_name = "_jetzig-session";
pub const Cipher = std.crypto.aead.chacha_poly.XChaCha20Poly1305;
allocator: std.mem.Allocator,
encryption_key: []const u8,
cookies: *jetzig.http.Cookies,
cookie_name: []const u8,
initialized: bool = false,
data: jetzig.data.Data,
@ -15,22 +15,30 @@ state: enum { parsed, pending } = .pending,
const Self = @This();
pub const default_cookie_name = "_jetzig-session";
pub const Options = struct {
cookie_name: []const u8 = default_cookie_name,
};
pub fn init(
allocator: std.mem.Allocator,
cookies: *jetzig.http.Cookies,
encryption_key: []const u8,
options: Options,
) Self {
return .{
.allocator = allocator,
.data = jetzig.data.Data.init(allocator),
.cookies = cookies,
.cookie_name = options.cookie_name,
.encryption_key = encryption_key,
};
}
/// Parse session cookie.
pub fn parse(self: *Self) !void {
if (self.cookies.get(cookie_name)) |cookie| {
if (self.cookies.get(self.cookie_name)) |cookie| {
try self.parseSessionCookie(cookie.value);
} else {
try self.reset();
@ -111,7 +119,7 @@ fn save(self: *Self) !void {
defer self.allocator.free(encrypted);
const encoded = try jetzig.util.base64Encode(self.allocator, encrypted);
defer self.allocator.free(encoded);
try self.cookies.put(.{ .name = cookie_name, .value = encoded });
try self.cookies.put(.{ .name = self.cookie_name, .value = encoded });
}
fn parseSessionCookie(self: *Self, cookie_value: []const u8) !void {
@ -180,7 +188,7 @@ test "put and get session key/value" {
try cookies.parse();
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
var session = Self.init(allocator, &cookies, &secret);
var session = Self.init(allocator, &cookies, &secret, .{});
defer session.deinit();
var data = jetzig.data.Data.init(allocator);
@ -199,7 +207,7 @@ test "remove session key/value" {
try cookies.parse();
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
var session = Self.init(allocator, &cookies, &secret);
var session = Self.init(allocator, &cookies, &secret, .{});
defer session.deinit();
var data = jetzig.data.Data.init(allocator);
@ -224,7 +232,7 @@ test "get value from parsed/decrypted cookie" {
try cookies.parse();
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
var session = Self.init(allocator, &cookies, &secret);
var session = Self.init(allocator, &cookies, &secret, .{});
defer session.deinit();
try session.parse();
@ -233,17 +241,32 @@ test "get value from parsed/decrypted cookie" {
}
test "invalid cookie value - too short" {
const allocator = std.testing.allocator;
var cookies = jetzig.http.Cookies.init(allocator, "_jetzig-session=abc");
defer cookies.deinit();
try cookies.parse();
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
var session = Self.init(allocator, &cookies, &secret, .{});
defer session.deinit();
try std.testing.expectError(error.JetzigInvalidSessionCookie, session.parse());
}
test "custom session cookie name" {
const allocator = std.testing.allocator;
var cookies = jetzig.http.Cookies.init(
allocator,
"_jetzig-session=abc",
"custom-cookie-name=fPCFwZHvPDT-XCVcsQUSspDLchS3tRuJDqPpB2v3127VXpRP_bPcPLgpHK6RiVkfcP1bMtU",
);
defer cookies.deinit();
try cookies.parse();
const secret: [Cipher.key_length]u8 = [_]u8{0x69} ** Cipher.key_length;
var session = Self.init(allocator, &cookies, &secret);
var session = Self.init(allocator, &cookies, &secret, .{ .cookie_name = "custom-cookie-name" });
defer session.deinit();
try std.testing.expectError(error.JetzigInvalidSessionCookie, session.parse());
try session.parse();
var value = (session.get("foo")).?;
try std.testing.expectEqualStrings("bar", try value.toString());
}

View File

@ -41,9 +41,9 @@ fn jetKVOptions(options: KVOptions) jetzig.jetkv.Options {
.port = options.valkey_options.port,
.connect_timeout = options.valkey_options.connect_timeout * std.time.ms_per_s,
.read_timeout = options.valkey_options.read_timeout * std.time.ms_per_s,
.connect = std.enums.nameCast(
.connect = @field(
jetzig.jetkv.ValkeyBackendOptions.ConnectMode,
options.valkey_options.connect,
@tagName(options.valkey_options.connect),
),
.buffer_size = options.valkey_options.buffer_size,
.pool_size = options.valkey_options.pool_size,

View File

@ -12,6 +12,11 @@ pub const NullLogger = @import("loggers/NullLogger.zig");
pub const LogQueue = @import("loggers/LogQueue.zig");
pub const LogFile = struct {
file: std.fs.File,
sync: bool = false,
};
pub const LogLevel = enum(u4) { TRACE, DEBUG, INFO, WARN, ERROR, FATAL };
pub const LogFormat = enum { development, production, json, null };

View File

@ -70,7 +70,11 @@ pub fn deinit(self: *LogQueue) void {
}
/// Set the stdout and stderr outputs. Must be called before `print`.
pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.File) !void {
pub fn setFiles(
self: *LogQueue,
stdout_file: jetzig.loggers.LogFile,
stderr_file: jetzig.loggers.LogFile,
) !void {
self.writer = Writer{
.queue = self,
.mutex = std.Thread.Mutex{},
@ -80,11 +84,11 @@ pub fn setFiles(self: *LogQueue, stdout_file: std.fs.File, stderr_file: std.fs.F
.stderr_file = stderr_file,
.queue = self,
};
self.stdout_is_tty = stdout_file.isTty();
self.stderr_is_tty = stderr_file.isTty();
self.stdout_is_tty = stdout_file.file.isTty();
self.stderr_is_tty = stderr_file.file.isTty();
self.stdout_colorize = std.io.tty.detectConfig(stdout_file) != .no_color;
self.stderr_colorize = std.io.tty.detectConfig(stderr_file) != .no_color;
self.stdout_colorize = std.io.tty.detectConfig(stdout_file.file) != .no_color;
self.stderr_colorize = std.io.tty.detectConfig(stderr_file.file) != .no_color;
self.state = .ready;
}
@ -147,8 +151,8 @@ pub const Writer = struct {
/// Reader for `LogQueue`. Reads log events from the queue and writes them to the designated
/// target (stdout or stderr).
pub const Reader = struct {
stdout_file: std.fs.File,
stderr_file: std.fs.File,
stdout_file: jetzig.loggers.LogFile,
stderr_file: jetzig.loggers.LogFile,
queue: *LogQueue,
pub const PublishOptions = struct {
@ -160,8 +164,8 @@ pub const Reader = struct {
pub fn publish(self: *Reader, options: PublishOptions) !void {
std.debug.assert(self.queue.state == .ready);
const stdout_writer = self.stdout_file.writer();
const stderr_writer = self.stderr_file.writer();
const stdout_writer = self.stdout_file.file.writer();
const stderr_writer = self.stderr_file.file.writer();
while (true) {
self.queue.condition_mutex.lock();
@ -181,13 +185,13 @@ pub const Reader = struct {
.stdout => {
stdout_written = true;
if (builtin.os.tag == .windows) {
file = self.stdout_file;
file = self.stdout_file.file;
}
},
.stderr => {
stderr_written = true;
if (builtin.os.tag == .windows) {
file = self.stderr_file;
file = self.stderr_file.file;
}
},
}
@ -220,8 +224,8 @@ pub const Reader = struct {
}
}
if (stdout_written and !self.queue.stdout_is_tty) try self.stdout_file.sync();
if (stderr_written and !self.queue.stderr_is_tty) try self.stderr_file.sync();
if (stdout_written and self.stdout_file.sync) try self.stdout_file.file.sync();
if (stderr_written and self.stderr_file.sync) try self.stderr_file.file.sync();
if (options.oneshot) break;
}
@ -289,7 +293,7 @@ test "print to stdout and stderr" {
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
defer stderr.close();
try log_queue.setFiles(stdout, stderr);
try log_queue.setFiles(.{ .file = stdout }, .{ .file = stderr });
try log_queue.print("foo {s}\n", .{"bar"}, .stdout);
try log_queue.print("baz {s}\n", .{"qux"}, .stderr);
try log_queue.print("quux {s}\n", .{"corge"}, .stdout);
@ -333,7 +337,7 @@ test "long messages" {
const stderr = try tmp_dir.dir.createFile("stderr.log", .{ .read = true });
defer stderr.close();
try log_queue.setFiles(stdout, stderr);
try log_queue.setFiles(.{ .file = stdout }, .{ .file = stderr });
try log_queue.print("foo" ** buffer_size, .{}, .stdout);
try log_queue.reader.publish(.{ .oneshot = true });

View File

@ -165,7 +165,7 @@ test "HTML part only" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: <Bob> user@example.com
\\From: Bob <user@example.com>
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -201,7 +201,7 @@ test "text part only" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: <Bob> user@example.com
\\From: Bob <user@example.com>
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -238,7 +238,7 @@ test "HTML and text parts" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: <Bob> user@example.com
\\From: Bob <user@example.com>
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -262,6 +262,41 @@ test "HTML and text parts" {
try std.testing.expectEqualStrings(expected, actual);
}
test "default email address name" {
const mail = Mail{
.allocator = std.testing.allocator,
.env = undefined,
.config = .{},
.boundary = 123456789,
.params = .{
.from = .{ .email = "user@example.com" },
.to = &.{.{ .email = "user@example.com" }},
.subject = "Test subject",
.text = "Hello",
},
};
const actual = try generateData(mail);
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: user@example.com <user@example.com>
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
\\--=_alternative_123456789
\\Content-Type: text/plain; charset="UTF-8"
\\Content-Transfer-Encoding: quoted-printable
\\
\\Hello
\\
\\.
\\
, "\n", "\r\n");
defer std.testing.allocator.free(expected);
try std.testing.expectEqualStrings(expected, actual);
}
test "long content encoding" {
const mail = Mail{
.allocator = std.testing.allocator,
@ -281,7 +316,7 @@ test "long content encoding" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: <Bob> user@example.com
\\From: Bob <user@example.com>
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -332,7 +367,7 @@ test "non-latin alphabet encoding" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: <Bob> user@example.com
\\From: Bob <user@example.com>
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"

View File

@ -22,7 +22,7 @@ pub const Address = struct {
email: []const u8,
pub fn format(address: Address, _: anytype, _: anytype, writer: anytype) !void {
try writer.print("<{?s}> {s}", .{ address.name, address.email });
try writer.print("{s} <{s}>", .{ address.name orelse address.email, address.email });
}
};

View File

@ -50,7 +50,7 @@ fn detectEncoding(request: *const jetzig.http.Request) ?Encoding {
while (it.next()) |param| {
inline for (@typeInfo(Encoding).@"enum".fields) |field| {
if (std.mem.eql(u8, field.name, jetzig.util.strip(param))) {
return std.enums.nameCast(Encoding, field.name);
return @field(Encoding, field.name);
}
}
}

View File

@ -19,17 +19,21 @@ pub fn afterRequest(request: *jetzig.http.Request) !void {
/// If a redirect was issued during request processing, reset any response data, set response
/// status to `200 OK` and replace the `Location` header with a `HX-Redirect` header.
/// Add Vary response header to prevent caching the page without layout for requests not coming
/// from htmx.
pub fn beforeResponse(request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
switch (response.status_code) {
.moved_permanently, .found => {},
else => return,
}
if (request.headers.get("HX-Request") == null) return;
if (response.headers.get("Location")) |location| {
response.status_code = .ok;
request.response_data.reset();
try response.headers.append("HX-Redirect", location);
switch (response.status_code) {
.moved_permanently, .found => {
if (response.headers.get("Location")) |location| {
response.status_code = .ok;
request.response_data.reset();
try response.headers.append("HX-Redirect", location);
}
},
else => {
try response.headers.append("Vary", "HX-Request");
},
}
}

View File

@ -55,7 +55,7 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {
try cookies.parse();
const session = try alloc.create(jetzig.http.Session);
session.* = jetzig.http.Session.init(alloc, cookies, jetzig.testing.secret);
session.* = jetzig.http.Session.init(alloc, cookies, jetzig.testing.secret, .{});
app.* = App{
.arena = arena,
@ -237,7 +237,7 @@ pub fn initSession(self: *App) !void {
const allocator = self.arena.allocator();
var local_session = try allocator.create(jetzig.http.Session);
local_session.* = jetzig.http.Session.init(allocator, self.cookies, jetzig.testing.secret);
local_session.* = jetzig.http.Session.init(allocator, self.cookies, jetzig.testing.secret, .{});
try local_session.parse();
self.session = local_session;
@ -347,7 +347,7 @@ fn stubbedRequest(
.route_data = null,
.middlewares = undefined,
.address = undefined,
.method = std.enums.nameCast(httpz.Method, @tagName(method)),
.method = @field(httpz.Method, @tagName(method)),
.protocol = .HTTP11,
.params = undefined,
.conn = undefined,