mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
Merge pull request #47 from jetzig-framework/auto-routing-markdown
Auto routing markdown
This commit is contained in:
commit
800b72eeb9
@ -7,8 +7,8 @@
|
|||||||
.hash = "1220bfc5c29bc930b5a524c210712ef65c6cde6770450899bef01164a3089e6707fa",
|
.hash = "1220bfc5c29bc930b5a524c210712ef65c6cde6770450899bef01164a3089e6707fa",
|
||||||
},
|
},
|
||||||
.zmpl = .{
|
.zmpl = .{
|
||||||
.url = "https://github.com/jetzig-framework/zmpl/archive/9e2df115c9f17e92fb60a4d09bf55ea48d0388b0.tar.gz",
|
.url = "https://github.com/jetzig-framework/zmpl/archive/ffdbd3767da28d5ab07c1a786ea778152d2f79f6.tar.gz",
|
||||||
.hash = "1220820b7f5f3e01b7dc976d32cf9ff65d44dee2642533f4b8104e19a824e802d7e1",
|
.hash = "12202cf05fd4ba2482a9b4b89c632b435310a76ac501b7a3d87dfd41006748dd138d",
|
||||||
},
|
},
|
||||||
.args = .{
|
.args = .{
|
||||||
.url = "https://github.com/MasterQ32/zig-args/archive/01d72b9a0128c474aeeb9019edd48605fa6d95f7.tar.gz",
|
.url = "https://github.com/MasterQ32/zig-args/archive/01d72b9a0128c474aeeb9019edd48605fa6d95f7.tar.gz",
|
||||||
|
@ -6,9 +6,9 @@ pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8) !v
|
|||||||
_ = args;
|
_ = args;
|
||||||
_ = cwd;
|
_ = cwd;
|
||||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
var secret: [44]u8 = undefined;
|
var secret: [128]u8 = undefined;
|
||||||
|
|
||||||
for (0..44) |index| {
|
for (0..128) |index| {
|
||||||
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
|
secret[index] = chars[std.crypto.random.intRangeAtMost(u8, 0, chars.len)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ fn generateRoutesForView(self: *Self, views_dir: std.fs.Dir, path: []const u8) !
|
|||||||
var json_buf = std.ArrayList(u8).init(self.allocator);
|
var json_buf = std.ArrayList(u8).init(self.allocator);
|
||||||
defer json_buf.deinit();
|
defer json_buf.deinit();
|
||||||
const json_writer = json_buf.writer();
|
const json_writer = json_buf.writer();
|
||||||
try item.toJson(json_writer);
|
try item.toJson(json_writer, false, 0);
|
||||||
var encoded_buf = std.ArrayList(u8).init(self.allocator);
|
var encoded_buf = std.ArrayList(u8).init(self.allocator);
|
||||||
defer encoded_buf.deinit();
|
defer encoded_buf.deinit();
|
||||||
const writer = encoded_buf.writer();
|
const writer = encoded_buf.writer();
|
||||||
|
@ -129,7 +129,9 @@ fn renderMarkdown(
|
|||||||
jetzig_options.markdown_fragments
|
jetzig_options.markdown_fragments
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
const content = try jetzig.markdown.render(allocator, &route, fragments) orelse return null;
|
const path = try std.mem.join(allocator, "/", &[_][]const u8{ route.uri_path, @tagName(route.action) });
|
||||||
|
defer allocator.free(path);
|
||||||
|
const content = try jetzig.markdown.render(allocator, path, fragments) orelse return null;
|
||||||
|
|
||||||
if (route.layout) |layout_name| {
|
if (route.layout) |layout_name| {
|
||||||
try view.data.addConst("jetzig_view", view.data.string(route.name));
|
try view.data.addConst("jetzig_view", view.data.string(route.name));
|
||||||
|
@ -8,12 +8,13 @@ const Environment = @This();
|
|||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub const EnvironmentName = enum { development, production };
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
help: bool = false,
|
help: bool = false,
|
||||||
bind: []const u8 = "127.0.0.1",
|
bind: []const u8 = "127.0.0.1",
|
||||||
port: u16 = 8080,
|
port: u16 = 8080,
|
||||||
// TODO:
|
environment: EnvironmentName = .development,
|
||||||
// environment: []const u8 = "development",
|
|
||||||
log: []const u8 = "-",
|
log: []const u8 = "-",
|
||||||
@"log-error": []const u8 = "-",
|
@"log-error": []const u8 = "-",
|
||||||
@"log-level": jetzig.loggers.LogLevel = .INFO,
|
@"log-level": jetzig.loggers.LogLevel = .INFO,
|
||||||
@ -24,8 +25,7 @@ const Options = struct {
|
|||||||
.h = "help",
|
.h = "help",
|
||||||
.b = "bind",
|
.b = "bind",
|
||||||
.p = "port",
|
.p = "port",
|
||||||
// TODO:
|
.e = "environment",
|
||||||
// .e = "environment",
|
|
||||||
.d = "detach",
|
.d = "detach",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,8 +35,7 @@ const Options = struct {
|
|||||||
.option_docs = .{
|
.option_docs = .{
|
||||||
.bind = "IP address/hostname to bind to (default: 127.0.0.1)",
|
.bind = "IP address/hostname to bind to (default: 127.0.0.1)",
|
||||||
.port = "Port to listen on (default: 8080)",
|
.port = "Port to listen on (default: 8080)",
|
||||||
// TODO:
|
.environment = "Set the server environment. Must be one of: { development, production } (default: development)",
|
||||||
// .environment = "Load an environment configuration from src/app/environments/<environment>.zig",
|
|
||||||
.log = "Path to log file. Use '-' for stdout (default: '-')",
|
.log = "Path to log file. Use '-' for stdout (default: '-')",
|
||||||
.@"log-error" =
|
.@"log-error" =
|
||||||
\\Optional path to separate error log file. Use '-' for stderr. If omitted, errors are logged to the location specified by the `log` option (or stderr if `log` is '-').
|
\\Optional path to separate error log file. Use '-' for stderr. If omitted, errors are logged to the location specified by the `log` option (or stderr if `log` is '-').
|
||||||
@ -94,9 +93,11 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
|||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const environment = options.options.environment;
|
||||||
|
|
||||||
// TODO: Generate nonce per session - do research to confirm correct best practice.
|
// TODO: Generate nonce per session - do research to confirm correct best practice.
|
||||||
const secret_len = jetzig.http.Session.Cipher.key_length + jetzig.http.Session.Cipher.nonce_length;
|
const secret_len = jetzig.http.Session.Cipher.key_length + jetzig.http.Session.Cipher.nonce_length;
|
||||||
const secret = try self.getSecret(&logger, secret_len);
|
const secret = (try self.getSecret(&logger, secret_len, environment))[0..secret_len];
|
||||||
|
|
||||||
if (secret.len != secret_len) {
|
if (secret.len != secret_len) {
|
||||||
try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len });
|
try logger.ERROR("Expected secret length: {}, found: {}.", .{ secret_len, secret.len });
|
||||||
@ -110,6 +111,7 @@ pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
|
|||||||
.bind = try self.allocator.dupe(u8, options.options.bind),
|
.bind = try self.allocator.dupe(u8, options.options.bind),
|
||||||
.port = options.options.port,
|
.port = options.options.port,
|
||||||
.detach = options.options.detach,
|
.detach = options.options.detach,
|
||||||
|
.environment = environment,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +134,18 @@ fn getLogFile(stream: enum { stdout, stderr }, options: Options) !std.fs.File {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u10) ![]const u8 {
|
fn getSecret(self: Environment, logger: *jetzig.loggers.Logger, comptime len: u10, environment: EnvironmentName) ![]const u8 {
|
||||||
return std.process.getEnvVarOwned(self.allocator, "JETZIG_SECRET") catch |err| {
|
const env_var = "JETZIG_SECRET";
|
||||||
|
|
||||||
|
return std.process.getEnvVarOwned(self.allocator, env_var) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.EnvironmentVariableNotFound => {
|
error.EnvironmentVariableNotFound => {
|
||||||
// TODO: Make this a failure when running in non-development mode.
|
if (environment != .development) {
|
||||||
|
try logger.ERROR("Environment variable `{s}` must be defined in production mode.", .{env_var});
|
||||||
|
try logger.ERROR("Run `jetzig generate secret` to generate an appropriate value.", .{});
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const secret = try jetzig.util.generateSecret(self.allocator, len);
|
const secret = try jetzig.util.generateSecret(self.allocator, len);
|
||||||
try logger.WARN(
|
try logger.WARN(
|
||||||
"Running in development mode, using auto-generated cookie encryption key: {s}",
|
"Running in development mode, using auto-generated cookie encryption key: {s}",
|
||||||
|
@ -10,5 +10,6 @@ pub const Headers = @import("http/Headers.zig");
|
|||||||
pub const Query = @import("http/Query.zig");
|
pub const Query = @import("http/Query.zig");
|
||||||
pub const Path = @import("http/Path.zig");
|
pub const Path = @import("http/Path.zig");
|
||||||
pub const status_codes = @import("http/status_codes.zig");
|
pub const status_codes = @import("http/status_codes.zig");
|
||||||
|
pub const StatusCode = status_codes.StatusCode;
|
||||||
pub const middleware = @import("http/middleware.zig");
|
pub const middleware = @import("http/middleware.zig");
|
||||||
pub const mime = @import("http/mime.zig");
|
pub const mime = @import("http/mime.zig");
|
||||||
|
@ -327,6 +327,34 @@ pub fn fmtMethod(self: *const Self, colorized: bool) []const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a status code appropriately for the current request format.
|
||||||
|
/// e.g. `.HTML` => `404 Not Found`
|
||||||
|
/// `.JSON` => `{ "message": "Not Found", "status": "404" }`
|
||||||
|
pub fn formatStatus(self: *Self, status_code: jetzig.http.StatusCode) ![]const u8 {
|
||||||
|
const status = jetzig.http.status_codes.get(status_code);
|
||||||
|
|
||||||
|
return switch (self.requestFormat()) {
|
||||||
|
.JSON => try std.json.stringifyAlloc(self.allocator, .{
|
||||||
|
.message = status.getMessage(),
|
||||||
|
.status = status.getCode(),
|
||||||
|
}, .{}),
|
||||||
|
.HTML, .UNKNOWN => status.getFormatted(.{ .linebreak = true }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setResponse(
|
||||||
|
self: *Self,
|
||||||
|
rendered_view: jetzig.http.Server.RenderedView,
|
||||||
|
options: struct { content_type: ?[]const u8 = null },
|
||||||
|
) void {
|
||||||
|
self.response.content = rendered_view.content;
|
||||||
|
self.response.status_code = rendered_view.view.status_code;
|
||||||
|
self.response.content_type = options.content_type orelse switch (self.requestFormat()) {
|
||||||
|
.HTML, .UNKNOWN => "text/html",
|
||||||
|
.JSON => "application/json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if a given route matches the current request.
|
// Determine if a given route matches the current request.
|
||||||
pub fn match(self: *Self, route: jetzig.views.Route) !bool {
|
pub fn match(self: *Self, route: jetzig.views.Route) !bool {
|
||||||
return switch (self.method) {
|
return switch (self.method) {
|
||||||
|
@ -9,6 +9,7 @@ pub const ServerOptions = struct {
|
|||||||
port: u16,
|
port: u16,
|
||||||
secret: []const u8,
|
secret: []const u8,
|
||||||
detach: bool,
|
detach: bool,
|
||||||
|
environment: jetzig.Environment.EnvironmentName,
|
||||||
};
|
};
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
@ -48,7 +49,11 @@ pub fn listen(self: *Self) !void {
|
|||||||
|
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
|
|
||||||
try self.logger.INFO("Listening on http://{s}:{}", .{ self.options.bind, self.options.port });
|
try self.logger.INFO("Listening on http://{s}:{} [{s}]", .{
|
||||||
|
self.options.bind,
|
||||||
|
self.options.port,
|
||||||
|
@tagName(self.options.environment),
|
||||||
|
});
|
||||||
try self.processRequests();
|
try self.processRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,12 +113,12 @@ fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
|
|||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
|
|
||||||
const rendered = try self.renderInternalServerError(request, err);
|
const rendered = try self.renderInternalServerError(request, err);
|
||||||
setResponse(request, rendered, .{});
|
request.setResponse(rendered, .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (static_resource) |resource| {
|
if (static_resource) |resource| {
|
||||||
try renderStatic(resource, request.response);
|
try renderStatic(resource, request);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,20 +131,11 @@ fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setResponse(
|
fn renderStatic(resource: StaticResource, request: *jetzig.http.Request) !void {
|
||||||
request: *jetzig.http.Request,
|
request.setResponse(
|
||||||
rendered_view: RenderedView,
|
.{ .view = .{ .data = request.response_data }, .content = resource.content },
|
||||||
options: struct { content_type: []const u8 = "text/html" },
|
.{ .content_type = resource.mime_type },
|
||||||
) void {
|
);
|
||||||
request.response.content = rendered_view.content;
|
|
||||||
request.response.status_code = rendered_view.view.status_code;
|
|
||||||
request.response.content_type = options.content_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderStatic(resource: StaticResource, response: *jetzig.http.Response) !void {
|
|
||||||
response.status_code = .ok;
|
|
||||||
response.content = resource.content;
|
|
||||||
response.content_type = resource.mime_type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderHTML(
|
fn renderHTML(
|
||||||
@ -152,22 +148,75 @@ fn renderHTML(
|
|||||||
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, err);
|
||||||
setResponse(request, rendered_error, .{});
|
return request.setResponse(rendered_error, .{});
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
setResponse(request, rendered, .{});
|
return request.setResponse(rendered, .{});
|
||||||
return;
|
}
|
||||||
} else if (try jetzig.markdown.render(request.allocator, matched_route, null)) |markdown_content| {
|
}
|
||||||
const rendered = self.renderView(matched_route, request, null) catch |err| {
|
|
||||||
|
if (try self.renderMarkdown(request, route)) |rendered| {
|
||||||
|
return request.setResponse(rendered, .{});
|
||||||
|
} else {
|
||||||
|
return request.setResponse(try renderNotFound(request), .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderJSON(
|
||||||
|
self: *Self,
|
||||||
|
request: *jetzig.http.Request,
|
||||||
|
route: ?*jetzig.views.Route,
|
||||||
|
) !void {
|
||||||
|
if (route) |matched_route| {
|
||||||
|
var rendered = try self.renderView(matched_route, request, null);
|
||||||
|
var data = rendered.view.data;
|
||||||
|
|
||||||
|
if (data.value) |_| {} else _ = try data.object();
|
||||||
|
|
||||||
|
rendered.content = if (self.options.environment == .development)
|
||||||
|
try data.toPrettyJson()
|
||||||
|
else
|
||||||
|
try data.toJson();
|
||||||
|
|
||||||
|
request.setResponse(rendered, .{});
|
||||||
|
} else {
|
||||||
|
request.setResponse(try renderNotFound(request), .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderMarkdown(
|
||||||
|
self: *Self,
|
||||||
|
request: *jetzig.http.Request,
|
||||||
|
maybe_route: ?*jetzig.views.Route,
|
||||||
|
) !?RenderedView {
|
||||||
|
const route = maybe_route orelse {
|
||||||
|
// No route recognized, but we can still render a static markdown file if it matches the URI:
|
||||||
|
if (request.method != .GET) return null;
|
||||||
|
if (try jetzig.markdown.render(request.allocator, request.path.base_path, null)) |content| {
|
||||||
|
return .{
|
||||||
|
.view = jetzig.views.View{ .data = request.response_data, .status_code = .ok },
|
||||||
|
.content = content,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = try std.mem.join(
|
||||||
|
request.allocator,
|
||||||
|
"/",
|
||||||
|
&[_][]const u8{ route.uri_path, @tagName(route.action) },
|
||||||
|
);
|
||||||
|
const markdown_content = try jetzig.markdown.render(request.allocator, path, null) orelse
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var rendered = self.renderView(route, request, null) catch |err| {
|
||||||
if (isUnhandledError(err)) return err;
|
if (isUnhandledError(err)) return err;
|
||||||
const rendered_error = try self.renderInternalServerError(request, err);
|
return try self.renderInternalServerError(request, err);
|
||||||
setResponse(request, rendered_error, .{});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try addTemplateConstants(rendered.view, matched_route);
|
try addTemplateConstants(rendered.view, route);
|
||||||
|
|
||||||
if (request.getLayout(matched_route)) |layout_name| {
|
if (request.getLayout(route)) |layout_name| {
|
||||||
// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
|
// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
|
||||||
const prefixed_name = try std.mem.concat(
|
const prefixed_name = try std.mem.concat(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
@ -178,46 +227,16 @@ fn renderHTML(
|
|||||||
|
|
||||||
if (zmpl.manifest.find(prefixed_name)) |layout| {
|
if (zmpl.manifest.find(prefixed_name)) |layout| {
|
||||||
rendered.view.data.content = .{ .data = markdown_content };
|
rendered.view.data.content = .{ .data = markdown_content };
|
||||||
request.response.content = try layout.render(rendered.view.data);
|
rendered.content = try layout.render(rendered.view.data);
|
||||||
} else {
|
} else {
|
||||||
try self.logger.WARN("Unknown layout: {s}", .{layout_name});
|
try self.logger.WARN("Unknown layout: {s}", .{layout_name});
|
||||||
request.response.content = markdown_content;
|
rendered.content = markdown_content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.response.status_code = rendered.view.status_code;
|
return rendered;
|
||||||
request.response.content_type = "text/html";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.response.content = "";
|
pub const RenderedView = struct { view: jetzig.views.View, content: []const u8 };
|
||||||
request.response.status_code = .not_found;
|
|
||||||
request.response.content_type = "text/html";
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderJSON(
|
|
||||||
self: *Self,
|
|
||||||
request: *jetzig.http.Request,
|
|
||||||
route: ?*jetzig.views.Route,
|
|
||||||
) !void {
|
|
||||||
if (route) |matched_route| {
|
|
||||||
const rendered = try self.renderView(matched_route, request, null);
|
|
||||||
var data = rendered.view.data;
|
|
||||||
|
|
||||||
if (data.value) |_| {} else _ = try data.object();
|
|
||||||
try request.headers.append("Content-Type", "application/json");
|
|
||||||
|
|
||||||
request.response.content = try data.toJson();
|
|
||||||
request.response.status_code = rendered.view.status_code;
|
|
||||||
request.response.content_type = "application/json";
|
|
||||||
} else {
|
|
||||||
request.response.content = "";
|
|
||||||
request.response.status_code = .not_found;
|
|
||||||
request.response.content_type = "application/json";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderedView = struct { view: jetzig.views.View, content: []const u8 };
|
|
||||||
|
|
||||||
fn renderView(
|
fn renderView(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
@ -231,7 +250,7 @@ fn renderView(
|
|||||||
_ = route.render(route.*, request) catch |err| {
|
_ = route.render(route.*, request) catch |err| {
|
||||||
try self.logger.ERROR("Encountered error: {s}", .{@errorName(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 renderBadRequest(request);
|
||||||
return try self.renderInternalServerError(request, err);
|
return try self.renderInternalServerError(request, err);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -322,28 +341,38 @@ fn isBadHttpError(err: anyerror) bool {
|
|||||||
fn renderInternalServerError(self: *Self, request: *jetzig.http.Request, err: anyerror) !RenderedView {
|
fn renderInternalServerError(self: *Self, request: *jetzig.http.Request, err: anyerror) !RenderedView {
|
||||||
request.response_data.reset();
|
request.response_data.reset();
|
||||||
|
|
||||||
var object = try request.response_data.object();
|
try self.logger.ERROR("Encountered Error: {s}", .{@errorName(err)});
|
||||||
try object.put("error", request.response_data.string(@errorName(err)));
|
|
||||||
|
|
||||||
const stack = @errorReturnTrace();
|
const stack = @errorReturnTrace();
|
||||||
if (stack) |capture| try self.logStackTrace(capture, request);
|
if (stack) |capture| try self.logStackTrace(capture, request);
|
||||||
|
|
||||||
|
const status = .internal_server_error;
|
||||||
|
const content = try request.formatStatus(status);
|
||||||
return .{
|
return .{
|
||||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = .internal_server_error },
|
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },
|
||||||
.content = "Internal Server Error\n",
|
.content = content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderBadRequest(self: *Self, request: *jetzig.http.Request) !RenderedView {
|
fn renderNotFound(request: *jetzig.http.Request) !RenderedView {
|
||||||
_ = self;
|
|
||||||
request.response_data.reset();
|
request.response_data.reset();
|
||||||
|
|
||||||
var object = try request.response_data.object();
|
const status: jetzig.http.StatusCode = .not_found;
|
||||||
try object.put("error", request.response_data.string("Bad Request"));
|
const content = try request.formatStatus(status);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.view = jetzig.views.View{ .data = request.response_data, .status_code = .bad_request },
|
.view = .{ .data = request.response_data, .status_code = status },
|
||||||
.content = "Bad Request\n",
|
.content = content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderBadRequest(request: *jetzig.http.Request) !RenderedView {
|
||||||
|
request.response_data.reset();
|
||||||
|
|
||||||
|
const status: jetzig.http.StatusCode = .not_found;
|
||||||
|
const content = try request.formatStatus(status);
|
||||||
|
return .{
|
||||||
|
.view = jetzig.views.View{ .data = request.response_data, .status_code = status },
|
||||||
|
.content = content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
@ -66,6 +67,8 @@ pub const StatusCode = enum {
|
|||||||
network_authentication_required,
|
network_authentication_required,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FormatOptions = struct { colorized: bool = false, linebreak: bool = false };
|
||||||
|
|
||||||
pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) type {
|
pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) type {
|
||||||
return struct {
|
return struct {
|
||||||
code: []const u8 = code,
|
code: []const u8 = code,
|
||||||
@ -73,11 +76,15 @@ pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) t
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn format(self: Self, colorized: bool) []const u8 {
|
pub fn getFormatted(self: Self, comptime options: FormatOptions) []const u8 {
|
||||||
_ = self;
|
_ = self;
|
||||||
const full_message = code ++ " " ++ message;
|
const linebreak = switch (builtin.os.tag) {
|
||||||
|
.windows => "\r\n",
|
||||||
|
inline else => "\n",
|
||||||
|
};
|
||||||
|
const full_message = code ++ " " ++ message ++ if (options.linebreak) linebreak else "";
|
||||||
|
|
||||||
if (!colorized) return full_message;
|
if (!options.colorized) return full_message;
|
||||||
|
|
||||||
if (std.mem.startsWith(u8, code, "2")) {
|
if (std.mem.startsWith(u8, code, "2")) {
|
||||||
return jetzig.colors.green(full_message);
|
return jetzig.colors.green(full_message);
|
||||||
@ -159,9 +166,9 @@ pub const TaggedStatusCode = union(StatusCode) {
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn format(self: Self, colorized: bool) []const u8 {
|
pub fn getFormatted(self: Self, comptime options: FormatOptions) []const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
inline else => |capture| capture.format(colorized),
|
inline else => |capture| capture.getFormatted(options),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,4 +177,16 @@ pub const TaggedStatusCode = union(StatusCode) {
|
|||||||
inline else => |capture| capture.code,
|
inline else => |capture| capture.code,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getMessage(self: Self) []const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
inline else => |capture| capture.message,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get(code: StatusCode) TaggedStatusCode {
|
||||||
|
switch (code) {
|
||||||
|
inline else => |capture| return @unionInit(TaggedStatusCode, @tagName(capture), .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -83,10 +83,15 @@ pub fn logRequest(self: DevelopmentLogger, request: *const jetzig.http.Request)
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatted_status = if (self.stdout_colorized)
|
||||||
|
status.getFormatted(.{ .colorized = true })
|
||||||
|
else
|
||||||
|
status.getFormatted(.{});
|
||||||
|
|
||||||
const message = try std.fmt.allocPrint(self.allocator, "[{s}/{s}/{s}] {s}", .{
|
const message = try std.fmt.allocPrint(self.allocator, "[{s}/{s}/{s}] {s}", .{
|
||||||
formatted_duration,
|
formatted_duration,
|
||||||
request.fmtMethod(self.stdout_colorized),
|
request.fmtMethod(self.stdout_colorized),
|
||||||
status.format(self.stdout_colorized),
|
formatted_status,
|
||||||
request.path.path,
|
request.path.path,
|
||||||
});
|
});
|
||||||
defer self.allocator.free(message);
|
defer self.allocator.free(message);
|
||||||
|
@ -5,7 +5,7 @@ const jetzig = @import("../jetzig.zig");
|
|||||||
const Zmd = @import("zmd").Zmd;
|
const Zmd = @import("zmd").Zmd;
|
||||||
pub fn render(
|
pub fn render(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
route: *const jetzig.views.Route,
|
path: []const u8,
|
||||||
custom_fragments: ?type,
|
custom_fragments: ?type,
|
||||||
) !?[]const u8 {
|
) !?[]const u8 {
|
||||||
const fragments = custom_fragments orelse jetzig.config.get(type, "markdown_fragments");
|
const fragments = custom_fragments orelse jetzig.config.get(type, "markdown_fragments");
|
||||||
@ -15,11 +15,10 @@ pub fn render(
|
|||||||
|
|
||||||
try path_buf.appendSlice(&[_][]const u8{ "src", "app", "views" });
|
try path_buf.appendSlice(&[_][]const u8{ "src", "app", "views" });
|
||||||
|
|
||||||
var it = std.mem.splitScalar(u8, route.uri_path, '/');
|
var it = std.mem.splitScalar(u8, path, '/');
|
||||||
while (it.next()) |segment| {
|
while (it.next()) |segment| {
|
||||||
try path_buf.append(segment);
|
try path_buf.append(segment);
|
||||||
}
|
}
|
||||||
try path_buf.append(@tagName(route.action));
|
|
||||||
|
|
||||||
const base_path = try std.fs.path.join(allocator, path_buf.items);
|
const base_path = try std.fs.path.join(allocator, path_buf.items);
|
||||||
defer allocator.free(base_path);
|
defer allocator.free(base_path);
|
||||||
|
@ -5,7 +5,7 @@ const Self = @This();
|
|||||||
const jetzig = @import("../../jetzig.zig");
|
const jetzig = @import("../../jetzig.zig");
|
||||||
|
|
||||||
data: *jetzig.data.Data,
|
data: *jetzig.data.Data,
|
||||||
status_code: jetzig.http.status_codes.StatusCode,
|
status_code: jetzig.http.status_codes.StatusCode = .ok,
|
||||||
|
|
||||||
pub fn deinit(self: Self) void {
|
pub fn deinit(self: Self) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user