Zig 0.14 test runner config changes

Update dependencies to match new Zig build config API for test runners,
misc. fixes needed after upgrading dependencies.
This commit is contained in:
Bob Farrell 2025-02-02 10:55:30 +00:00
parent 1b5c72b75f
commit a5430ff380
13 changed files with 106 additions and 86 deletions

View File

@ -315,7 +315,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
.root_source_file = tests_file_path,
.target = target,
.optimize = optimize,
.test_runner = jetzig_dep.path("src/test_runner.zig"),
.test_runner = .{ .mode = .simple, .path = jetzig_dep.path("src/test_runner.zig") },
});
exe_unit_tests.root_module.addImport("jetzig", jetzig_module);
exe_unit_tests.root_module.addImport("static", static_module);

View File

@ -6,37 +6,37 @@
.url = "https://github.com/jetzig-framework/zmd/archive/d6c8aa9a9cde99674ccb096d8f94ed09cba8dab.tar.gz",
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
},
.zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/fb58c2d793576407a8a78942cc9e5a177c75d0b1.tar.gz",
.hash = "1220cf76b9c5209fa6e6a6b894dad95cfa5c226445d6b31dd4cf0a72507ca27b4cd6",
},
.jetkv = .{
.url = "https://github.com/jetzig-framework/jetkv/archive/acaa30db281f1c331d20c48cfe6539186549ad45.tar.gz",
.hash = "1220b260b20cb65d801a00a39dc6506387f5faa1a225f85160e011bd2aabd2ce6e0b",
},
.jetquery = .{
.url = "https://github.com/jetzig-framework/jetquery/archive/ec99c0accedbf783c9836f096e2381e4d8b396eb.tar.gz",
.hash = "1220d03534fb9e30dbe46d9450e4a8a9530cd0cc76b88ba37f3e44337c017943b859",
},
.jetcommon = .{
.url = "https://github.com/jetzig-framework/jetcommon/archive/86f24cfdf2aaa0e8ada4539a6edef882708ced2b.tar.gz",
.hash = "12200439fc28aa7fa08f0e8fea100f6724c34c9dbfaaae4feec482c80e5ac08ea4f6",
.url = "https://github.com/jetzig-framework/jetkv/archive/9d754e552e7569239a900ed9e0f313a0554ed2d3.tar.gz",
.hash = "122013f8596bc615990fd7771c833cab4d2959ecac8d05c4f6c973aa46624e43afea",
},
.args = .{
.url = "https://github.com/ikskuh/zig-args/archive/968258dc1b1230493d8f1677097c832a3d7e0bd8.tar.gz",
.hash = "1220bdedf1a993d852d8aebcd63922a8fb163fac37b9c6ff72d187b2847a4a3a4248",
},
.pg = .{
.url = "https://github.com/karlseguin/pg.zig/archive/4ddae09948cb1563b394cd724b95de14cc88fc12.tar.gz",
.hash = "1220779868e6a2f387addec799f176342f5d9a0277139cdb51336e0c1c1b904fcffa",
.jetcommon = .{
.url = "https://github.com/jetzig-framework/jetcommon/archive/5be57d534b3d469f5570cd4b373b8d61032b1b8b.tar.gz",
.hash = "122079c6ceb28fa93163c2f95e2f175bb8f93f3075fa34af63045671ab7dd824e756",
},
.smtp_client = .{
.url = "https://github.com/karlseguin/smtp_client.zig/archive/3cbe8f269e4c3a6bce407e7ae48b2c76307c559f.tar.gz",
.hash = "1220de146446d0cae4396e346cb8283dd5e086491f8577ddbd5e03ad0928111d8bc6",
.pg = .{
.url = "https://github.com/karlseguin/pg.zig/archive/0110cfdf387403a5a326115b5184861c4604d711.tar.gz",
.hash = "12205019ce2bc2e08c76352ea37a14600d412e5e0ecdd7ddd27b4e83a62f37d8ba94",
},
.httpz = .{
.url = "https://github.com/karlseguin/http.zig/archive/da9e944de0be6e5c67ca711dd238ce82d81558b4.tar.gz",
.hash = "12201df692f62d526fdf94e6000cf8de2142edf27484887e2e8f1ec5db4c9b808e5c",
.url = "https://github.com/karlseguin/http.zig/archive/46753ab508a86d0eb510510fc2ed6940a1ebf20a.tar.gz",
.hash = "12207dbe64a04fb960156cbc990153cb3637a08e3fe23077c7199621b5c6377f5d20",
},
.smtp_client = .{
.url = "https://github.com/karlseguin/smtp_client.zig/archive/5163c66cc42cdd93176a6b1cad45f3db3a291a6a.tar.gz",
.hash = "1220a7807b5161550cb0cba772689c4872bfeee8305a26c3cd0e12a8ccde1d546910",
},
.jetquery = .{
.url = "https://github.com/jetzig-framework/jetquery/archive/d4010cfd9ced2e7deb0f3a6cc64e2d32b8db95ba.tar.gz",
.hash = "122069eeb0d43931e49f93419bdb5930ac3a6bc35d1e977738fe872ecaac8ff32aec",
},
.zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/b1dfca8eec73520af5b029016c5b5914da659b6d.tar.gz",
.hash = "1220e70c218c89de219d4f9506a4ad69bd1b5257cd8c7cdc2ea823830e1d8b9dc4df",
},
},

