mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
WIP
This commit is contained in:
parent
4210aa5e83
commit
d27907a1c5
56
build.zig
56
build.zig
@ -4,6 +4,7 @@ pub const Routes = @import("src/Routes.zig");
|
|||||||
pub const GenerateMimeTypes = @import("src/GenerateMimeTypes.zig");
|
pub const GenerateMimeTypes = @import("src/GenerateMimeTypes.zig");
|
||||||
|
|
||||||
const zmpl_build = @import("zmpl");
|
const zmpl_build = @import("zmpl");
|
||||||
|
const Environment = enum { development, testing, production };
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
@ -106,7 +107,10 @@ pub fn build(b: *std.Build) !void {
|
|||||||
main_tests.root_module.addImport("jetcommon", jetcommon_dep.module("jetcommon"));
|
main_tests.root_module.addImport("jetcommon", jetcommon_dep.module("jetcommon"));
|
||||||
main_tests.root_module.addImport("httpz", httpz_dep.module("httpz"));
|
main_tests.root_module.addImport("httpz", httpz_dep.module("httpz"));
|
||||||
main_tests.root_module.addImport("smtp", smtp_client_dep.module("smtp_client"));
|
main_tests.root_module.addImport("smtp", smtp_client_dep.module("smtp_client"));
|
||||||
|
const test_build_options = b.addOptions();
|
||||||
|
test_build_options.addOption(Environment, "environment", .testing);
|
||||||
const run_main_tests = b.addRunArtifact(main_tests);
|
const run_main_tests = b.addRunArtifact(main_tests);
|
||||||
|
main_tests.root_module.addOptions("build_options", test_build_options);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run library tests");
|
const test_step = b.step("test", "Run library tests");
|
||||||
test_step.dependOn(&run_main_tests.step);
|
test_step.dependOn(&run_main_tests.step);
|
||||||
@ -123,12 +127,13 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
return error.ZmplVersionNotSupported;
|
return error.ZmplVersionNotSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = b.option([]const u8, "seed", "Internal test seed");
|
||||||
|
|
||||||
const target = b.host;
|
const target = b.host;
|
||||||
const optimize = exe.root_module.optimize orelse .Debug;
|
const optimize = exe.root_module.optimize orelse .Debug;
|
||||||
|
|
||||||
if (optimize != .Debug) exe.linkLibC();
|
if (optimize != .Debug) exe.linkLibC();
|
||||||
|
|
||||||
const Environment = enum { development, testing, production };
|
|
||||||
const environment = b.option(
|
const environment = b.option(
|
||||||
Environment,
|
Environment,
|
||||||
"environment",
|
"environment",
|
||||||
@ -207,7 +212,6 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
exe_routes_file.root_module.addImport("zmpl", zmpl_module);
|
exe_routes_file.root_module.addImport("zmpl", zmpl_module);
|
||||||
|
|
||||||
const run_routes_file_cmd = b.addRunArtifact(exe_routes_file);
|
const run_routes_file_cmd = b.addRunArtifact(exe_routes_file);
|
||||||
run_routes_file_cmd.has_side_effects = true; // FIXME
|
|
||||||
const routes_file_path = run_routes_file_cmd.addOutputFileArg("routes.zig");
|
const routes_file_path = run_routes_file_cmd.addOutputFileArg("routes.zig");
|
||||||
run_routes_file_cmd.addArgs(&.{
|
run_routes_file_cmd.addArgs(&.{
|
||||||
root_path,
|
root_path,
|
||||||
@ -217,6 +221,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
jobs_path,
|
jobs_path,
|
||||||
mailers_path,
|
mailers_path,
|
||||||
});
|
});
|
||||||
|
|
||||||
const routes_module = b.createModule(.{ .root_source_file = routes_file_path });
|
const routes_module = b.createModule(.{ .root_source_file = routes_file_path });
|
||||||
routes_module.addImport("jetzig", jetzig_module);
|
routes_module.addImport("jetzig", jetzig_module);
|
||||||
exe.root_module.addImport("routes", routes_module);
|
exe.root_module.addImport("routes", routes_module);
|
||||||
@ -262,7 +267,10 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
run_static_routes_cmd.expectExitCode(0);
|
run_static_routes_cmd.expectExitCode(0);
|
||||||
|
|
||||||
const run_tests_file_cmd = b.addRunArtifact(exe_routes_file);
|
const run_tests_file_cmd = b.addRunArtifact(exe_routes_file);
|
||||||
|
run_tests_file_cmd.step.dependOn(&run_routes_file_cmd.step);
|
||||||
|
|
||||||
const tests_file_path = run_tests_file_cmd.addOutputFileArg("tests.zig");
|
const tests_file_path = run_tests_file_cmd.addOutputFileArg("tests.zig");
|
||||||
|
|
||||||
run_tests_file_cmd.addArgs(&.{
|
run_tests_file_cmd.addArgs(&.{
|
||||||
root_path,
|
root_path,
|
||||||
b.pathFromRoot("src"),
|
b.pathFromRoot("src"),
|
||||||
@ -270,8 +278,15 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
views_path,
|
views_path,
|
||||||
jobs_path,
|
jobs_path,
|
||||||
mailers_path,
|
mailers_path,
|
||||||
|
try randomSeed(b), // Hack to force cache skip - let's find a better way.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (try scanSourceFiles(b)) |sub_path| {
|
||||||
|
// XXX: We don't use these args, but they help Zig's build system to cache/bust steps.
|
||||||
|
run_routes_file_cmd.addFileArg(.{ .src_path = .{ .owner = b, .sub_path = sub_path } });
|
||||||
|
run_tests_file_cmd.addFileArg(.{ .src_path = .{ .owner = b, .sub_path = sub_path } });
|
||||||
|
}
|
||||||
|
|
||||||
const exe_unit_tests = b.addTest(.{
|
const exe_unit_tests = b.addTest(.{
|
||||||
.root_source_file = tests_file_path,
|
.root_source_file = tests_file_path,
|
||||||
.target = target,
|
.target = target,
|
||||||
@ -280,6 +295,8 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
});
|
});
|
||||||
exe_unit_tests.root_module.addImport("jetzig", jetzig_module);
|
exe_unit_tests.root_module.addImport("jetzig", jetzig_module);
|
||||||
exe_unit_tests.root_module.addImport("static", static_module);
|
exe_unit_tests.root_module.addImport("static", static_module);
|
||||||
|
exe_unit_tests.root_module.addImport("routes", routes_module);
|
||||||
|
exe_unit_tests.root_module.addImport("main", main_module);
|
||||||
|
|
||||||
var it = exe.root_module.import_table.iterator();
|
var it = exe.root_module.import_table.iterator();
|
||||||
while (it.next()) |import| {
|
while (it.next()) |import| {
|
||||||
@ -303,12 +320,10 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||||
|
|
||||||
const test_step = b.step("jetzig:test", "Run tests");
|
const test_step = b.step("jetzig:test", "Run tests");
|
||||||
test_step.dependOn(&run_static_routes_cmd.step);
|
|
||||||
test_step.dependOn(&run_exe_unit_tests.step);
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
test_step.dependOn(&run_tests_file_cmd.step);
|
run_exe_unit_tests.step.dependOn(&run_static_routes_cmd.step);
|
||||||
exe_unit_tests.root_module.addImport("routes", routes_module);
|
run_exe_unit_tests.step.dependOn(&run_routes_file_cmd.step);
|
||||||
|
|
||||||
const routes_step = b.step("jetzig:routes", "List all routes in your app");
|
const routes_step = b.step("jetzig:routes", "List all routes in your app");
|
||||||
const exe_routes = b.addExecutable(.{
|
const exe_routes = b.addExecutable(.{
|
||||||
@ -346,7 +361,7 @@ fn registerDatabaseSteps(b: *std.Build, exe_database: *std.Build.Step.Compile) v
|
|||||||
.{ "rollback", "Roll back a migration in your Jetzig app's database." },
|
.{ "rollback", "Roll back a migration in your Jetzig app's database." },
|
||||||
.{ "create", "Create a database for your Jetzig app." },
|
.{ "create", "Create a database for your Jetzig app." },
|
||||||
.{ "drop", "Drop your Jetzig app's database." },
|
.{ "drop", "Drop your Jetzig app's database." },
|
||||||
.{ "dump", "Read your app's database and generate a JetQuery schema." },
|
.{ "reflect", "Read your app's database and generate a JetQuery schema." },
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (commands) |command| {
|
inline for (commands) |command| {
|
||||||
@ -411,3 +426,30 @@ fn isSourceFile(b: *std.Build, path: []const u8) !bool {
|
|||||||
};
|
};
|
||||||
return stat.kind == .file;
|
return stat.kind == .file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scanSourceFiles(b: *std.Build) ![]const []const u8 {
|
||||||
|
var buf = std.ArrayList([]const u8).init(b.allocator);
|
||||||
|
|
||||||
|
var src_dir = try std.fs.openDirAbsolute(b.pathFromRoot("src"), .{ .iterate = true });
|
||||||
|
defer src_dir.close();
|
||||||
|
|
||||||
|
var walker = try src_dir.walk(b.allocator);
|
||||||
|
defer walker.deinit();
|
||||||
|
|
||||||
|
while (try walker.next()) |entry| {
|
||||||
|
if (entry.kind == .file) try buf.append(
|
||||||
|
try std.fs.path.join(b.allocator, &.{ "src", entry.path }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return try buf.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn randomSeed(b: *std.Build) ![]const u8 {
|
||||||
|
const seed = try b.allocator.alloc(u8, 32);
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
for (0..32) |index| {
|
||||||
|
seed[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
@ -7,14 +7,16 @@
|
|||||||
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
.hash = "1220d0e8734628fd910a73146e804d10a3269e3e7d065de6bb0e3e88d5ba234eb163",
|
||||||
},
|
},
|
||||||
.zmpl = .{
|
.zmpl = .{
|
||||||
.path = "../zmpl",
|
.url = "https://github.com/jetzig-framework/zmpl/archive/517b453f333c328ff5ebafa49020b1040e54e880.tar.gz",
|
||||||
|
.hash = "1220798a4647e3b0766aad653830a2601e11c567ba6bfe83e526eb91d04a6c45f7d8",
|
||||||
},
|
},
|
||||||
.jetkv = .{
|
.jetkv = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetkv/archive/2b1130a48979ea2871c8cf6ca89c38b1e7062839.tar.gz",
|
||||||
.hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af",
|
.hash = "12201d75d73aad5e1c996de4d5ae87a00e58479c8d469bc2eeb5fdeeac8857bc09af",
|
||||||
},
|
},
|
||||||
.jetquery = .{
|
.jetquery = .{
|
||||||
.path = "../jetquery",
|
.url = "https://github.com/jetzig-framework/jetquery/archive/8a5c1660504bb6f235e0fd2add7b7aca493a855b.tar.gz",
|
||||||
|
.hash = "1220f5daafc820790e66a3f509289d3575c8e7841748b72620f0ba687823fa2db4e4",
|
||||||
},
|
},
|
||||||
.jetcommon = .{
|
.jetcommon = .{
|
||||||
.url = "https://github.com/jetzig-framework/jetcommon/archive/a248776ba56d6cc2b160d593ac3305756adcd26e.tar.gz",
|
.url = "https://github.com/jetzig-framework/jetcommon/archive/a248776ba56d6cc2b160d593ac3305756adcd26e.tar.gz",
|
||||||
@ -25,13 +27,13 @@
|
|||||||
.hash = "1220411a8c46d95bbf3b6e2059854bcb3c5159d428814099df5294232b9980517e9c",
|
.hash = "1220411a8c46d95bbf3b6e2059854bcb3c5159d428814099df5294232b9980517e9c",
|
||||||
},
|
},
|
||||||
.pg = .{
|
.pg = .{
|
||||||
// .url = "https://github.com/karlseguin/pg.zig/archive/1491270ac43c7eba91992bb06b3758254c36e39a.tar.gz",
|
.url = "https://github.com/karlseguin/pg.zig/archive/9226a0a256cd000eee2f9652c8c03af8dcbed79b.tar.gz",
|
||||||
// .hash = "1220bcc68967188de7ad5d520a4629c0d1e169c111d87e6978a3c128de5ec2b6bdd0",
|
.hash = "12202b30ebf018ca398f665e51cae6d000fdfe2d08d5ec369e3110f762c548b154a4",
|
||||||
.path = "../pg.zig",
|
|
||||||
},
|
},
|
||||||
.smtp_client = .{
|
.smtp_client = .{
|
||||||
.url = "https://github.com/karlseguin/smtp_client.zig/archive/8fcfad9ca2d9e446612c79f4e54050cfbe81b38d.tar.gz",
|
// Pending https://github.com/karlseguin/smtp_client.zig/pull/9 before using mainline
|
||||||
.hash = "1220cebfcf6c63295819df92ec54abe62aad91b1d16666781194c29a7874bb7bbbda",
|
.url = "https://github.com/bobf/smtp_client.zig/archive/945554f22f025ba8a1efd01e56bda427bb4e22ca.tar.gz",
|
||||||
|
.hash = "1220de146446d0cae4396e346cb8283dd5e086491f8577ddbd5e03ad0928111d8bc6",
|
||||||
},
|
},
|
||||||
.httpz = .{
|
.httpz = .{
|
||||||
.url = "https://github.com/karlseguin/http.zig/archive/da9e944de0be6e5c67ca711dd238ce82d81558b4.tar.gz",
|
.url = "https://github.com/karlseguin/http.zig/archive/da9e944de0be6e5c67ca711dd238ce82d81558b4.tar.gz",
|
||||||
|
@ -10,13 +10,13 @@ const migrate = @import("database/migrate.zig");
|
|||||||
const rollback = @import("database/rollback.zig");
|
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 dump = @import("database/dump.zig");
|
const reflect = @import("database/reflect.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|dump]",
|
.usage_summary = "[migrate|rollback|create|drop|reflect]",
|
||||||
.full_text =
|
.full_text =
|
||||||
\\Manage the application's database.
|
\\Manage the application's database.
|
||||||
\\
|
\\
|
||||||
@ -40,13 +40,13 @@ pub fn run(
|
|||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
const Action = enum { migrate, rollback, create, drop, dump };
|
const Action = enum { migrate, rollback, create, drop, reflect };
|
||||||
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 },
|
||||||
.{ "dump", .dump },
|
.{ "reflect", .reflect },
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = if (main_options.positionals.len > 0)
|
const action = if (main_options.positionals.len > 0)
|
||||||
@ -74,7 +74,7 @@ pub fn run(
|
|||||||
.rollback => rollback.run(alloc, cwd, sub_args, options, T, main_options),
|
.rollback => rollback.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
.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),
|
||||||
.dump => dump.run(alloc, cwd, sub_args, options, T, main_options),
|
.reflect => reflect.run(alloc, cwd, sub_args, options, T, main_options),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ pub fn run(
|
|||||||
\\
|
\\
|
||||||
\\Example:
|
\\Example:
|
||||||
\\
|
\\
|
||||||
\\ jetzig database dump
|
\\ jetzig database reflect
|
||||||
\\
|
\\
|
||||||
, .{});
|
, .{});
|
||||||
|
|
||||||
@ -30,6 +30,6 @@ pub fn run(
|
|||||||
"zig",
|
"zig",
|
||||||
"build",
|
"build",
|
||||||
util.environmentBuildOption(main_options.options.environment),
|
util.environmentBuildOption(main_options.options.environment),
|
||||||
"jetzig:database:dump",
|
"jetzig:database:reflect",
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -104,6 +104,14 @@ pub fn run(
|
|||||||
&[_]Replace{.{ .from = "jetzig-demo", .to = project_name }},
|
&[_]Replace{.{ .from = "jetzig-demo", .to = project_name }},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try copySourceFile(
|
||||||
|
allocator,
|
||||||
|
install_dir,
|
||||||
|
"demo/config/database.zig",
|
||||||
|
"config/database.zig",
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
try copySourceFile(
|
try copySourceFile(
|
||||||
allocator,
|
allocator,
|
||||||
install_dir,
|
install_dir,
|
||||||
|
@ -37,8 +37,7 @@ pub fn run(
|
|||||||
try util.execCommand(allocator, &.{
|
try util.execCommand(allocator, &.{
|
||||||
"zig",
|
"zig",
|
||||||
"build",
|
"build",
|
||||||
"-e",
|
"-Denvironment=testing",
|
||||||
"testing",
|
|
||||||
"jetzig:test",
|
"jetzig:test",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
1
demo/.gitignore
vendored
1
demo/.gitignore
vendored
@ -3,3 +3,4 @@ zig-out/
|
|||||||
static/
|
static/
|
||||||
src/app/views/**/.*.zig
|
src/app/views/**/.*.zig
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
log/
|
||||||
|
@ -6,7 +6,7 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
|||||||
|
|
||||||
const session = try request.session();
|
const session = try request.session();
|
||||||
|
|
||||||
if (try session.get("message")) |message| {
|
if (session.get("message")) |message| {
|
||||||
try root.put("message", message);
|
try root.put("message", message);
|
||||||
} else {
|
} else {
|
||||||
try root.put("message", data.string("No message saved yet"));
|
try root.put("message", data.string("No message saved yet"));
|
||||||
|
48
demo/src/config/database.zig
Normal file
48
demo/src/config/database.zig
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
pub const database = .{
|
||||||
|
// Null adapter fails when a database call is invoked.
|
||||||
|
.development = .{
|
||||||
|
.adapter = .null,
|
||||||
|
},
|
||||||
|
.testing = .{
|
||||||
|
.adapter = .null,
|
||||||
|
},
|
||||||
|
.production = .{
|
||||||
|
.adapter = .null,
|
||||||
|
},
|
||||||
|
// PostgreSQL adapter configuration.
|
||||||
|
//
|
||||||
|
// All options except `adapter` can be configured using environment variables:
|
||||||
|
//
|
||||||
|
// * JETQUERY_HOSTNAME
|
||||||
|
// * JETQUERY_PORT
|
||||||
|
// * JETQUERY_USERNAME
|
||||||
|
// * JETQUERY_PASSWORD
|
||||||
|
// * JETQUERY_DATABASE
|
||||||
|
//
|
||||||
|
// .testing = .{
|
||||||
|
// .adapter = .postgresql,
|
||||||
|
// .hostname = "localhost",
|
||||||
|
// .port = 5432,
|
||||||
|
// .username = "postgres",
|
||||||
|
// .password = "password",
|
||||||
|
// .database = "myapp_testing",
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// .development = .{
|
||||||
|
// .adapter = .postgresql,
|
||||||
|
// .hostname = "localhost",
|
||||||
|
// .port = 5432,
|
||||||
|
// .username = "postgres",
|
||||||
|
// .password = "password",
|
||||||
|
// .database = "myapp_development",
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// .production = .{
|
||||||
|
// .adapter = .postgresql,
|
||||||
|
// .hostname = "localhost",
|
||||||
|
// .port = 5432,
|
||||||
|
// .username = "postgres",
|
||||||
|
// .password = "password",
|
||||||
|
// .database = "myapp_production",
|
||||||
|
// },
|
||||||
|
};
|
@ -14,7 +14,7 @@ const production_drop_failure_message = "To drop a production database, " ++
|
|||||||
|
|
||||||
const environment = jetzig.build_options.environment;
|
const environment = jetzig.build_options.environment;
|
||||||
const config = @field(jetquery.config.database, @tagName(environment));
|
const config = @field(jetquery.config.database, @tagName(environment));
|
||||||
const Action = enum { migrate, rollback, create, drop, dump };
|
const Action = enum { migrate, rollback, create, drop, reflect };
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
@ -35,7 +35,7 @@ pub fn main() !void {
|
|||||||
.{ "rollback", .rollback },
|
.{ "rollback", .rollback },
|
||||||
.{ "create", .create },
|
.{ "create", .create },
|
||||||
.{ "drop", .drop },
|
.{ "drop", .drop },
|
||||||
.{ "dump", .dump },
|
.{ "reflect", .reflect },
|
||||||
});
|
});
|
||||||
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedDatabaseArgument;
|
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedDatabaseArgument;
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ pub fn main() !void {
|
|||||||
try repo.dropDatabase(config.database, .{});
|
try repo.dropDatabase(config.database, .{});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.dump => {
|
.reflect => {
|
||||||
var cwd = try jetzig.util.detectJetzigProjectDir();
|
var cwd = try jetzig.util.detectJetzigProjectDir();
|
||||||
defer cwd.close();
|
defer cwd.close();
|
||||||
|
|
||||||
@ -99,12 +99,12 @@ pub fn main() !void {
|
|||||||
,
|
,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const dump = try reflect.generateSchema();
|
const schema = try reflect.generateSchema();
|
||||||
const path = try cwd.realpathAlloc(
|
const path = try cwd.realpathAlloc(
|
||||||
allocator,
|
allocator,
|
||||||
try std.fs.path.join(allocator, &.{ "src", "app", "database", "Schema.zig" }),
|
try std.fs.path.join(allocator, &.{ "src", "app", "database", "Schema.zig" }),
|
||||||
);
|
);
|
||||||
try jetzig.util.createFile(path, dump);
|
try jetzig.util.createFile(path, schema);
|
||||||
std.log.info("Database schema written to `{s}`.", .{path});
|
std.log.info("Database schema written to `{s}`.", .{path});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ fn migrationsRepo(action: Action, allocator: std.mem.Allocator) !MigrationsRepo
|
|||||||
.admin = switch (action) {
|
.admin = switch (action) {
|
||||||
.migrate, .rollback => false,
|
.migrate, .rollback => false,
|
||||||
.create, .drop => true,
|
.create, .drop => true,
|
||||||
.dump => unreachable, // We use a separate repo for schema reflection.
|
.reflect => unreachable, // We use a separate repo for schema reflection.
|
||||||
},
|
},
|
||||||
.context = .migration,
|
.context = .migration,
|
||||||
},
|
},
|
||||||
|
@ -97,10 +97,8 @@ pub const job_worker_threads: usize = 1;
|
|||||||
/// milliseconds.
|
/// milliseconds.
|
||||||
pub const job_worker_sleep_interval_ms: usize = 10;
|
pub const job_worker_sleep_interval_ms: usize = 10;
|
||||||
|
|
||||||
/// Database Schema.
|
/// Database Schema. Set to `@import("Schema")` to load `src/app/database/Schema.zig`.
|
||||||
pub const Schema: type = struct {
|
pub const Schema: type = struct {};
|
||||||
pub const _null = struct {}; // https://github.com/ziglang/zig/pull/21331
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Key-value store options. Set backend to `.file` to use a file-based store.
|
/// Key-value store options. Set backend to `.file` to use a file-based store.
|
||||||
/// When using `.file` backend, you must also set `.file_options`.
|
/// When using `.file` backend, you must also set `.file_options`.
|
||||||
@ -166,7 +164,7 @@ pub const force_development_email_delivery = false;
|
|||||||
/// Reconciles a configuration value from user-defined values and defaults provided by Jetzig.
|
/// Reconciles a configuration value from user-defined values and defaults provided by Jetzig.
|
||||||
pub fn get(T: type, comptime key: []const u8) T {
|
pub fn get(T: type, comptime key: []const u8) T {
|
||||||
const self = @This();
|
const self = @This();
|
||||||
if (!@hasDecl(self, key)) @panic("Unknown config option: " ++ key);
|
if (!@hasDecl(self, key)) @compileError("Unknown config option: " ++ key);
|
||||||
|
|
||||||
if (@hasDecl(root, "jetzig_options") and @hasDecl(root.jetzig_options, key)) {
|
if (@hasDecl(root, "jetzig_options") and @hasDecl(root.jetzig_options, key)) {
|
||||||
return @field(root.jetzig_options, key);
|
return @field(root.jetzig_options, key);
|
||||||
|
@ -10,10 +10,10 @@ pub fn Query(comptime model: anytype) type {
|
|||||||
return jetzig.jetquery.Query(adapter, Schema, model);
|
return jetzig.jetquery.Query(adapter, Schema, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn repo(allocator: std.mem.Allocator, app: *const jetzig.App) !Repo {
|
pub fn repo(allocator: std.mem.Allocator, app: anytype) !Repo {
|
||||||
// XXX: Is this terrible ?
|
// XXX: Is this terrible ?
|
||||||
const Callback = struct {
|
const Callback = struct {
|
||||||
var jetzig_app: *const jetzig.App = undefined;
|
var jetzig_app: @TypeOf(app) = undefined;
|
||||||
pub fn callbackFn(event: jetzig.jetquery.events.Event) !void {
|
pub fn callbackFn(event: jetzig.jetquery.events.Event) !void {
|
||||||
try eventCallback(event, jetzig_app);
|
try eventCallback(event, jetzig_app);
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ pub fn repo(allocator: std.mem.Allocator, app: *const jetzig.App) !Repo {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eventCallback(event: jetzig.jetquery.events.Event, app: *const jetzig.App) !void {
|
fn eventCallback(event: jetzig.jetquery.events.Event, app: anytype) !void {
|
||||||
try app.server.logger.logSql(event);
|
try app.server.logger.logSql(event);
|
||||||
if (event.err) |err| {
|
if (event.err) |err| {
|
||||||
try app.server.logger.ERROR("[database] {?s}", .{err.message});
|
try app.server.logger.ERROR("[database] {?s}", .{err.message});
|
||||||
|
@ -47,8 +47,6 @@ pub fn reset(self: *Self) !void {
|
|||||||
|
|
||||||
/// Free allocated memory.
|
/// Free allocated memory.
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
if (self.state != .parsed) return;
|
|
||||||
|
|
||||||
self.data.deinit();
|
self.data.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +76,7 @@ pub fn getT(
|
|||||||
|
|
||||||
/// Put a value into the session.
|
/// Put a value into the session.
|
||||||
pub fn put(self: *Self, key: []const u8, value: anytype) !void {
|
pub fn put(self: *Self, key: []const u8, value: anytype) !void {
|
||||||
if (self.state != .parsed) return error.UnparsedSessionCookie;
|
std.debug.assert(self.state == .parsed);
|
||||||
|
|
||||||
switch (self.data.value.?.*) {
|
switch (self.data.value.?.*) {
|
||||||
.object => |*object| {
|
.object => |*object| {
|
||||||
@ -90,10 +88,9 @@ pub fn put(self: *Self, key: []const u8, value: anytype) !void {
|
|||||||
try self.save();
|
try self.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes true if a value was removed
|
// Returns `true` if a value was removed and `false` otherwise.
|
||||||
// and false otherwise
|
|
||||||
pub fn remove(self: *Self, key: []const u8) !bool {
|
pub fn remove(self: *Self, key: []const u8) !bool {
|
||||||
if (self.state != .parsed) return error.UnparsedSessionCookie;
|
std.debug.assert(self.state == .parsed);
|
||||||
|
|
||||||
// copied from `get()`
|
// copied from `get()`
|
||||||
const result = switch (self.data.value.?.*) {
|
const result = switch (self.data.value.?.*) {
|
||||||
@ -191,7 +188,7 @@ test "put and get session key/value" {
|
|||||||
|
|
||||||
try session.parse();
|
try session.parse();
|
||||||
try session.put("foo", data.string("bar"));
|
try session.put("foo", data.string("bar"));
|
||||||
var value = (try session.get("foo")).?;
|
var value = (session.get("foo")).?;
|
||||||
try std.testing.expectEqualStrings(try value.toString(), "bar");
|
try std.testing.expectEqualStrings(try value.toString(), "bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,11 +207,11 @@ test "remove session key/value" {
|
|||||||
|
|
||||||
try session.parse();
|
try session.parse();
|
||||||
try session.put("foo", data.string("bar"));
|
try session.put("foo", data.string("bar"));
|
||||||
var value = (try session.get("foo")).?;
|
var value = (session.get("foo")).?;
|
||||||
try std.testing.expectEqualStrings(try value.toString(), "bar");
|
try std.testing.expectEqualStrings(try value.toString(), "bar");
|
||||||
|
|
||||||
try std.testing.expectEqual(true, try session.remove("foo"));
|
try std.testing.expectEqual(true, try session.remove("foo"));
|
||||||
try std.testing.expectEqual(null, try session.get("foo"));
|
try std.testing.expectEqual(null, session.get("foo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "get value from parsed/decrypted cookie" {
|
test "get value from parsed/decrypted cookie" {
|
||||||
@ -231,7 +228,7 @@ test "get value from parsed/decrypted cookie" {
|
|||||||
defer session.deinit();
|
defer session.deinit();
|
||||||
|
|
||||||
try session.parse();
|
try session.parse();
|
||||||
var value = (try session.get("foo")).?;
|
var value = (session.get("foo")).?;
|
||||||
try std.testing.expectEqualStrings("bar", try value.toString());
|
try std.testing.expectEqualStrings("bar", try value.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@ const jetzig = @import("../../jetzig.zig");
|
|||||||
|
|
||||||
const TestLogger = @This();
|
const TestLogger = @This();
|
||||||
|
|
||||||
enabled: bool = false,
|
mode: enum { stream, file, disable },
|
||||||
|
file: ?std.fs.File = null,
|
||||||
|
|
||||||
pub fn TRACE(self: TestLogger, comptime message: []const u8, args: anytype) !void {
|
pub fn TRACE(self: TestLogger, comptime message: []const u8, args: anytype) !void {
|
||||||
try self.log(.TRACE, message, args);
|
try self.log(.TRACE, message, args);
|
||||||
@ -55,7 +56,10 @@ pub fn log(
|
|||||||
comptime message: []const u8,
|
comptime message: []const u8,
|
||||||
args: anytype,
|
args: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
if (self.enabled) {
|
const template = "-- test logger: " ++ @tagName(level) ++ " " ++ message ++ "\n";
|
||||||
std.debug.print("-- test logger: " ++ @tagName(level) ++ " " ++ message ++ "\n", args);
|
switch (self.mode) {
|
||||||
|
.stream => std.debug.print(template, args),
|
||||||
|
.file => try self.file.?.writer().print(template, args),
|
||||||
|
.disable => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ const jetzig = @import("../jetzig.zig");
|
|||||||
|
|
||||||
pub const HtmxMiddleware = @import("middleware/HtmxMiddleware.zig");
|
pub const HtmxMiddleware = @import("middleware/HtmxMiddleware.zig");
|
||||||
pub const CompressionMiddleware = @import("middleware/CompressionMiddleware.zig");
|
pub const CompressionMiddleware = @import("middleware/CompressionMiddleware.zig");
|
||||||
|
pub const AuthMiddleware = @import("middleware/AuthMiddleware.zig");
|
||||||
|
|
||||||
const RouteOptions = struct {
|
const RouteOptions = struct {
|
||||||
content: ?[]const u8 = null,
|
content: ?[]const u8 = null,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const jetzig = @import("jetzig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
pub const middleware_name = "auth";
|
pub const middleware_name = "auth";
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ const user_model = jetzig.config.get(jetzig.auth.AuthOptions, "auth").user_model
|
|||||||
/// Define any custom data fields you want to store here. Assigning to these fields in the `init`
|
/// Define any custom data fields you want to store here. Assigning to these fields in the `init`
|
||||||
/// function allows you to access them in the `beforeRequest` and `afterRequest` functions, where
|
/// function allows you to access them in the `beforeRequest` and `afterRequest` functions, where
|
||||||
/// they can also be modified.
|
/// they can also be modified.
|
||||||
user: ?@TypeOf(jetzig.database.Query(user_model).find(0)).ResultType(),
|
user: ?@TypeOf(jetzig.database.Query(user_model).find(0)).ResultType,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -12,6 +12,11 @@ store: *jetzig.kv.Store,
|
|||||||
cache: *jetzig.kv.Store,
|
cache: *jetzig.kv.Store,
|
||||||
job_queue: *jetzig.kv.Store,
|
job_queue: *jetzig.kv.Store,
|
||||||
multipart_boundary: ?[]const u8 = null,
|
multipart_boundary: ?[]const u8 = null,
|
||||||
|
logger: jetzig.loggers.Logger,
|
||||||
|
server: Server,
|
||||||
|
repo: *jetzig.database.Repo,
|
||||||
|
|
||||||
|
const Server = struct { logger: jetzig.loggers.Logger };
|
||||||
|
|
||||||
const initHook = jetzig.root.initHook;
|
const initHook = jetzig.root.initHook;
|
||||||
|
|
||||||
@ -31,20 +36,39 @@ pub fn init(allocator: std.mem.Allocator, routes_module: type) !App {
|
|||||||
const arena = try allocator.create(std.heap.ArenaAllocator);
|
const arena = try allocator.create(std.heap.ArenaAllocator);
|
||||||
arena.* = std.heap.ArenaAllocator.init(allocator);
|
arena.* = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
|
||||||
return .{
|
var dir = try std.fs.cwd().makeOpenPath("log", .{});
|
||||||
|
const file = try dir.createFile("test.log", .{ .exclusive = false, .truncate = false });
|
||||||
|
|
||||||
|
const logger = jetzig.loggers.Logger{
|
||||||
|
.test_logger = jetzig.loggers.TestLogger{ .mode = .file, .file = file },
|
||||||
|
};
|
||||||
|
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
const app = try alloc.create(App);
|
||||||
|
const repo = try alloc.create(jetzig.database.Repo);
|
||||||
|
|
||||||
|
app.* = App{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.routes = &routes_module.routes,
|
.routes = &routes_module.routes,
|
||||||
.store = try createStore(arena.allocator()),
|
.store = try createStore(arena.allocator()),
|
||||||
.cache = try createStore(arena.allocator()),
|
.cache = try createStore(arena.allocator()),
|
||||||
.job_queue = try createStore(arena.allocator()),
|
.job_queue = try createStore(arena.allocator()),
|
||||||
|
.logger = logger,
|
||||||
|
.server = .{ .logger = logger },
|
||||||
|
.repo = repo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
repo.* = try jetzig.database.repo(alloc, app.*);
|
||||||
|
|
||||||
|
return app.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Free allocated resources for test app.
|
/// Free allocated resources for test app.
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.allocator.destroy(self.arena);
|
self.allocator.destroy(self.arena);
|
||||||
|
if (self.logger.test_logger.file) |file| file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const RequestOptions = struct {
|
const RequestOptions = struct {
|
||||||
@ -85,19 +109,21 @@ pub fn request(
|
|||||||
const options = try buildOptions(allocator, self, args);
|
const options = try buildOptions(allocator, self, args);
|
||||||
const routes = try jetzig.App.createRoutes(allocator, self.routes);
|
const routes = try jetzig.App.createRoutes(allocator, self.routes);
|
||||||
|
|
||||||
const logger = jetzig.loggers.Logger{ .test_logger = jetzig.loggers.TestLogger{} };
|
|
||||||
var log_queue = jetzig.loggers.LogQueue.init(allocator);
|
var log_queue = jetzig.loggers.LogQueue.init(allocator);
|
||||||
|
|
||||||
// We init the `std.process.EnvMap` directly here (instead of calling `std.process.getEnvMap`
|
// We init the `std.process.EnvMap` directly here (instead of calling `std.process.getEnvMap`
|
||||||
// to ensure that tests run in a clean environment. Users can manually add items to the
|
// to ensure that tests run in a clean environment. Users can manually add items to the
|
||||||
// environment within a test if required.
|
// environment within a test if required.
|
||||||
const vars = jetzig.Environment.Vars{ .env_map = std.process.EnvMap.init(allocator) };
|
const vars = jetzig.Environment.Vars{ .env_map = std.process.EnvMap.init(allocator) };
|
||||||
var server = jetzig.http.Server{
|
var server = jetzig.http.Server{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.logger = logger,
|
.logger = self.logger,
|
||||||
.env = .{
|
.env = .{
|
||||||
|
.parent_allocator = undefined,
|
||||||
|
.arena = undefined,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.vars = vars,
|
.vars = vars,
|
||||||
.logger = logger,
|
.logger = self.logger,
|
||||||
.bind = undefined,
|
.bind = undefined,
|
||||||
.port = undefined,
|
.port = undefined,
|
||||||
.detach = false,
|
.detach = false,
|
||||||
@ -114,7 +140,7 @@ pub fn request(
|
|||||||
.cache = self.cache,
|
.cache = self.cache,
|
||||||
.job_queue = self.job_queue,
|
.job_queue = self.job_queue,
|
||||||
.global = undefined,
|
.global = undefined,
|
||||||
.repo = undefined, // TODO - database test helpers
|
.repo = self.repo,
|
||||||
};
|
};
|
||||||
|
|
||||||
try server.decodeStaticParams();
|
try server.decodeStaticParams();
|
||||||
@ -154,7 +180,8 @@ pub fn params(self: App, args: anytype) []Param {
|
|||||||
const allocator = self.arena.allocator();
|
const allocator = self.arena.allocator();
|
||||||
var array = std.ArrayList(Param).init(allocator);
|
var array = std.ArrayList(Param).init(allocator);
|
||||||
inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| {
|
inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |field| {
|
||||||
array.append(.{ .key = field.name, .value = @field(args, field.name) }) catch @panic("OOM");
|
const value = coerceString(allocator, @field(args, field.name));
|
||||||
|
array.append(.{ .key = field.name, .value = value }) catch @panic("OOM");
|
||||||
}
|
}
|
||||||
return array.toOwnedSlice() catch @panic("OOM");
|
return array.toOwnedSlice() catch @panic("OOM");
|
||||||
}
|
}
|
||||||
@ -333,3 +360,14 @@ fn buildHeaders(allocator: std.mem.Allocator, args: anytype) ![]const jetzig.tes
|
|||||||
}
|
}
|
||||||
return try headers.toOwnedSlice();
|
return try headers.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn coerceString(allocator: std.mem.Allocator, value: anytype) []const u8 {
|
||||||
|
return switch (@typeInfo(@TypeOf(value))) {
|
||||||
|
.int,
|
||||||
|
.float,
|
||||||
|
.comptime_int,
|
||||||
|
.comptime_float,
|
||||||
|
=> std.fmt.allocPrint(allocator, "{d}", .{value}) catch @panic("OOM"),
|
||||||
|
else => value, // TODO: Handle more complex types - arrays, objects, etc.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -7,6 +7,8 @@ pub const std_options = std.Options{
|
|||||||
.logFn = log,
|
.logFn = log,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const jetzig_options = @import("main").jetzig_options;
|
||||||
|
|
||||||
pub fn log(
|
pub fn log(
|
||||||
comptime message_level: std.log.Level,
|
comptime message_level: std.log.Level,
|
||||||
comptime scope: @Type(.enum_literal),
|
comptime scope: @Type(.enum_literal),
|
||||||
@ -144,14 +146,14 @@ const Test = struct {
|
|||||||
|
|
||||||
fn printSkipped(self: Test, writer: anytype) !void {
|
fn printSkipped(self: Test, writer: anytype) !void {
|
||||||
try writer.print(
|
try writer.print(
|
||||||
jetzig.colors.yellow("[SKIP]") ++ name_template,
|
"[" ++ jetzig.colors.yellow("SKIP") ++ "]" ++ name_template,
|
||||||
.{ self.module orelse "tests", self.name },
|
.{ self.module orelse "tests", self.name },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn printLeaked(self: Test, writer: anytype) !void {
|
fn printLeaked(self: Test, writer: anytype) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
try writer.print(jetzig.colors.red(" [LEAKED]"), .{});
|
try writer.print("[" ++ jetzig.colors.red("LEAKED") ++ "] ", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn printDuration(self: Test, writer: anytype) !void {
|
fn printDuration(self: Test, writer: anytype) !void {
|
||||||
@ -251,6 +253,7 @@ fn printSummary(tests: []const Test, start: i128) !void {
|
|||||||
} else {
|
} else {
|
||||||
try writer.print(jetzig.colors.red(" FAIL ") ++ "\n", .{});
|
try writer.print(jetzig.colors.red(" FAIL ") ++ "\n", .{});
|
||||||
try writer.print(jetzig.colors.red(" ▔▔▔▔") ++ "\n", .{});
|
try writer.print(jetzig.colors.red(" ▔▔▔▔") ++ "\n", .{});
|
||||||
|
try writer.print("Server logs: " ++ jetzig.colors.cyan("log/test.log") ++ "\n\n", .{});
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user