mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
Database CLI improvements
Eradication of `data` arg to requests. We no longer need to pass this value around as we have a) type inference, b) nested object insertion via `put` and `append`. Fix `joinPath` numeric type coercion Detect empty string params and treat them as blank with expectParams() Fix error logging/stack trace printing. Update Zmpl - includes debug tokens + error tracing to source template in debug builds.
This commit is contained in:
parent
d054e58fa0
commit
92dce21244
@ -7,8 +7,8 @@
|
|||||||
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
||||||
},
|
},
|
||||||
.zmpl = .{
|
.zmpl = .{
|
||||||
.url = "https://github.com/jetzig-framework/zmpl/archive/7b7452bc7fdb0bd2f8a6a9c4e9312900b486aeba.tar.gz",
|
.url = "https://github.com/jetzig-framework/zmpl/archive/25b91d030b992631d319adde1cf01baecd9f3934.tar.gz",
|
||||||
.hash = "1220ed127f38fa51df53a85b3cc2030a7555e34058db7fd374ebaef817abb43d35f7",
|
.hash = "12208dd5a4bf0c6c7efc4e9f37a5d8ed80d6004d5680176d1fc2114bfa593e927baf",
|
||||||
},
|
},
|
||||||
.jetkv = .{
|
.jetkv = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz",
|
||||||
|
208
cli/colors.zig
Normal file
208
cli/colors.zig
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// Must be consistent with `std.io.tty.Color` for Windows compatibility.
|
||||||
|
pub const codes = .{
|
||||||
|
.escape = "\x1b[",
|
||||||
|
.black = "30m",
|
||||||
|
.red = "31m",
|
||||||
|
.green = "32m",
|
||||||
|
.yellow = "33m",
|
||||||
|
.blue = "34m",
|
||||||
|
.magenta = "35m",
|
||||||
|
.cyan = "36m",
|
||||||
|
.white = "37m",
|
||||||
|
.bright_black = "90m",
|
||||||
|
.bright_red = "91m",
|
||||||
|
.bright_green = "92m",
|
||||||
|
.bright_yellow = "93m",
|
||||||
|
.bright_blue = "94m",
|
||||||
|
.bright_magenta = "95m",
|
||||||
|
.bright_cyan = "96m",
|
||||||
|
.bright_white = "97m",
|
||||||
|
.bold = "1m",
|
||||||
|
.dim = "2m",
|
||||||
|
.reset = "0m",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Map color codes generated by `std.io.tty.Config.setColor` back to `std.io.tty.Color`. Used by
|
||||||
|
/// `jetzig.util.writeAnsi` to parse escape codes so they can be passed to
|
||||||
|
/// `std.io.tty.Config.setColor` (using Windows API to set console color mode).
|
||||||
|
const ansi_colors = .{
|
||||||
|
.{ "30", .black },
|
||||||
|
.{ "31", .red },
|
||||||
|
.{ "32", .green },
|
||||||
|
.{ "33", .yellow },
|
||||||
|
.{ "34", .blue },
|
||||||
|
.{ "35", .magenta },
|
||||||
|
.{ "36", .cyan },
|
||||||
|
.{ "37", .white },
|
||||||
|
.{ "90", .bright_black },
|
||||||
|
.{ "91", .bright_red },
|
||||||
|
.{ "92", .bright_green },
|
||||||
|
.{ "93", .bright_yellow },
|
||||||
|
.{ "94", .bright_blue },
|
||||||
|
.{ "95", .bright_magenta },
|
||||||
|
.{ "96", .bright_cyan },
|
||||||
|
.{ "97", .bright_white },
|
||||||
|
.{ "1", .bold },
|
||||||
|
.{ "2", .dim },
|
||||||
|
.{ "0", .reset },
|
||||||
|
};
|
||||||
|
pub const codes_map = if (@hasDecl(std, "ComptimeStringMap"))
|
||||||
|
std.ComptimeStringMap(std.io.tty.Color, ansi_colors)
|
||||||
|
else if (@hasDecl(std, "StaticStringMap"))
|
||||||
|
std.StaticStringMap(std.io.tty.Color).initComptime(ansi_colors)
|
||||||
|
else
|
||||||
|
unreachable;
|
||||||
|
|
||||||
|
// Map basic ANSI color codes to Windows TextAttribute colors
|
||||||
|
// used by std.os.windows.SetConsoleTextAttribute()
|
||||||
|
const windows_colors = .{
|
||||||
|
.{ "30", 0 },
|
||||||
|
.{ "31", 4 },
|
||||||
|
.{ "32", 2 },
|
||||||
|
.{ "33", 6 },
|
||||||
|
.{ "34", 1 },
|
||||||
|
.{ "35", 5 },
|
||||||
|
.{ "36", 3 },
|
||||||
|
.{ "37", 7 },
|
||||||
|
.{ "90", 8 },
|
||||||
|
.{ "91", 12 },
|
||||||
|
.{ "92", 10 },
|
||||||
|
.{ "93", 14 },
|
||||||
|
.{ "94", 9 },
|
||||||
|
.{ "95", 13 },
|
||||||
|
.{ "96", 11 },
|
||||||
|
.{ "97", 15 },
|
||||||
|
.{ "1", 7 },
|
||||||
|
.{ "2", 7 },
|
||||||
|
.{ "0", 7 },
|
||||||
|
};
|
||||||
|
pub const windows_map = if (@hasDecl(std, "ComptimeStringMap"))
|
||||||
|
std.ComptimeStringMap(u16, windows_colors)
|
||||||
|
else if (@hasDecl(std, "StaticStringMap"))
|
||||||
|
std.StaticStringMap(u16).initComptime(windows_colors)
|
||||||
|
else
|
||||||
|
unreachable;
|
||||||
|
|
||||||
|
/// Colorize a log message. Note that we force `.escape_codes` when we are a TTY even on Windows.
|
||||||
|
/// `jetzig.loggers.LogQueue` parses the ANSI codes and uses `std.io.tty.Config.setColor` to
|
||||||
|
/// invoke the appropriate Windows API call to set the terminal color before writing each token.
|
||||||
|
/// We must do it this way because Windows colors are set by API calls at the time of write, not
|
||||||
|
/// encoded into the message string.
|
||||||
|
pub fn colorize(color: std.io.tty.Color, buf: []u8, input: []const u8, is_colorized: bool) ![]const u8 {
|
||||||
|
if (!is_colorized) return input;
|
||||||
|
|
||||||
|
const config: std.io.tty.Config = .escape_codes;
|
||||||
|
var stream = std.io.fixedBufferStream(buf);
|
||||||
|
const writer = stream.writer();
|
||||||
|
try config.setColor(writer, color);
|
||||||
|
try writer.writeAll(input);
|
||||||
|
try config.setColor(writer, .reset);
|
||||||
|
|
||||||
|
return stream.getWritten();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap(comptime attribute: []const u8, comptime message: []const u8) []const u8 {
|
||||||
|
return codes.escape ++ attribute ++ message ++ codes.escape ++ codes.reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtimeWrap(allocator: std.mem.Allocator, attribute: []const u8, message: []const u8) ![]const u8 {
|
||||||
|
return try std.mem.join(
|
||||||
|
allocator,
|
||||||
|
"",
|
||||||
|
&[_][]const u8{ codes.escape, attribute, message, codes.escape, codes.reset },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bold(comptime message: []const u8) []const u8 {
|
||||||
|
return codes.escape ++ codes.bold ++ message ++ codes.escape ++ codes.reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn black(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.black, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeBlack(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.black, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn red(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.red, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeRed(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.red, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn green(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.green, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeGreen(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.green, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn yellow(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.yellow, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeYellow(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.yellow, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blue(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.blue, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeBlue(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.blue, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn magenta(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.magenta, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeMagenta(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.magenta, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cyan(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.cyan, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeCyan(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.cyan, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn white(comptime message: []const u8) []const u8 {
|
||||||
|
return wrap(codes.white, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtimeWhite(allocator: std.mem.Allocator, message: []const u8) ![]const u8 {
|
||||||
|
return try runtimeWrap(allocator, codes.white, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn duration(buf: *[256]u8, delta: i64, is_colorized: bool) ![]const u8 {
|
||||||
|
if (!is_colorized) {
|
||||||
|
return try std.fmt.bufPrint(
|
||||||
|
buf,
|
||||||
|
"{}",
|
||||||
|
.{std.fmt.fmtDurationSigned(delta)},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const color: std.io.tty.Color = if (delta < 1000000)
|
||||||
|
.green
|
||||||
|
else if (delta < 5000000)
|
||||||
|
.yellow
|
||||||
|
else
|
||||||
|
.red;
|
||||||
|
var duration_buf: [256]u8 = undefined;
|
||||||
|
const formatted_duration = try std.fmt.bufPrint(
|
||||||
|
&duration_buf,
|
||||||
|
"{}",
|
||||||
|
.{std.fmt.fmtDurationSigned(delta)},
|
||||||
|
);
|
||||||
|
return try colorize(color, buf, formatted_duration, true);
|
||||||
|
}
|
@ -5,17 +5,16 @@ const util = @import("../util.zig");
|
|||||||
/// Command line options for the `update` command.
|
/// Command line options for the `update` command.
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
pub const meta = .{
|
pub const meta = .{
|
||||||
.usage_summary = "[password]",
|
.usage_summary = "[init|create]",
|
||||||
.full_text =
|
.full_text =
|
||||||
\\Generates a password
|
\\Manage user authentication. Initialize with `init` to generate a users table migration.
|
||||||
|
\\
|
||||||
\\Example:
|
\\Example:
|
||||||
\\
|
\\
|
||||||
\\ jetzig update
|
\\ jetzig auth init
|
||||||
\\ jetzig update web
|
\\ jetzig auth create bob@jetzig.dev
|
||||||
,
|
,
|
||||||
.option_docs = .{
|
.option_docs = .{},
|
||||||
.path = "Set the output path relative to the current directory (default: current directory)",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,9 +31,10 @@ pub fn run(
|
|||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const allocator = arena.allocator();
|
const allocator = arena.allocator();
|
||||||
|
|
||||||
const Action = enum { user_create };
|
const Action = enum { init, create };
|
||||||
const map = std.StaticStringMap(Action).initComptime(.{
|
const map = std.StaticStringMap(Action).initComptime(.{
|
||||||
.{ "user:create", .user_create },
|
.{ "init", .init },
|
||||||
|
.{ "create", .create },
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = if (main_options.positionals.len > 0)
|
const action = if (main_options.positionals.len > 0)
|
||||||
@ -55,7 +55,20 @@ pub fn run(
|
|||||||
break :blk error.JetzigCommandError;
|
break :blk error.JetzigCommandError;
|
||||||
} else if (action) |capture|
|
} else if (action) |capture|
|
||||||
switch (capture) {
|
switch (capture) {
|
||||||
.user_create => blk: {
|
.init => {
|
||||||
|
const argv = [_][]const u8{
|
||||||
|
"jetzig",
|
||||||
|
"generate",
|
||||||
|
"migration",
|
||||||
|
"create_users",
|
||||||
|
"table:users",
|
||||||
|
"column:email:string:index:unique",
|
||||||
|
"column:password_hash:string",
|
||||||
|
};
|
||||||
|
try util.runCommand(allocator, &argv);
|
||||||
|
try util.print(.success, "Migration created. Run `jetzig database update` to run migration and reflect database.", .{});
|
||||||
|
},
|
||||||
|
.create => blk: {
|
||||||
if (sub_args.len < 1) {
|
if (sub_args.len < 1) {
|
||||||
std.debug.print("Missing argument. Expected an email/username parameter.\n", .{});
|
std.debug.print("Missing argument. Expected an email/username parameter.\n", .{});
|
||||||
break :blk error.JetzigCommandError;
|
break :blk error.JetzigCommandError;
|
||||||
|
@ -149,7 +149,7 @@ pub fn run(
|
|||||||
const tmpdir_real_path = try tmpdir.realpathAlloc(allocator, ".");
|
const tmpdir_real_path = try tmpdir.realpathAlloc(allocator, ".");
|
||||||
defer allocator.free(tmpdir_real_path);
|
defer allocator.free(tmpdir_real_path);
|
||||||
|
|
||||||
try util.runCommand(allocator, tmpdir_real_path, tar_argv.items);
|
try util.runCommandInDir(allocator, tar_argv.items, .{ .path = tmpdir_real_path });
|
||||||
|
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
.windows => {},
|
.windows => {},
|
||||||
@ -215,7 +215,7 @@ fn zig_build_install(allocator: std.mem.Allocator, path: []const u8, options: Op
|
|||||||
defer project_dir.close();
|
defer project_dir.close();
|
||||||
project_dir.makePath(".bundle") catch {};
|
project_dir.makePath(".bundle") catch {};
|
||||||
|
|
||||||
try util.runCommand(allocator, path, install_argv.items);
|
try util.runCommandInDir(allocator, install_argv.items, .{ .path = path });
|
||||||
|
|
||||||
const install_bin_path = try std.fs.path.join(allocator, &[_][]const u8{ ".bundle", "bin" });
|
const install_bin_path = try std.fs.path.join(allocator, &[_][]const u8{ ".bundle", "bin" });
|
||||||
defer allocator.free(install_bin_path);
|
defer allocator.free(install_bin_path);
|
||||||
|
@ -9,12 +9,15 @@ const rollback = @import("database/rollback.zig");
|
|||||||
const create = @import("database/create.zig");
|
const create = @import("database/create.zig");
|
||||||
const drop = @import("database/drop.zig");
|
const drop = @import("database/drop.zig");
|
||||||
const reflect = @import("database/reflect.zig");
|
const reflect = @import("database/reflect.zig");
|
||||||
|
const update = @import("database/update.zig");
|
||||||
|
const setup = @import("database/setup.zig");
|
||||||
|
|
||||||
pub const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE";
|
pub const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE";
|
||||||
|
|
||||||
/// Command line options for the `database` command.
|
/// Command line options for the `database` command.
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
pub const meta = .{
|
pub const meta = .{
|
||||||
.usage_summary = "[migrate|rollback|create|drop|reflect]",
|
.usage_summary = "[setup|create|drop|migrate|rollback|reflect|update]",
|
||||||
.full_text =
|
.full_text =
|
||||||
\\Manage the application's database.
|
\\Manage the application's database.
|
||||||
\\
|
\\
|
||||||
@ -38,13 +41,15 @@ pub fn run(
|
|||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
const Action = enum { migrate, rollback, create, drop, reflect };
|
const Action = enum { migrate, rollback, create, drop, reflect, update, setup };
|
||||||
const map = std.StaticStringMap(Action).initComptime(.{
|
const map = std.StaticStringMap(Action).initComptime(.{
|
||||||
.{ "migrate", .migrate },
|
.{ "migrate", .migrate },
|
||||||
.{ "rollback", .rollback },
|
.{ "rollback", .rollback },
|
||||||
.{ "create", .create },
|
.{ "create", .create },
|
||||||
.{ "drop", .drop },
|
.{ "drop", .drop },
|
||||||
.{ "reflect", .reflect },
|
.{ "reflect", .reflect },
|
||||||
|
.{ "update", .update },
|
||||||
|
.{ "setup", .setup },
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = if (main_options.positionals.len > 0)
|
const action = if (main_options.positionals.len > 0)
|
||||||
@ -73,6 +78,8 @@ pub fn run(
|
|||||||
.create => create.run(alloc, cwd, sub_args, options, T, main_options),
|
.create => create.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
.drop => drop.run(alloc, cwd, sub_args, options, T, main_options),
|
.drop => drop.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
.reflect => reflect.run(alloc, cwd, sub_args, options, T, main_options),
|
.reflect => reflect.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
|
.update => update.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
|
.setup => setup.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
54
cli/commands/database/setup.zig
Normal file
54
cli/commands/database/setup.zig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const cli = @import("../../cli.zig");
|
||||||
|
const util = @import("../../util.zig");
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cwd: std.fs.Dir,
|
||||||
|
args: []const []const u8,
|
||||||
|
options: cli.database.Options,
|
||||||
|
T: type,
|
||||||
|
main_options: T,
|
||||||
|
) !void {
|
||||||
|
_ = cwd;
|
||||||
|
_ = options;
|
||||||
|
if (main_options.options.help or args.len != 0) {
|
||||||
|
std.debug.print(
|
||||||
|
\\Set up a database: create a database, run migrations, reflect schema.
|
||||||
|
\\
|
||||||
|
\\Convenience wrapper for:
|
||||||
|
\\
|
||||||
|
\\* jetzig database create
|
||||||
|
\\* jetzig database update
|
||||||
|
\\
|
||||||
|
\\Example:
|
||||||
|
\\
|
||||||
|
\\ jetzig database setup
|
||||||
|
\\ jetzig --environment=testing setup
|
||||||
|
\\
|
||||||
|
, .{});
|
||||||
|
|
||||||
|
return if (main_options.options.help) {} else error.JetzigCommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = main_options.options.environment;
|
||||||
|
try runCommand(allocator, env, "create");
|
||||||
|
try runCommand(allocator, env, "migrate");
|
||||||
|
try runCommand(allocator, env, "reflect");
|
||||||
|
|
||||||
|
try util.print(
|
||||||
|
.success,
|
||||||
|
"Database created, migrations applied, and Schema generated successfully.",
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runCommand(allocator: std.mem.Allocator, environment: anytype, comptime action: []const u8) !void {
|
||||||
|
try util.runCommand(allocator, &.{
|
||||||
|
"zig",
|
||||||
|
"build",
|
||||||
|
util.environmentBuildOption(environment),
|
||||||
|
"jetzig:database:" ++ action,
|
||||||
|
});
|
||||||
|
}
|
45
cli/commands/database/update.zig
Normal file
45
cli/commands/database/update.zig
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const cli = @import("../../cli.zig");
|
||||||
|
const util = @import("../../util.zig");
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
cwd: std.fs.Dir,
|
||||||
|
args: []const []const u8,
|
||||||
|
options: cli.database.Options,
|
||||||
|
T: type,
|
||||||
|
main_options: T,
|
||||||
|
) !void {
|
||||||
|
_ = cwd;
|
||||||
|
_ = options;
|
||||||
|
if (main_options.options.help or args.len != 0) {
|
||||||
|
std.debug.print(
|
||||||
|
\\Update a database: run migrations and reflect schema.
|
||||||
|
\\
|
||||||
|
\\Convenience wrapper for `jetzig database migrate` and `jetzig database reflect`.
|
||||||
|
\\
|
||||||
|
\\Example:
|
||||||
|
\\
|
||||||
|
\\ jetzig database update
|
||||||
|
\\ jetzig --environment=testing update
|
||||||
|
\\
|
||||||
|
, .{});
|
||||||
|
|
||||||
|
return if (main_options.options.help) {} else error.JetzigCommandError;
|
||||||
|
}
|
||||||
|
|
||||||
|
try util.runCommand(allocator, &.{
|
||||||
|
"zig",
|
||||||
|
"build",
|
||||||
|
util.environmentBuildOption(main_options.options.environment),
|
||||||
|
"jetzig:database:migrate",
|
||||||
|
});
|
||||||
|
|
||||||
|
try util.runCommand(allocator, &.{
|
||||||
|
"zig",
|
||||||
|
"build",
|
||||||
|
util.environmentBuildOption(main_options.options.environment),
|
||||||
|
"jetzig:database:reflect",
|
||||||
|
});
|
||||||
|
}
|
@ -47,7 +47,7 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, he
|
|||||||
\\//
|
\\//
|
||||||
\\// Arguments:
|
\\// Arguments:
|
||||||
\\// * allocator: Arena allocator for use during the job execution process.
|
\\// * allocator: Arena allocator for use during the job execution process.
|
||||||
\\// * params: Params assigned to a job (from a request, any values added to `data`).
|
\\// * params: Params assigned to a job (from a request, values added to response data).
|
||||||
\\// * env: Provides the following fields:
|
\\// * env: Provides the following fields:
|
||||||
\\// - logger: Logger attached to the same stream as the Jetzig server.
|
\\// - logger: Logger attached to the same stream as the Jetzig server.
|
||||||
\\// - environment: Enum of `{ production, development }`.
|
\\// - environment: Enum of `{ production, development }`.
|
||||||
|
@ -194,12 +194,16 @@ pub fn run(
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
try util.runCommand(allocator, realpath, &[_][]const u8{
|
try util.runCommandInDir(
|
||||||
"zig",
|
allocator,
|
||||||
"fetch",
|
&[_][]const u8{
|
||||||
"--save",
|
"zig",
|
||||||
github_url,
|
"fetch",
|
||||||
});
|
"--save",
|
||||||
|
github_url,
|
||||||
|
},
|
||||||
|
.{ .dir = install_dir },
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: Use arg or interactive prompt to do Git setup in net project, default to no.
|
// TODO: Use arg or interactive prompt to do Git setup in net project, default to no.
|
||||||
// const git_setup = false;
|
// const git_setup = false;
|
||||||
@ -322,22 +326,34 @@ fn promptInput(
|
|||||||
|
|
||||||
// Initialize a new Git repository when setting up a new project (optional).
|
// Initialize a new Git repository when setting up a new project (optional).
|
||||||
fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void {
|
fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void {
|
||||||
try util.runCommand(allocator, install_dir, &[_][]const u8{
|
try util.runCommandInDir(
|
||||||
"git",
|
allocator,
|
||||||
"init",
|
&[_][]const u8{
|
||||||
".",
|
"git",
|
||||||
});
|
"init",
|
||||||
|
".",
|
||||||
|
},
|
||||||
|
.{ .path = install_dir },
|
||||||
|
);
|
||||||
|
|
||||||
try util.runCommand(allocator, install_dir, &[_][]const u8{
|
try util.runCommandInDir(
|
||||||
"git",
|
allocator,
|
||||||
"add",
|
&[_][]const u8{
|
||||||
".",
|
"git",
|
||||||
});
|
"add",
|
||||||
|
".",
|
||||||
|
},
|
||||||
|
.{ .path = install_dir },
|
||||||
|
);
|
||||||
|
|
||||||
try util.runCommand(allocator, install_dir, &[_][]const u8{
|
try util.runCommandInDir(
|
||||||
"git",
|
allocator,
|
||||||
"commit",
|
&[_][]const u8{
|
||||||
"-m",
|
"git",
|
||||||
"Initialize Jetzig project",
|
"commit",
|
||||||
});
|
"-m",
|
||||||
|
"Initialize Jetzig project",
|
||||||
|
},
|
||||||
|
.{ .path = install_dir },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,8 @@ pub fn run(
|
|||||||
);
|
);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
util.runCommand(
|
util.runCommandInDir(
|
||||||
allocator,
|
allocator,
|
||||||
realpath,
|
|
||||||
&.{
|
&.{
|
||||||
"zig",
|
"zig",
|
||||||
"build",
|
"build",
|
||||||
@ -75,6 +74,7 @@ pub fn run(
|
|||||||
"--color",
|
"--color",
|
||||||
"on",
|
"on",
|
||||||
},
|
},
|
||||||
|
.{ .path = realpath },
|
||||||
) catch {
|
) catch {
|
||||||
std.debug.print("Build failed, waiting for file change...\n", .{});
|
std.debug.print("Build failed, waiting for file change...\n", .{});
|
||||||
try awaitFileChange(allocator, cwd, &mtime);
|
try awaitFileChange(allocator, cwd, &mtime);
|
||||||
|
@ -55,18 +55,15 @@ pub fn run(
|
|||||||
const save_arg = try std.mem.concat(allocator, u8, &[_][]const u8{ "--save=", name });
|
const save_arg = try std.mem.concat(allocator, u8, &[_][]const u8{ "--save=", name });
|
||||||
defer allocator.free(save_arg);
|
defer allocator.free(save_arg);
|
||||||
|
|
||||||
var cwd = try util.detectJetzigProjectDir();
|
try util.runCommand(
|
||||||
defer cwd.close();
|
allocator,
|
||||||
|
&[_][]const u8{
|
||||||
const realpath = try std.fs.realpathAlloc(allocator, ".");
|
"zig",
|
||||||
defer allocator.free(realpath);
|
"fetch",
|
||||||
|
save_arg,
|
||||||
try util.runCommand(allocator, realpath, &[_][]const u8{
|
github_url,
|
||||||
"zig",
|
},
|
||||||
"fetch",
|
);
|
||||||
save_arg,
|
|
||||||
github_url,
|
|
||||||
});
|
|
||||||
|
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
\\Update complete.
|
\\Update complete.
|
||||||
|
55
cli/util.zig
55
cli/util.zig
@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const cli = @import("cli.zig");
|
const cli = @import("cli.zig");
|
||||||
|
const colors = @import("colors.zig");
|
||||||
|
|
||||||
/// Decode a base64 string, used for parsing out build artifacts generated by the CLI program's
|
/// Decode a base64 string, used for parsing out build artifacts generated by the CLI program's
|
||||||
/// build.zig which are stored in the executable as a module.
|
/// build.zig which are stored in the executable as a module.
|
||||||
@ -16,14 +17,35 @@ pub fn base64Decode(allocator: std.mem.Allocator, input: []const u8) ![]const u8
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const icons = .{
|
||||||
|
.check = "✅",
|
||||||
|
.cross = "❌",
|
||||||
|
};
|
||||||
|
|
||||||
/// Print a success confirmation.
|
/// Print a success confirmation.
|
||||||
pub fn printSuccess() void {
|
pub fn printSuccess() void {
|
||||||
std.debug.print(" ✅\n", .{});
|
std.debug.print(" " ++ icons.check ++ "\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Print a failure confirmation.
|
/// Print a failure confirmation.
|
||||||
pub fn printFailure() void {
|
pub fn printFailure() void {
|
||||||
std.debug.print(" ❌\n", .{});
|
std.debug.print(" " ++ icons.cross ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrintContext = enum { success, failure };
|
||||||
|
/// Print some output in with a given context to stderr.
|
||||||
|
pub fn print(comptime context: PrintContext, comptime message: []const u8, args: anytype) !void {
|
||||||
|
const writer = std.io.getStdErr().writer();
|
||||||
|
switch (context) {
|
||||||
|
.success => try writer.print(
|
||||||
|
std.fmt.comptimePrint("{s} {s}\n", .{ icons.check, colors.green(message) }),
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
.failure => try writer.print(
|
||||||
|
std.fmt.comptimePrint("{s} {s}\n", .{ icons.cross, colors.red(message) }),
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detects a Jetzig project directory either in the current directory or one of its parent
|
/// Detects a Jetzig project directory either in the current directory or one of its parent
|
||||||
@ -126,9 +148,32 @@ pub fn runCommandStreaming(allocator: std.mem.Allocator, install_path: []const u
|
|||||||
_ = try child.wait();
|
_ = try child.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a command as a child process and verifies successful exit code.
|
/// Runs a command as a child process in Jetzig project directory and verifies successful exit
|
||||||
pub fn runCommand(allocator: std.mem.Allocator, install_path: []const u8, argv: []const []const u8) !void {
|
/// code.
|
||||||
const result = try std.process.Child.run(.{ .allocator = allocator, .argv = argv, .cwd = install_path });
|
pub fn runCommand(allocator: std.mem.Allocator, argv: []const []const u8) !void {
|
||||||
|
var dir = try detectJetzigProjectDir();
|
||||||
|
defer dir.close();
|
||||||
|
try runCommandInDir(allocator, argv, .{ .dir = dir });
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dir = union(enum) {
|
||||||
|
path: []const u8,
|
||||||
|
dir: std.fs.Dir,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Runs a command as a child process in the given directory and verifies successful exit code.
|
||||||
|
pub fn runCommandInDir(allocator: std.mem.Allocator, argv: []const []const u8, dir: Dir) !void {
|
||||||
|
const cwd_path = switch (dir) {
|
||||||
|
.path => |capture| capture,
|
||||||
|
.dir => |capture| try capture.realpathAlloc(allocator, "."),
|
||||||
|
};
|
||||||
|
defer if (dir == .dir) allocator.free(cwd_path);
|
||||||
|
|
||||||
|
const result = try std.process.Child.run(.{
|
||||||
|
.allocator = allocator,
|
||||||
|
.argv = argv,
|
||||||
|
.cwd = cwd_path,
|
||||||
|
});
|
||||||
defer allocator.free(result.stdout);
|
defer allocator.free(result.stdout);
|
||||||
defer allocator.free(result.stderr);
|
defer allocator.free(result.stderr);
|
||||||
|
|
||||||
|
@ -28,12 +28,11 @@ pub const defaults: jetzig.mail.DefaultMailParams = .{
|
|||||||
pub fn deliver(
|
pub fn deliver(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
mail: *jetzig.mail.MailParams,
|
mail: *jetzig.mail.MailParams,
|
||||||
data: *jetzig.data.Data,
|
|
||||||
params: *jetzig.data.Value,
|
params: *jetzig.data.Value,
|
||||||
env: jetzig.jobs.JobEnv,
|
env: jetzig.jobs.JobEnv,
|
||||||
) !void {
|
) !void {
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
try params.put("email_message", data.string("Custom email message"));
|
try params.put("email_message", "Custom email message");
|
||||||
|
|
||||||
try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)});
|
try env.logger.INFO("Delivering email with subject: '{?s}'", .{mail.get(.subject)});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("jetzig");
|
||||||
|
|
||||||
pub fn bar(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn bar(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||||
var root = try data.object();
|
var root = try request.data(.object);
|
||||||
try root.put("id", data.string(id));
|
try root.put("id", id);
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("jetzig");
|
||||||
|
|
||||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||||
_ = data;
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn post(request: *jetzig.Request) !jetzig.View {
|
||||||
var root = try data.root(.object);
|
var root = try request.data(.object);
|
||||||
|
|
||||||
const params = try request.params();
|
const params = try request.params();
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ const std = @import("std");
|
|||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("jetzig");
|
||||||
|
|
||||||
/// This example demonstrates usage of Jetzig's KV store.
|
/// This example demonstrates usage of Jetzig's KV store.
|
||||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||||
var root = try data.object();
|
var root = try request.data(.object);
|
||||||
|
|
||||||
// Fetch a string from the KV store. If it exists, store it in the root data object,
|
// Fetch a string from the KV store. If it exists, store it in the root data object,
|
||||||
// otherwise store a string value to be picked up by the next request.
|
// otherwise store a string value to be picked up by the next request.
|
||||||
@ -11,7 +11,7 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
|||||||
try root.put("stored_string", capture);
|
try root.put("stored_string", capture);
|
||||||
} else {
|
} else {
|
||||||
try root.put("stored_string", null);
|
try root.put("stored_string", null);
|
||||||
try request.store.put("example-key", data.string("example-value"));
|
try request.store.put("example-key", "example-value");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left-pop an item from an array and store it in the root data object. This will empty the
|
// Left-pop an item from an array and store it in the root data object. This will empty the
|
||||||
@ -21,9 +21,9 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
|||||||
try root.put("popped", value);
|
try root.put("popped", value);
|
||||||
} else {
|
} else {
|
||||||
// Store some values in an array in the KV store.
|
// Store some values in an array in the KV store.
|
||||||
try request.store.append("example-array", data.string("hello"));
|
try request.store.append("example-array", "hello");
|
||||||
try request.store.append("example-array", data.string("goodbye"));
|
try request.store.append("example-array", "goodbye");
|
||||||
try request.store.append("example-array", data.string("hello again"));
|
try request.store.append("example-array", "hello again");
|
||||||
|
|
||||||
try root.put("popped", null);
|
try root.put("popped", null);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("jetzig");
|
||||||
|
|
||||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||||
_ = data;
|
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,9 +12,9 @@ pub const static_params = .{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn get(id: []const u8, request: *jetzig.StaticRequest, data: *jetzig.Data) !jetzig.View {
|
pub fn get(id: []const u8, request: *jetzig.StaticRequest) !jetzig.View {
|
||||||
var object = try data.object();
|
var object = try request.data(.object);
|
||||||
try object.put("id", data.string(id));
|
try object.put("id", id);
|
||||||
const params = try request.params();
|
const params = try request.params();
|
||||||
if (params.get("foo")) |value| try object.put("foo", value);
|
if (params.get("foo")) |value| try object.put("foo", value);
|
||||||
return request.render(.ok);
|
return request.render(.ok);
|
||||||
|
@ -42,6 +42,14 @@ test "post query params" {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
try response2.expectStatus(.unprocessable_entity);
|
try response2.expectStatus(.unprocessable_entity);
|
||||||
|
|
||||||
|
const response3 = try app.request(.POST, "/params", .{
|
||||||
|
.params = .{
|
||||||
|
.name = "", // empty param
|
||||||
|
.favorite_animal = "raccoon",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try response3.expectStatus(.unprocessable_entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "post json" {
|
test "post json" {
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("jetzig");
|
||||||
|
|
||||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
pub fn index(request: *jetzig.Request) !jetzig.View {
|
||||||
_ = data;
|
|
||||||
const params = try request.params();
|
const params = try request.params();
|
||||||
if (params.get("redirect")) |location| {
|
if (params.get("redirect")) |location| {
|
||||||
switch (location.*) {
|
switch (location.*) {
|
||||||
// Value is `.Null` when param is empty, e.g.:
|
// Value is `.null` when param is empty, e.g.:
|
||||||
// `http://localhost:8080/redirect?redirect`
|
// `http://localhost:8080/redirect?redirect`
|
||||||
.Null => return request.redirect("http://www.example.com/", .moved_permanently),
|
.null => return request.redirect("http://www.example.com/", .moved_permanently),
|
||||||
// Value is `.string` when param is present, e.g.:
|
// Value is `.string` when param is present, e.g.:
|
||||||
// `http://localhost:8080/redirect?redirect=https://jetzig.dev/`
|
// `http://localhost:8080/redirect?redirect=https://jetzig.dev/`
|
||||||
.string => |string| return request.redirect(string.value, .moved_permanently),
|
.string => |string| return request.redirect(string.value, .moved_permanently),
|
||||||
|
@ -7,6 +7,7 @@ const jetzig = @import("jetzig");
|
|||||||
const Migrate = @import("jetquery_migrate").Migrate;
|
const Migrate = @import("jetquery_migrate").Migrate;
|
||||||
const MigrateSchema = @import("jetquery_migrate").MigrateSchema;
|
const MigrateSchema = @import("jetquery_migrate").MigrateSchema;
|
||||||
const Schema = @import("Schema");
|
const Schema = @import("Schema");
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE";
|
const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE";
|
||||||
const production_drop_failure_message = "To drop a production database, " ++
|
const production_drop_failure_message = "To drop a production database, " ++
|
||||||
@ -20,6 +21,15 @@ pub fn main() !void {
|
|||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer std.debug.assert(gpa.deinit() == .ok);
|
defer std.debug.assert(gpa.deinit() == .ok);
|
||||||
|
|
||||||
|
if (comptime !@hasField(@TypeOf(config), "adapter") or config.adapter == .null) {
|
||||||
|
try util.print(
|
||||||
|
.failure,
|
||||||
|
"Database is currently not configured. Update `config/database.zig` before running database commands.",
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const gpa_allocator = gpa.allocator();
|
const gpa_allocator = gpa.allocator();
|
||||||
var arena = std.heap.ArenaAllocator.init(gpa_allocator);
|
var arena = std.heap.ArenaAllocator.init(gpa_allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
|
33
src/commands/util.zig
Normal file
33
src/commands/util.zig
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const colors = @import("jetzig").colors;
|
||||||
|
|
||||||
|
const icons = .{
|
||||||
|
.check = "✅",
|
||||||
|
.cross = "❌",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Print a success confirmation.
|
||||||
|
pub fn printSuccess() void {
|
||||||
|
std.debug.print(" " ++ icons.check ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print a failure confirmation.
|
||||||
|
pub fn printFailure() void {
|
||||||
|
std.debug.print(" " ++ icons.cross ++ "\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrintContext = enum { success, failure };
|
||||||
|
/// Print some output in with a given context to stderr.
|
||||||
|
pub fn print(comptime context: PrintContext, comptime message: []const u8, args: anytype) !void {
|
||||||
|
const writer = std.io.getStdErr().writer();
|
||||||
|
switch (context) {
|
||||||
|
.success => try writer.print(
|
||||||
|
std.fmt.comptimePrint("{s} {s}\n", .{ icons.check, colors.green(message) }),
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
.failure => try writer.print(
|
||||||
|
std.fmt.comptimePrint("{s} {s}\n", .{ icons.cross, colors.red(message) }),
|
||||||
|
args,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
@ -186,13 +186,13 @@ test "query string with param without value" {
|
|||||||
|
|
||||||
const foo = data.get("foo").?;
|
const foo = data.get("foo").?;
|
||||||
try switch (foo.*) {
|
try switch (foo.*) {
|
||||||
.Null => {},
|
.null => {},
|
||||||
else => std.testing.expect(false),
|
else => std.testing.expect(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
const bar = data.get("bar").?;
|
const bar = data.get("bar").?;
|
||||||
try switch (bar.*) {
|
try switch (bar.*) {
|
||||||
.Null => {},
|
.null => {},
|
||||||
else => std.testing.expect(false),
|
else => std.testing.expect(false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -60,8 +60,9 @@ pub const RequestStore = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a String from the store.
|
/// Get a String from the store.
|
||||||
pub fn put(self: RequestStore, key: []const u8, value: *jetzig.data.Value) !void {
|
pub fn put(self: RequestStore, key: []const u8, value: anytype) !void {
|
||||||
try self.store.put(key, value);
|
const alloc = (try self.data()).allocator();
|
||||||
|
try self.store.put(key, try jetzig.Data.zmplValue(value, alloc));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a String to from the key-value store and return it if found.
|
/// Remove a String to from the key-value store and return it if found.
|
||||||
@ -75,13 +76,15 @@ pub const RequestStore = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Append a Value to the end of an Array in the key-value store.
|
/// Append a Value to the end of an Array in the key-value store.
|
||||||
pub fn append(self: RequestStore, key: []const u8, value: *jetzig.data.Value) !void {
|
pub fn append(self: RequestStore, key: []const u8, value: anytype) !void {
|
||||||
try self.store.append(key, value);
|
const alloc = (try self.data()).allocator();
|
||||||
|
try self.store.append(key, try jetzig.Data.zmplValue(value, alloc));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepend a Value to the start of an Array in the key-value store.
|
/// Prepend a Value to the start of an Array in the key-value store.
|
||||||
pub fn prepend(self: RequestStore, key: []const u8, value: *jetzig.data.Value) !void {
|
pub fn prepend(self: RequestStore, key: []const u8, value: anytype) !void {
|
||||||
try self.store.prepend(key, value);
|
const alloc = (try self.data()).allocator();
|
||||||
|
try self.store.prepend(key, try jetzig.Data.zmplValue(value, alloc));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pop a String from an Array in the key-value store.
|
/// Pop a String from an Array in the key-value store.
|
||||||
@ -660,7 +663,7 @@ pub fn joinPath(self: *const Request, args: anytype) ![]const u8 {
|
|||||||
@compileError("Cannot coerce type `" ++ @typeName(field.type) ++ "` to string."),
|
@compileError("Cannot coerce type `" ++ @typeName(field.type) ++ "` to string."),
|
||||||
else => args[index], // Assume []const u8, let Zig do the work.
|
else => args[index], // Assume []const u8, let Zig do the work.
|
||||||
},
|
},
|
||||||
.int, .float => try std.fmt.allocPrint(self.allocator, "{d}", args[index]),
|
.int, .float => try std.fmt.allocPrint(self.allocator, "{d}", .{args[index]}),
|
||||||
else => @compileError("Cannot coerce type `" ++ @typeName(field.type) ++ "` to string."),
|
else => @compileError("Cannot coerce type `" ++ @typeName(field.type) ++ "` to string."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ fn renderResponse(self: *Server, request: *jetzig.http.Request) !void {
|
|||||||
const static_resource = self.matchStaticResource(request) catch |err| {
|
const static_resource = self.matchStaticResource(request) catch |err| {
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
|
|
||||||
const rendered = try self.renderInternalServerError(request, err);
|
const rendered = try self.renderInternalServerError(request, @errorReturnTrace(), err);
|
||||||
request.setResponse(rendered, .{});
|
request.setResponse(rendered, .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -220,7 +220,11 @@ fn renderHTML(
|
|||||||
if (zmpl.findPrefixed("views", matched_route.template)) |template| {
|
if (zmpl.findPrefixed("views", matched_route.template)) |template| {
|
||||||
const rendered = self.renderView(matched_route, request, template) catch |err| {
|
const rendered = self.renderView(matched_route, request, template) catch |err| {
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
const rendered_error = try self.renderInternalServerError(request, err);
|
const rendered_error = try self.renderInternalServerError(
|
||||||
|
request,
|
||||||
|
@errorReturnTrace(),
|
||||||
|
err,
|
||||||
|
);
|
||||||
return request.setResponse(rendered_error, .{});
|
return request.setResponse(rendered_error, .{});
|
||||||
};
|
};
|
||||||
return request.setResponse(rendered, .{});
|
return request.setResponse(rendered, .{});
|
||||||
@ -229,7 +233,7 @@ fn renderHTML(
|
|||||||
// assigned in a view.
|
// assigned in a view.
|
||||||
const rendered = self.renderView(matched_route, request, null) catch |err| {
|
const rendered = self.renderView(matched_route, request, null) catch |err| {
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
const rendered_error = try self.renderInternalServerError(request, err);
|
const rendered_error = try self.renderInternalServerError(request, @errorReturnTrace(), err);
|
||||||
return request.setResponse(rendered_error, .{});
|
return request.setResponse(rendered_error, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -296,10 +300,9 @@ fn renderView(
|
|||||||
// `return request.render(.ok)`, but the actual rendered view is stored in
|
// `return request.render(.ok)`, but the actual rendered view is stored in
|
||||||
// `request.rendered_view`.
|
// `request.rendered_view`.
|
||||||
_ = route.render(route, request) catch |err| {
|
_ = route.render(route, request) catch |err| {
|
||||||
try self.logger.ERROR("Encountered error: {s}", .{@errorName(err)});
|
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
if (isBadRequest(err)) return try self.renderBadRequest(request);
|
if (isBadRequest(err)) return try self.renderBadRequest(request);
|
||||||
return try self.renderInternalServerError(request, err);
|
return try self.renderInternalServerError(request, @errorReturnTrace(), err);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.failed) {
|
if (request.failed) {
|
||||||
@ -414,10 +417,15 @@ fn isBadHttpError(err: anyerror) bool {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderInternalServerError(self: *Server, request: *jetzig.http.Request, err: anyerror) !RenderedView {
|
fn renderInternalServerError(
|
||||||
|
self: *Server,
|
||||||
|
request: *jetzig.http.Request,
|
||||||
|
stack_trace: ?*std.builtin.StackTrace,
|
||||||
|
err: anyerror,
|
||||||
|
) !RenderedView {
|
||||||
request.response_data.reset();
|
request.response_data.reset();
|
||||||
|
|
||||||
try self.logger.logError(err);
|
try self.logger.logError(stack_trace, err);
|
||||||
|
|
||||||
const status = .internal_server_error;
|
const status = .internal_server_error;
|
||||||
return try self.renderError(request, status);
|
return try self.renderError(request, status);
|
||||||
@ -460,13 +468,11 @@ fn renderErrorView(
|
|||||||
|
|
||||||
_ = route.render(route.*, request) catch |err| {
|
_ = route.render(route.*, request) catch |err| {
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
try self.logger.logError(err);
|
try self.logger.logError(@errorReturnTrace(), err);
|
||||||
try self.logger.ERROR(
|
try self.logger.ERROR(
|
||||||
"Unexepected error occurred while rendering error page: {s}",
|
"Unexepected error occurred while rendering error page: {s}",
|
||||||
.{@errorName(err)},
|
.{@errorName(err)},
|
||||||
);
|
);
|
||||||
const stack = @errorReturnTrace();
|
|
||||||
if (stack) |capture| try self.logStackTrace(capture, request.allocator);
|
|
||||||
return try renderDefaultError(request, status_code);
|
return try renderDefaultError(request, status_code);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Self = @This();
|
const StaticRequest = @This();
|
||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
response_data: *jetzig.data.Data,
|
response_data: *jetzig.data.Data,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
json: []const u8,
|
json: []const u8,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, json: []const u8) !Self {
|
pub fn init(allocator: std.mem.Allocator, json: []const u8) !StaticRequest {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.response_data = try allocator.create(jetzig.data.Data),
|
.response_data = try allocator.create(jetzig.data.Data),
|
||||||
@ -14,31 +14,35 @@ pub fn init(allocator: std.mem.Allocator, json: []const u8) !Self {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *StaticRequest) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View {
|
pub fn render(self: *StaticRequest, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View {
|
||||||
return .{ .data = self.response_data, .status_code = status_code };
|
return .{ .data = self.response_data, .status_code = status_code };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resourceId(self: *Self) ![]const u8 {
|
pub fn data(self: *StaticRequest, comptime root: @TypeOf(.enum_literal)) !*jetzig.Data.Value {
|
||||||
var data = try self.allocator.create(jetzig.data.Data);
|
return try self.response_data.root(root);
|
||||||
data.* = jetzig.data.Data.init(self.allocator);
|
}
|
||||||
defer self.allocator.destroy(data);
|
|
||||||
defer data.deinit();
|
|
||||||
|
|
||||||
try data.fromJson(self.json);
|
pub fn resourceId(self: *StaticRequest) ![]const u8 {
|
||||||
|
var params_data = try self.allocator.create(jetzig.data.Data);
|
||||||
|
params_data.* = jetzig.data.Data.init(self.allocator);
|
||||||
|
defer self.allocator.destroy(params_data);
|
||||||
|
defer params_data.deinit();
|
||||||
|
|
||||||
|
try params_data.fromJson(self.json);
|
||||||
// Routes generator rejects missing `.id` option so this should always be present.
|
// Routes generator rejects missing `.id` option so this should always be present.
|
||||||
// Note that static requests are never rendered at runtime so we can be unsafe here and risk
|
// Note that static requests are never rendered at runtime so we can be unsafe here and risk
|
||||||
// failing a build (which would not be coherent if we allowed it to complete).
|
// failing a build (which would not be coherent if we allowed it to complete).
|
||||||
return try self.allocator.dupe(u8, data.value.?.get("id").?.string.value);
|
return try self.allocator.dupe(u8, params_data.value.?.get("id").?.string.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the static params defined by `pub const static_params` in the relevant view.
|
/// Returns the static params defined by `pub const static_params` in the relevant view.
|
||||||
pub fn params(self: *Self) !*jetzig.data.Value {
|
pub fn params(self: *StaticRequest) !*jetzig.data.Value {
|
||||||
var data = try self.allocator.create(jetzig.data.Data);
|
var params_data = try self.allocator.create(jetzig.data.Data);
|
||||||
data.* = jetzig.data.Data.init(self.allocator);
|
params_data.* = jetzig.data.Data.init(self.allocator);
|
||||||
try data.fromJson(self.json);
|
try params_data.fromJson(self.json);
|
||||||
return data.value.?.get("params") orelse data.object();
|
return params_data.value.?.get("params") orelse params_data.object();
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,13 @@ pub fn expectParams(request: *jetzig.http.Request, T: type) !?T {
|
|||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
inline for (fields, 0..) |field, index| {
|
inline for (fields, 0..) |field, index| {
|
||||||
if (actual_params.get(field.name)) |value| {
|
var maybe_value = actual_params.get(field.name);
|
||||||
|
|
||||||
|
if (isBlank(maybe_value)) {
|
||||||
|
maybe_value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maybe_value) |value| {
|
||||||
switch (@typeInfo(field.type)) {
|
switch (@typeInfo(field.type)) {
|
||||||
.optional => |info| if (value.coerce(info.child)) |coerced| {
|
.optional => |info| if (value.coerce(info.child)) |coerced| {
|
||||||
@field(t, field.name) = coerced;
|
@field(t, field.name) = coerced;
|
||||||
@ -66,6 +72,12 @@ pub fn expectParams(request: *jetzig.http.Request, T: type) !?T {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn isBlank(maybe_value: ?*const jetzig.Data.Value) bool {
|
||||||
|
if (maybe_value) |value| {
|
||||||
|
return value.* == .string and jetzig.util.strip(value.string.value).len == 0;
|
||||||
|
} else return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// See `Request.paramsInfo`.
|
/// See `Request.paramsInfo`.
|
||||||
pub const ParamsInfo = struct {
|
pub const ParamsInfo = struct {
|
||||||
params: std.BoundedArray(ParamInfo, 1024),
|
params: std.BoundedArray(ParamInfo, 1024),
|
||||||
|
@ -44,7 +44,7 @@ pub fn deinit(self: *Store) void {
|
|||||||
self.store.deinit();
|
self.store.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Put a Value or into the key-value store.
|
/// Put a or into the key-value store.
|
||||||
pub fn put(self: *Store, key: []const u8, value: *jetzig.data.Value) !void {
|
pub fn put(self: *Store, key: []const u8, value: *jetzig.data.Value) !void {
|
||||||
try self.store.put(key, try value.toJson());
|
try self.store.put(key, try value.toJson());
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,13 @@ pub const Logger = union(enum) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logError(self: *const Logger, err: anyerror) !void {
|
pub fn logError(
|
||||||
|
self: *const Logger,
|
||||||
|
stack_trace: ?*std.builtin.StackTrace,
|
||||||
|
err: anyerror,
|
||||||
|
) !void {
|
||||||
switch (self.*) {
|
switch (self.*) {
|
||||||
inline else => |*logger| try logger.logError(err),
|
inline else => |*logger| try logger.logError(stack_trace, err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,17 +238,13 @@ fn printSql(self: *const DevelopmentLogger, sql: []const u8) !void {
|
|||||||
try self.print(.INFO, "{s}", .{stream.getWritten()});
|
try self.print(.INFO, "{s}", .{stream.getWritten()});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logError(self: *const DevelopmentLogger, err: anyerror) !void {
|
pub fn logError(self: *const DevelopmentLogger, stack_trace: ?*std.builtin.StackTrace, err: anyerror) !void {
|
||||||
if (@errorReturnTrace()) |stack| {
|
if (stack_trace) |stack| {
|
||||||
try self.log(.ERROR, "\nStack Trace:\n{}", .{stack});
|
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
||||||
var buf = std.ArrayList(u8).init(self.allocator);
|
try self.log(.ERROR, "Stack trace:\n{}", .{stack});
|
||||||
defer buf.deinit();
|
} else {
|
||||||
const writer = buf.writer();
|
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
||||||
try stack.format("", .{}, writer);
|
|
||||||
try self.logger.ERROR("{s}\n", .{buf.items});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn logFile(self: DevelopmentLogger, comptime level: jetzig.loggers.LogLevel) std.fs.File {
|
fn logFile(self: DevelopmentLogger, comptime level: jetzig.loggers.LogLevel) std.fs.File {
|
||||||
|
@ -112,7 +112,13 @@ pub fn logSql(self: *const JsonLogger, event: jetzig.jetquery.events.Event) !voi
|
|||||||
try self.log_queue.print("{s}\n", .{stream.getWritten()}, .stdout);
|
try self.log_queue.print("{s}\n", .{stream.getWritten()}, .stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logError(self: *const JsonLogger, err: anyerror) !void {
|
pub fn logError(
|
||||||
|
self: *const JsonLogger,
|
||||||
|
stack_trace: ?*std.builtin.StackTrace,
|
||||||
|
err: anyerror,
|
||||||
|
) !void {
|
||||||
|
// TODO: Format this as JSON and include line number/column if available.
|
||||||
|
_ = stack_trace;
|
||||||
try self.log(.ERROR, "Encountered error: {s}", .{@errorName(err)});
|
try self.log(.ERROR, "Encountered error: {s}", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ pub inline fn logRequest(self: @This(), request: *const jetzig.http.Request) !vo
|
|||||||
_ = request;
|
_ = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn logError(self: @This(), err: anyerror) !void {
|
pub inline fn logError(self: @This(), stack_trace: ?*std.builtin.StackTrace, err: anyerror) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
|
_ = stack_trace;
|
||||||
std.debug.print("Error: {s}\n", .{@errorName(err)});
|
std.debug.print("Error: {s}\n", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,8 @@ const sql_tokens = .{
|
|||||||
"VALUES",
|
"VALUES",
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn logError(self: *const ProductionLogger, err: anyerror) !void {
|
pub fn logError(self: *const ProductionLogger, stack_trace: ?*std.builtin.StackTrace, err: anyerror) !void {
|
||||||
|
// TODO: Include line number/column if available.
|
||||||
|
_ = stack_trace;
|
||||||
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
try self.log(.ERROR, "Encountered Error: {s}", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,9 @@ pub fn logSql(self: TestLogger, event: jetzig.jetquery.events.Event) !void {
|
|||||||
try self.log(.INFO, "[database] {?s}", .{event.sql});
|
try self.log(.INFO, "[database] {?s}", .{event.sql});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logError(self: TestLogger, err: anyerror) !void {
|
pub fn logError(self: TestLogger, stack_trace: ?*std.builtin.StackTrace, err: anyerror) !void {
|
||||||
|
// TODO: Output useful debug info from stack trace
|
||||||
|
_ = stack_trace;
|
||||||
try self.log(.ERROR, "Encountered error: {s}", .{@errorName(err)});
|
try self.log(.ERROR, "Encountered error: {s}", .{@errorName(err)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const jetzig = @import("../../jetzig.zig");
|
|||||||
/// Default Mail Job. Send an email with the given params in the background.
|
/// Default Mail Job. Send an email with the given params in the background.
|
||||||
pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv) !void {
|
pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig.jobs.JobEnv) !void {
|
||||||
const mailer_name = if (params.get("mailer_name")) |param| switch (param.*) {
|
const mailer_name = if (params.get("mailer_name")) |param| switch (param.*) {
|
||||||
.Null => null,
|
.null => null,
|
||||||
.string => |string| string.value,
|
.string => |string| string.value,
|
||||||
else => null,
|
else => null,
|
||||||
} else null;
|
} else null;
|
||||||
@ -34,9 +34,7 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
|||||||
.defaults = mailer.defaults,
|
.defaults = mailer.defaults,
|
||||||
};
|
};
|
||||||
|
|
||||||
var data = jetzig.data.Data.init(allocator);
|
try mailer.deliverFn(allocator, &mail_params, params.get("params").?, env);
|
||||||
|
|
||||||
try mailer.deliverFn(allocator, &mail_params, &data, params.get("params").?, env);
|
|
||||||
|
|
||||||
const mail = jetzig.mail.Mail.init(allocator, env, .{
|
const mail = jetzig.mail.Mail.init(allocator, env, .{
|
||||||
.subject = mail_params.get(.subject) orelse "(No subject)",
|
.subject = mail_params.get(.subject) orelse "(No subject)",
|
||||||
@ -62,7 +60,7 @@ pub fn run(allocator: std.mem.Allocator, params: *jetzig.data.Value, env: jetzig
|
|||||||
fn resolveSubject(subject: ?*const jetzig.data.Value) ?[]const u8 {
|
fn resolveSubject(subject: ?*const jetzig.data.Value) ?[]const u8 {
|
||||||
if (subject) |capture| {
|
if (subject) |capture| {
|
||||||
return switch (capture.*) {
|
return switch (capture.*) {
|
||||||
.Null => null,
|
.null => null,
|
||||||
.string => |string| string.value,
|
.string => |string| string.value,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
@ -73,7 +71,7 @@ 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) ?[]const u8 {
|
||||||
return if (from) |capture| switch (capture.*) {
|
return if (from) |capture| switch (capture.*) {
|
||||||
.Null => null,
|
.null => null,
|
||||||
.string => |string| string.value,
|
.string => |string| string.value,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
} else null;
|
} else null;
|
||||||
@ -97,7 +95,7 @@ fn resolveText(
|
|||||||
) !?[]const u8 {
|
) !?[]const u8 {
|
||||||
if (text) |capture| {
|
if (text) |capture| {
|
||||||
return switch (capture.*) {
|
return switch (capture.*) {
|
||||||
.Null => try defaultText(allocator, mailer, params),
|
.null => try defaultText(allocator, mailer, params),
|
||||||
.string => |string| string.value,
|
.string => |string| string.value,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
@ -114,7 +112,7 @@ fn resolveHtml(
|
|||||||
) !?[]const u8 {
|
) !?[]const u8 {
|
||||||
if (text) |capture| {
|
if (text) |capture| {
|
||||||
return switch (capture.*) {
|
return switch (capture.*) {
|
||||||
.Null => try defaultHtml(allocator, mailer, params),
|
.null => try defaultHtml(allocator, mailer, params),
|
||||||
.string => |string| string.value,
|
.string => |string| string.value,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,6 @@ const jetzig = @import("../../jetzig.zig");
|
|||||||
pub const DeliverFn = *const fn (
|
pub const DeliverFn = *const fn (
|
||||||
std.mem.Allocator,
|
std.mem.Allocator,
|
||||||
*jetzig.mail.MailParams,
|
*jetzig.mail.MailParams,
|
||||||
*jetzig.data.Data,
|
|
||||||
*jetzig.data.Value,
|
*jetzig.data.Value,
|
||||||
jetzig.jobs.JobEnv,
|
jetzig.jobs.JobEnv,
|
||||||
) anyerror!void;
|
) anyerror!void;
|
||||||
|
@ -205,7 +205,7 @@ pub fn expectJson(expected_path: []const u8, expected_value: anytype, response:
|
|||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
},
|
},
|
||||||
.Null => switch (@typeInfo(@TypeOf(expected_value))) {
|
.null => switch (@typeInfo(@TypeOf(expected_value))) {
|
||||||
.optional => {
|
.optional => {
|
||||||
if (expected_value == null) return;
|
if (expected_value == null) return;
|
||||||
},
|
},
|
||||||
@ -264,7 +264,7 @@ pub fn expectJson(expected_path: []const u8, expected_value: anytype, response:
|
|||||||
else => unreachable,
|
else => unreachable,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.Null => {
|
.null => {
|
||||||
logFailure(
|
logFailure(
|
||||||
"Expected value in " ++ jetzig.colors.cyan("{s}") ++ ", found " ++ jetzig.colors.green("null") ++ "\nJSON:" ++ json_banner,
|
"Expected value in " ++ jetzig.colors.cyan("{s}") ++ ", found " ++ jetzig.colors.green("null") ++ "\nJSON:" ++ json_banner,
|
||||||
.{ expected_path, try jsonPretty(response) },
|
.{ expected_path, try jsonPretty(response) },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user