View File

@ -9,8 +9,8 @@
.hash = "1220bdedf1a993d852d8aebcd63922a8fb163fac37b9c6ff72d187b2847a4a3a4248",
},
.jetquery = .{
.url = "https://github.com/jetzig-framework/jetquery/archive/ec99c0accedbf783c9836f096e2381e4d8b396eb.tar.gz",
.hash = "1220d03534fb9e30dbe46d9450e4a8a9530cd0cc76b88ba37f3e44337c017943b859",
.url = "https://github.com/jetzig-framework/jetquery/archive/d4010cfd9ced2e7deb0f3a6cc64e2d32b8db95ba.tar.gz",
.hash = "122069eeb0d43931e49f93419bdb5930ac3a6bc35d1e977738fe872ecaac8ff32aec",
},
},
.paths = .{

View File

@ -56,21 +56,16 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he
\\// * allocator: Arena allocator for use during the mail delivery process.
\\// * mail: Mail parameters (from, to, subject, etc.). Inspect or override any values
\\// assigned when the mail was created.
\\// * data: Provides `data.string()` etc. for generating Jetzig Values.
\\// * params: Template data for `text.zmpl` and `html.zmpl`. Inherits all response data
\\// assigned in a view function and can be modified for email-specific content.
\\// * env: Provides the following fields:
\\// - logger: Logger attached to the same stream as the Jetzig server.
\\// - environment: Enum of `{ production, development }`.
\\// * env: Provides various information about the environment. See `jetzig.jobs.JobEnv`.
\\pub fn deliver(
\\ allocator: std.mem.Allocator,
\\ mail: *jetzig.mail.MailParams,
\\ data: *jetzig.data.Data,
\\ params: *jetzig.data.Value,
\\ env: jetzig.jobs.JobEnv,
\\) !void {
\\ _ = allocator;
\\ _ = data;
\\ _ = params;
\\ try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)});
\\}

View File

@ -12,8 +12,8 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
env,
.{
.subject = "Hello!!!",
.from = "bob@jetzig.dev",
.to = &.{"bob@jetzig.dev"},
.from = .{ .email = "bob@jetzig.dev" },
.to = &.{.{ .email = "bob@jetzig.dev" }},
.html = "<div>Hello!</div>",
.text = "Hello!",
},

View File

@ -3,7 +3,7 @@ const jetzig = @import("jetzig");
// Default values for this mailer.
pub const defaults: jetzig.mail.DefaultMailParams = .{
.from = "no-reply@example.com",
.from = .{ .email = "no-reply@example.com" },
.subject = "Default subject",
};

View File

@ -10,7 +10,7 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
// * `src/app/mailers/welcome/html.zmpl`
// * `src/app/mailers/welcome/text.zmpl`
// All mailer templates have access to the same template data as a view template.
const mail = request.mail("welcome", .{ .to = &.{"hello@jetzig.dev"} });
const mail = request.mail("welcome", .{ .to = &.{.{ .email = "hello@jetzig.dev" }} });
// Deliver the email asynchronously via a built-in mail Job. Use `.now` to send the email
// synchronously (i.e. before the request has returned).

View File

@ -141,6 +141,7 @@ pub fn init(
.HEAD => Method.HEAD,
.PUT => Method.PUT,
.OPTIONS => Method.OPTIONS,
.CONNECT, .OTHER => return error.JetzigUnsupportedHttpMethod,
};
const response_data = try allocator.create(jetzig.data.Data);
@ -580,25 +581,18 @@ const RequestMail = struct {
_ = options;
var mail_job = try self.request.job("__jetzig_mail");
try mail_job.params.put("mailer_name", mail_job.data.string(self.name));
const from = if (self.mail_params.from) |from| mail_job.data.string(from) else null;
try mail_job.params.put("from", from);
try mail_job.params.put("mailer_name", self.name);
try mail_job.params.put("from", self.mail_params.from);
var to_array = try mail_job.data.array();
if (self.mail_params.to) |capture| {
for (capture) |to| try to_array.append(mail_job.data.string(to));
if (self.mail_params.to) |to| {
for (to) |each| try to_array.append(each);
}
try mail_job.params.put("to", to_array);
const subject = if (self.mail_params.subject) |subject| mail_job.data.string(subject) else null;
try mail_job.params.put("subject", subject);
const html = if (self.mail_params.html) |html| mail_job.data.string(html) else null;
try mail_job.params.put("html", html);
const text = if (self.mail_params.text) |text| mail_job.data.string(text) else null;
try mail_job.params.put("text", text);
try mail_job.params.put("subject", self.mail_params.subject);
try mail_job.params.put("html", self.mail_params.html);
try mail_job.params.put("text", self.mail_params.text);
if (self.request.response_data.value) |value| try mail_job.params.put(
"params",

View File

@ -3,6 +3,7 @@ const std = @import("std");
pub const Mail = @import("mail/Mail.zig");
pub const SMTPConfig = @import("mail/SMTPConfig.zig");
pub const MailParams = @import("mail/MailParams.zig");
pub const Address = MailParams.Address;
pub const DefaultMailParams = MailParams.DefaultMailParams;
pub const components = @import("mail/components.zig");
pub const Job = @import("mail/Job.zig");

View File

@ -51,9 +51,7 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
);
} else {
try mail.deliver();
try env.logger.INFO("Delivered mail to: {s}", .{
try std.mem.join(allocator, ", ", mail.params.to.?),
});
try env.logger.INFO("Delivered mail to: {s}", .{mail.params.to.?});
}
}
@ -69,19 +67,32 @@ fn resolveSubject(subject: ?*const jetzig.data.Value) ?[]const u8 {
}
}
fn resolveFrom(from: ?*const jetzig.data.Value) ?[]const u8 {
fn resolveFrom(from: ?*const jetzig.data.Value) ?jetzig.mail.Address {
return if (from) |capture| switch (capture.*) {
.null => null,
.string => |string| string.value,
.string => |string| .{ .email = string.value },
.object => |object| .{
.email = object.getT(.string, "email") orelse return null,
.name = object.getT(.string, "name") orelse return null,
},
else => unreachable,
} else null;
}
fn resolveTo(allocator: std.mem.Allocator, params: *const jetzig.data.Value) !?[]const []const u8 {
var to = std.ArrayList([]const u8).init(allocator);
fn resolveTo(allocator: std.mem.Allocator, params: *const jetzig.data.Value) !?[]const jetzig.mail.Address {
var to = std.ArrayList(jetzig.mail.Address).init(allocator);
if (params.get("to")) |capture| {
for (capture.items(.array)) |recipient| {
try to.append(recipient.string.value);
const maybe_address: ?jetzig.mail.Address = switch (recipient.*) {
.null => null,
.string => |string| .{ .email = string.value },
.object => |object| .{
.email = object.getT(.string, "email") orelse return null,
.name = object.getT(.string, "name") orelse return null,
},
else => unreachable,
};
if (maybe_address) |address| try to.append(address);
}
}
return if (to.items.len > 0) try to.toOwnedSlice() else null;

View File

@ -30,9 +30,16 @@ pub fn deliver(self: Mail) !void {
const data = try self.generateData();
defer self.allocator.free(data);
const to = try self.allocator.alloc(smtp.Message.Address, self.params.to.?.len);
defer self.allocator.free(to);
for (self.params.to.?, 0..) |address, index| {
to[index] = .{ .address = address.email, .name = address.name };
}
try smtp.send(.{
.from = self.params.from.?,
.to = self.params.to.?,
.from = .{ .address = self.params.from.?.email, .name = self.params.from.?.name },
.to = to,
.data = data,
}, try self.config.toSMTP(self.allocator, self.env));
}
@ -147,8 +154,8 @@ test "HTML part only" {
.config = .{},
.boundary = 123456789,
.params = .{
.from = "user@example.com",
.to = &.{"user@example.com"},
.from = .{ .name = "Bob", .email = "user@example.com" },
.to = &.{.{ .name = "Alice", .email = "user@example.com" }},
.subject = "Test subject",
.html = "<div>Hello</div>",
},
@ -158,7 +165,7 @@ test "HTML part only" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: user@example.com
\\From: <Bob> user@example.com
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -183,8 +190,8 @@ test "text part only" {
.config = .{},
.boundary = 123456789,
.params = .{
.from = "user@example.com",
.to = &.{"user@example.com"},
.from = .{ .name = "Bob", .email = "user@example.com" },
.to = &.{.{ .name = "Alice", .email = "user@example.com" }},
.subject = "Test subject",
.text = "Hello",
},
@ -194,7 +201,7 @@ test "text part only" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: user@example.com
\\From: <Bob> user@example.com
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -219,8 +226,8 @@ test "HTML and text parts" {
.config = .{},
.boundary = 123456789,
.params = .{
.from = "user@example.com",
.to = &.{"user@example.com"},
.from = .{ .name = "Bob", .email = "user@example.com" },
.to = &.{.{ .name = "Alice", .email = "user@example.com" }},
.subject = "Test subject",
.html = "<div>Hello</div>",
.text = "Hello",
@ -231,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: user@example.com
\\From: <Bob> user@example.com
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -262,8 +269,8 @@ test "long content encoding" {
.config = .{},
.boundary = 123456789,
.params = .{
.from = "user@example.com",
.to = &.{"user@example.com"},
.from = .{ .name = "Bob", .email = "user@example.com" },
.to = &.{.{ .name = "Alice", .email = "user@example.com" }},
.subject = "Test subject",
.html = "<html><body><div style=\"background-color: black; color: #ff00ff;\">Hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo!!!</div></body></html>",
.text = "Hello",
@ -274,7 +281,7 @@ test "long content encoding" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: user@example.com
\\From: <Bob> user@example.com
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"
@ -312,8 +319,8 @@ test "non-latin alphabet encoding" {
.config = .{},
.boundary = 123456789,
.params = .{
.from = "user@example.com",
.to = &.{"user@example.com"},
.from = .{ .name = "Bob", .email = "user@example.com" },
.to = &.{.{ .name = "Alice", .email = "user@example.com" }},
.subject = "Test subject",
.html = "<html><body><div>你爱学习 Zig 吗?</div></body></html>",
@ -325,7 +332,7 @@ test "non-latin alphabet encoding" {
defer std.testing.allocator.free(actual);
const expected = try std.mem.replaceOwned(u8, std.testing.allocator,
\\From: user@example.com
\\From: <Bob> user@example.com
\\Subject: Test subject
\\MIME-Version: 1.0
\\Content-Type: multipart/alternative; boundary="=_alternative_123456789"

View File

@ -1,22 +1,31 @@
subject: ?[]const u8 = null,
from: ?[]const u8 = null,
to: ?[]const []const u8 = null,
cc: ?[]const []const u8 = null,
bcc: ?[]const []const u8 = null, // TODO
from: ?Address = null,
to: ?[]const Address = null,
cc: ?[]const Address = null,
bcc: ?[]const Address = null, // TODO
html: ?[]const u8 = null,
text: ?[]const u8 = null,
defaults: ?DefaultMailParams = null,
pub const DefaultMailParams = struct {
subject: ?[]const u8 = null,
from: ?[]const u8 = null,
to: ?[]const []const u8 = null,
cc: ?[]const []const u8 = null,
bcc: ?[]const []const u8 = null, // TODO
from: ?Address = null,
to: ?[]const Address = null,
cc: ?[]const Address = null,
bcc: ?[]const Address = null, // TODO
html: ?[]const u8 = null,
text: ?[]const u8 = null,
};
pub const Address = struct {
name: ?[]const u8 = null,
email: []const u8,
pub fn format(address: Address, _: anytype, _: anytype, writer: anytype) !void {
try writer.print("<{?s}> {s}", .{ address.name, address.email });
}
};
const MailParams = @This();
pub fn get(
@ -24,10 +33,10 @@ pub fn get(
comptime field: enum { subject, from, to, cc, bcc, html, text },
) ?switch (field) {
.subject => []const u8,
.from => []const u8,
.to => []const []const u8,
.cc => []const []const u8,
.bcc => []const []const u8,
.from => Address,
.to => []const Address,
.cc => []const Address,
.bcc => []const Address,
.html => []const u8,
.text => []const u8,
} {

View File

@ -349,6 +349,9 @@ fn stubbedRequest(
.method = std.enums.nameCast(httpz.Method, @tagName(method)),
.protocol = .HTTP11,
.params = undefined,
.conn = undefined,
.method_string = undefined,
.unread_body = undefined,
.headers = request_headers,
.body_buffer = if (options.getBody()) |capture|
.{ .data = @constCast(capture), .type = .static }