WIP html parsing
This commit is contained in:
		
							parent
							
								
									70fbe8668b
								
							
						
					
					
						commit
						492f890283
					
				@ -1,5 +1,5 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const htzx = @import("build/htzx_build.zig");
 | 
			
		||||
const htzx = @import("src/htzx/htzx_build.zig");
 | 
			
		||||
pub fn build(b: *std.Build) !void {
 | 
			
		||||
    const target = b.standardTargetOptions(.{});
 | 
			
		||||
    const optimize = b.standardOptimizeOption(.{});
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								src/htzx/html_parser/scanner.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/htzx/html_parser/scanner.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
const Token = @import("token.zig").Token;
 | 
			
		||||
 | 
			
		||||
pub fn Scanner(comptime Source: type) type {
 | 
			
		||||
    return struct {
 | 
			
		||||
        const Self = @This();
 | 
			
		||||
        const SourceType = Source;
 | 
			
		||||
        line: u32,
 | 
			
		||||
        col: u32,
 | 
			
		||||
        current: u32,
 | 
			
		||||
 | 
			
		||||
        // fn scanArrayList(self: Self, source: SourceType, token_array: std.ArrayListAligned(Token, null)) !std.ArrayListAligned(Token, null) {
 | 
			
		||||
        //     _ = self;
 | 
			
		||||
        //     var eof = false;
 | 
			
		||||
        //     while (!eof) {
 | 
			
		||||
        //         // switch(
 | 
			
		||||
        //     }
 | 
			
		||||
        // }
 | 
			
		||||
    };
 | 
			
		||||
} 
 | 
			
		||||
							
								
								
									
										82
									
								
								src/htzx/html_parser/token.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/htzx/html_parser/token.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Token = struct {
 | 
			
		||||
    lexeme: Lexeme,
 | 
			
		||||
    raw: []const u8,
 | 
			
		||||
    line: u32,
 | 
			
		||||
    col: u32,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Lexeme = enum {
 | 
			
		||||
    NEWLINE,
 | 
			
		||||
    LT,
 | 
			
		||||
    GT,
 | 
			
		||||
    CLOSING_LT,
 | 
			
		||||
    EQUALS,
 | 
			
		||||
    TAG,
 | 
			
		||||
	SINGLE_QUOTE,
 | 
			
		||||
	DOUBLE_QOUTE,
 | 
			
		||||
	TEXT,
 | 
			
		||||
	DOUBLE_OPEN_BRACE,
 | 
			
		||||
	DOUBLE_CLOSE_BRACE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const TagTypes = enum {
 | 
			
		||||
    a,
 | 
			
		||||
    abbr,
 | 
			
		||||
    blockquote,
 | 
			
		||||
    body,
 | 
			
		||||
    br,
 | 
			
		||||
    button,
 | 
			
		||||
    canvas,
 | 
			
		||||
    caption,
 | 
			
		||||
    code,
 | 
			
		||||
    div,
 | 
			
		||||
    em,
 | 
			
		||||
    embed,
 | 
			
		||||
    form,
 | 
			
		||||
    frame,
 | 
			
		||||
    h1,
 | 
			
		||||
    h2,
 | 
			
		||||
    h3,
 | 
			
		||||
    h4,
 | 
			
		||||
    h5,
 | 
			
		||||
    h6,
 | 
			
		||||
    head,
 | 
			
		||||
    header,
 | 
			
		||||
    html,
 | 
			
		||||
    iframe,
 | 
			
		||||
    img,
 | 
			
		||||
    input,
 | 
			
		||||
    label,
 | 
			
		||||
    main,
 | 
			
		||||
    nav,
 | 
			
		||||
    object,
 | 
			
		||||
    option,
 | 
			
		||||
    p,
 | 
			
		||||
    picture,
 | 
			
		||||
    script,
 | 
			
		||||
    section,
 | 
			
		||||
    source,
 | 
			
		||||
    span,
 | 
			
		||||
    strong,
 | 
			
		||||
    style,
 | 
			
		||||
    table,
 | 
			
		||||
    template,
 | 
			
		||||
    title,
 | 
			
		||||
    video,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const TagAttributes = enum {
 | 
			
		||||
    href,
 | 
			
		||||
    alt,
 | 
			
		||||
    border,
 | 
			
		||||
    name,
 | 
			
		||||
    src,
 | 
			
		||||
    style,
 | 
			
		||||
    class,
 | 
			
		||||
    maxlength,
 | 
			
		||||
    name,
 | 
			
		||||
    onblur
 | 
			
		||||
};
 | 
			
		||||
// <h3 class="gogo">text</h3>
 | 
			
		||||
@ -4,8 +4,10 @@ const log = std.log.scoped(.htzx_comp);
 | 
			
		||||
pub fn build_htzx(b: *std.Build, exe: *std.Build.Step.Compile) !void {
 | 
			
		||||
    // Setup comptime parsing for /lib files
 | 
			
		||||
    var markup_files = std.ArrayList([]const u8).init(b.allocator);
 | 
			
		||||
    var markup_files_content = std.ArrayList([]const u8).init(b.allocator);
 | 
			
		||||
    // var zig_files = std.ArrayList([]const u8).init(b.allocator);
 | 
			
		||||
    defer markup_files.deinit();
 | 
			
		||||
    defer markup_files_content.deinit();
 | 
			
		||||
    // defer zig_files.deinit();
 | 
			
		||||
 | 
			
		||||
    var options = b.addOptions();
 | 
			
		||||
@ -27,7 +29,7 @@ pub fn build_htzx(b: *std.Build, exe: *std.Build.Step.Compile) !void {
 | 
			
		||||
        }
 | 
			
		||||
        if (file.?.kind == .file) {
 | 
			
		||||
            const relative_path = file.?.path;
 | 
			
		||||
            const file_path = try std.mem.concat(b.allocator, u8, &[_][]const u8{lib_dir_path, relative_path});
 | 
			
		||||
            const file_path = try std.mem.concat(b.allocator, u8, &[_][]const u8{ lib_dir_path, relative_path });
 | 
			
		||||
            var split_iter = std.mem.splitBackwardsAny(u8, relative_path, ".");
 | 
			
		||||
            var extension = split_iter.first();
 | 
			
		||||
            // if (std.mem.eql(u8, extension, "zig")) {
 | 
			
		||||
@ -49,8 +51,11 @@ pub fn build_htzx(b: *std.Build, exe: *std.Build.Step.Compile) !void {
 | 
			
		||||
            if (std.mem.eql(u8, extension, "html")) {
 | 
			
		||||
                log.info("Adding html file {s}...", .{file_path});
 | 
			
		||||
                try markup_files.append(b.dupe(file_path));
 | 
			
		||||
                var f = try std.fs.cwd().openFile(file_path, .{});
 | 
			
		||||
                var content = try f.readToEndAlloc(b.allocator, 1024 * 1024);
 | 
			
		||||
                try markup_files_content.append(content);
 | 
			
		||||
            } else {
 | 
			
		||||
               log.info("Unrecognized file extension '.{s}'\t{s}", .{ extension, file_path });
 | 
			
		||||
                log.info("Unrecognized file extension '.{s}'\t{s}", .{ extension, file_path });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // std.debug.print("Got entry in `versions:` base: {s}, path: {s}, kind: {any}\n", .{ ver.?.basename, ver.?.path, ver.?.kind });
 | 
			
		||||
@ -60,5 +65,6 @@ pub fn build_htzx(b: *std.Build, exe: *std.Build.Step.Compile) !void {
 | 
			
		||||
    // as a string array at comptime in main.zig
 | 
			
		||||
    // options.addOption([]const []const u8, "zig_files", zig_files.items);
 | 
			
		||||
    options.addOption([]const []const u8, "markup_files", markup_files.items);
 | 
			
		||||
    options.addOption([]const []const u8, "markup_files_content", markup_files_content.items);
 | 
			
		||||
    exe.addOptions("options", options);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/htzx/method_route.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/htzx/method_route.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const string = []const u8;
 | 
			
		||||
 | 
			
		||||
/// Tagged union `std.http.Method` with string route
 | 
			
		||||
pub const MethodRoute = union(std.http.Method) {
 | 
			
		||||
    GET: string,
 | 
			
		||||
    HEAD: string,
 | 
			
		||||
    POST: string,
 | 
			
		||||
    PUT: string,
 | 
			
		||||
    DELETE: string,
 | 
			
		||||
    CONNECT: string,
 | 
			
		||||
    OPTIONS: string,
 | 
			
		||||
    TRACE: string,
 | 
			
		||||
    PATCH: string,
 | 
			
		||||
 | 
			
		||||
    pub fn create(method: std.http.Method, target: string) MethodRoute {
 | 
			
		||||
        var method_route = switch (method) {
 | 
			
		||||
            .GET => MethodRoute{ .GET = target },
 | 
			
		||||
            .POST => MethodRoute{ .POST = target },
 | 
			
		||||
            .PUT => MethodRoute{ .PUT = target },
 | 
			
		||||
            .DELETE => MethodRoute{ .DELETE = target },
 | 
			
		||||
            .PATCH => MethodRoute{ .PATCH = target },
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        };
 | 
			
		||||
        return method_route;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn getRoute(method_route: MethodRoute) string {
 | 
			
		||||
        var route = switch (method_route) {
 | 
			
		||||
            .GET => method_route.GET,
 | 
			
		||||
            .POST => method_route.POST,
 | 
			
		||||
            .PUT => method_route.PUT,
 | 
			
		||||
            .DELETE => method_route.DELETE,
 | 
			
		||||
            .PATCH => method_route.PATCH,
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        };
 | 
			
		||||
        return route;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn getMethod(method_route: MethodRoute) std.http.Method {
 | 
			
		||||
        var http_method = switch (method_route) {
 | 
			
		||||
            .GET => std.http.Method.GET,
 | 
			
		||||
            .POST => std.http.Method.POST,
 | 
			
		||||
            .PUT => std.http.Method.PUT,
 | 
			
		||||
            .DELETE => std.http.Method.DELETE,
 | 
			
		||||
            .PATCH => std.http.Method.PATCH,
 | 
			
		||||
            else => unreachable,
 | 
			
		||||
        };
 | 
			
		||||
        return http_method;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Route Context for creating a std.ArrayHashMap with MethodRoute keys
 | 
			
		||||
pub const RouteContext = struct {
 | 
			
		||||
    pub fn hash(self: @This(), key: MethodRoute) u32 {
 | 
			
		||||
        _ = self;
 | 
			
		||||
        var route = key.getRoute();
 | 
			
		||||
        return std.array_hash_map.hashString(route);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn eql(self: @This(), a: MethodRoute, b: MethodRoute, b_index: usize) bool {
 | 
			
		||||
        _ = b_index;
 | 
			
		||||
        _ = self;
 | 
			
		||||
        return std.mem.eql(u8, a.getRoute(), b.getRoute()) and a.getMethod() == b.getMethod();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@ -1,17 +1,66 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const one_megabyte: usize = 1024 * 1024;
 | 
			
		||||
const string = []const u8;
 | 
			
		||||
const log = std.log.scoped(.server);
 | 
			
		||||
const HtmlValidator = @import("validators.zig").HtmlValidator;
 | 
			
		||||
const markup_files_content = @import("options").markup_files_content;
 | 
			
		||||
 | 
			
		||||
pub const MarkupContent = struct {
 | 
			
		||||
    path: string,
 | 
			
		||||
    html: string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Caller owns the returned StringArrayHashMap
 | 
			
		||||
pub fn open_html_load(allocator: std.mem.Allocator, filenames: []const []const u8) !std.StringArrayHashMap(string) {
 | 
			
		||||
pub fn open_validate_html_load(allocator: std.mem.Allocator, comptime filenames: []const []const u8) !std.StringArrayHashMap(string) {
 | 
			
		||||
    var dir = std.fs.cwd();
 | 
			
		||||
    var html_file_map = std.StringArrayHashMap(string).init(allocator);
 | 
			
		||||
 | 
			
		||||
    for (filenames) |file_name| {
 | 
			
		||||
    comptime var index = 0;
 | 
			
		||||
    inline for (filenames) |file_name| {
 | 
			
		||||
        comptime {
 | 
			
		||||
            // Validate comptime html file content
 | 
			
		||||
            var html_content = markup_files_content[index];
 | 
			
		||||
            var valid = HtmlValidator.comp_validate(html_content);
 | 
			
		||||
            if (!valid) {
 | 
			
		||||
                @compileError("Invalid html in file " ++ file_name);
 | 
			
		||||
            }
 | 
			
		||||
            index += 1;
 | 
			
		||||
        }
 | 
			
		||||
        // Get rid of "src/lib"
 | 
			
		||||
        var file_base = file_name[7..];
 | 
			
		||||
        var iter = std.mem.splitBackwardsSequence(u8, file_base, "/");
 | 
			
		||||
        var basename = iter.first();
 | 
			
		||||
        var route_len = file_base.len - basename.len;
 | 
			
		||||
        var arr: [100]u8 = undefined;
 | 
			
		||||
        var buf = arr[0..];
 | 
			
		||||
        var route_name = try std.fmt.bufPrint(buf, "{s}", .{file_base[0..route_len]});
 | 
			
		||||
        var f = try dir.openFile(file_name, .{});
 | 
			
		||||
        var html_content = try f.readToEndAlloc(allocator, one_megabyte);
 | 
			
		||||
        try html_file_map.putNoClobber(file_name, html_content);
 | 
			
		||||
        // log.info("Adding html get route to {s}\tGot file_base {s}, basename {s}, route_len {d}...", .{
 | 
			
		||||
        //     route_name,
 | 
			
		||||
        //     file_base,
 | 
			
		||||
        //     basename,
 | 
			
		||||
        //     route_len,
 | 
			
		||||
        // });
 | 
			
		||||
        try html_file_map.putNoClobber(try allocator.dupe(u8, route_name), html_content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return html_file_map;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// pub fn nameToEnumTag(name: []const u8, comptime Enum: type) enumError!Enum {
 | 
			
		||||
//     comptime {
 | 
			
		||||
//         if (@typeInfo(Enum) != .Enum) {
 | 
			
		||||
//             @compileError("Non enum type passed into function: " ++ @typeName(Enum));
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
 | 
			
		||||
//     inline for (@typeInfo(Enum).Enum.fields) |field| {
 | 
			
		||||
//         if (std.mem.eql(u8, name, field.name)) {
 | 
			
		||||
//             const tag: Enum = @enumFromInt(field.value);
 | 
			
		||||
//             return tag;
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
//     return enumError.EnumFieldNotFound;
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								src/htzx/validators.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/htzx/validators.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
pub const HtmlValidator = struct {
 | 
			
		||||
 | 
			
		||||
    pub fn  comp_validate(comptime html: []const u8) bool {
 | 
			
		||||
        _ = html;
 | 
			
		||||
        // TODO this needs to be done
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn validate(html: []const u8) bool {
 | 
			
		||||
        _ = html;
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>My Simple HTML Page</title>
 | 
			
		||||
</head>
 | 
			
		||||
<script src="https://unpkg.com/htmx.org@1.9.8"></script>
 | 
			
		||||
<body>
 | 
			
		||||
    <h1>Welcome to My Web Page</h1>
 | 
			
		||||
    <p>This is a simple HTML document.</p>
 | 
			
		||||
    <p>Thank you for visiting.</p>
 | 
			
		||||
    <div id="parent-div">
 | 
			
		||||
        <h3>Want Zig Facts?</h3>
 | 
			
		||||
        <button hx-get="/zig"
 | 
			
		||||
            hx-trigger="click"
 | 
			
		||||
            hx-target="#parent-div"
 | 
			
		||||
            hx-swap="outerHTML"
 | 
			
		||||
        >
 | 
			
		||||
           Get Zig Facts
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub fn post() i32 {
 | 
			
		||||
    std.debug.print("Running route get!\n", .{});
 | 
			
		||||
    return 13;
 | 
			
		||||
}
 | 
			
		||||
@ -14,7 +14,8 @@ pub fn main() !void {
 | 
			
		||||
    var server = Zx.init(allocator);
 | 
			
		||||
    defer server.deinit();
 | 
			
		||||
 | 
			
		||||
    try server.GET("/zig", index.getIndex);
 | 
			
		||||
    try server.GET("/zig", index.getZig);
 | 
			
		||||
    try server.POST("/zig", index.postZig);
 | 
			
		||||
 | 
			
		||||
    server.runServer() catch |err| {
 | 
			
		||||
        log.err("Server exited with err: {any}\nDumping trace:\n", .{err});
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,44 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const http = std.http;
 | 
			
		||||
const zx = @import("../zx.zig");
 | 
			
		||||
const log = std.log.scoped(.index);
 | 
			
		||||
 | 
			
		||||
pub fn getIndex(ctz: *zx.ZxContext) zx.ZxError!void {
 | 
			
		||||
    ctz.json(.{ .success = true, .message = "hello did you get this?", .age = 1 }) catch |err| {
 | 
			
		||||
        std.debug.print("Got error {any}\n", .{err});
 | 
			
		||||
pub fn getZig(ctz: *zx.ZxContext) zx.ZxError!void {
 | 
			
		||||
    ctz.html(
 | 
			
		||||
    \\<ul>
 | 
			
		||||
    \\<li>Zig is awesome</li>
 | 
			
		||||
    \\<li>Zig runs everywhere</li>
 | 
			
		||||
    \\<li>Zig has a cool name</li>
 | 
			
		||||
    \\</ul>
 | 
			
		||||
    , .ok) catch |err| {
 | 
			
		||||
        log.err("Got error {any}\n", .{err});
 | 
			
		||||
        return zx.ZxError.Unexpected;
 | 
			
		||||
    };
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ZigPost = struct {
 | 
			
		||||
    id: u32,
 | 
			
		||||
    name: []const u8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn postZig(ctz: *zx.ZxContext) zx.ZxError!void {
 | 
			
		||||
    if (ctz.req_body.len > 0) {
 | 
			
		||||
        var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
 | 
			
		||||
        defer arena.deinit();
 | 
			
		||||
 | 
			
		||||
        var json_body = std.json.parseFromSliceLeaky(ZigPost, arena.allocator(), ctz.req_body, .{}) catch |err| {
 | 
			
		||||
            log.err("Error while parsing json: {any}", .{err});
 | 
			
		||||
            return zx.ZxError.Unexpected;
 | 
			
		||||
        };
 | 
			
		||||
        log.info("Got id {d} name {s}", .{ json_body.id, json_body.name });
 | 
			
		||||
        try ctz.json(.{ .success = true }, .ok);
 | 
			
		||||
    } else {
 | 
			
		||||
        ctz.json(.{ .success = false, .message = "Invalid request" }, .bad_request) catch |err| {
 | 
			
		||||
            log.err("Got error {any}\n", .{err});
 | 
			
		||||
            return zx.ZxError.Unexpected;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										126
									
								
								src/zx.zig
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								src/zx.zig
									
									
									
									
									
								
							@ -1,11 +1,20 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
const options = @import("options");
 | 
			
		||||
const zx_utils = @import("htzx/utils.zig");
 | 
			
		||||
const markup_files = options.markup_files;
 | 
			
		||||
const http = std.http;
 | 
			
		||||
const log = std.log.scoped(.server);
 | 
			
		||||
 | 
			
		||||
const zx_utils = @import("htzx/utils.zig");
 | 
			
		||||
 | 
			
		||||
const MethodRoute = @import("htzx/method_route.zig").MethodRoute;
 | 
			
		||||
const RouteContext = @import("htzx/method_route.zig").RouteContext;
 | 
			
		||||
const validators = @import("htzx/validators.zig");
 | 
			
		||||
const HtmlValidator = validators.HtmlValidator;
 | 
			
		||||
 | 
			
		||||
const markup_files = options.markup_files;
 | 
			
		||||
 | 
			
		||||
// Types
 | 
			
		||||
const string = []const u8;
 | 
			
		||||
pub const ZxRouteFn = *const fn (*ZxContext) ZxError!void;
 | 
			
		||||
 | 
			
		||||
const server_addr = "127.0.0.1";
 | 
			
		||||
const server_port = 8000;
 | 
			
		||||
@ -18,19 +27,17 @@ pub const ZxError = error{
 | 
			
		||||
    Unexpected,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const ZxRouteFn = *const fn (*ZxContext) ZxError!void;
 | 
			
		||||
 | 
			
		||||
pub const Zx = struct {
 | 
			
		||||
    server: http.Server,
 | 
			
		||||
    allocator: std.mem.Allocator,
 | 
			
		||||
    routes: std.StringArrayHashMap(ZxRouteFn),
 | 
			
		||||
    routes: std.ArrayHashMap(MethodRoute, ZxRouteFn, RouteContext, true),
 | 
			
		||||
    html_files_map: std.StringArrayHashMap(string),
 | 
			
		||||
 | 
			
		||||
    pub fn init(
 | 
			
		||||
        allocator: std.mem.Allocator,
 | 
			
		||||
    ) Zx {
 | 
			
		||||
        var server = http.Server.init(allocator, .{ .reuse_address = true });
 | 
			
		||||
        var routes = std.StringArrayHashMap(ZxRouteFn).init(allocator);
 | 
			
		||||
        var routes = std.ArrayHashMap(MethodRoute, ZxRouteFn, RouteContext, true).init(allocator);
 | 
			
		||||
        var html_files_map = std.StringArrayHashMap(string).init(allocator);
 | 
			
		||||
        return .{ .server = server, .allocator = allocator, .routes = routes, .html_files_map = html_files_map };
 | 
			
		||||
    }
 | 
			
		||||
@ -40,20 +47,19 @@ pub const Zx = struct {
 | 
			
		||||
        zx.routes.deinit();
 | 
			
		||||
 | 
			
		||||
        // free html file content values
 | 
			
		||||
        for (zx.html_files_map.keys()) |key| {
 | 
			
		||||
            var entry = zx.html_files_map.getEntry(key);
 | 
			
		||||
            zx.allocator.free(entry.?.value_ptr);
 | 
			
		||||
        }
 | 
			
		||||
        zx.allocator.free(zx.html_files_map.keys());
 | 
			
		||||
        zx.allocator.free(zx.html_files_map.values());
 | 
			
		||||
        // for (zx.html_files_map.keys()) |key| {
 | 
			
		||||
        //     var entry = zx.html_files_map.getEntry(key);
 | 
			
		||||
        //     zx.allocator.destroy(entry.?.value_ptr);
 | 
			
		||||
        // }
 | 
			
		||||
        zx.html_files_map.deinit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn runServer(zx: *Zx) !void {
 | 
			
		||||
        log.info("Server is running at {s}:{d}", .{ server_addr, server_port });
 | 
			
		||||
 | 
			
		||||
        for (markup_files) |file| {
 | 
			
		||||
            log.info("{s}", .{file});
 | 
			
		||||
        }
 | 
			
		||||
        zx.html_files_map = try zx_utils.open_html_load(zx.allocator, markup_files);
 | 
			
		||||
        zx.html_files_map = try zx_utils.open_validate_html_load(zx.allocator, markup_files);
 | 
			
		||||
 | 
			
		||||
        // Parse the server address.
 | 
			
		||||
        const address = try std.net.Address.parseIp(server_addr, server_port);
 | 
			
		||||
@ -80,13 +86,15 @@ pub const Zx = struct {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn POST(comptime path: string, handler: ZxRouteFn) void {
 | 
			
		||||
        _ = handler;
 | 
			
		||||
        _ = path;
 | 
			
		||||
    pub fn POST(zx: *Zx, path: string, handler: ZxRouteFn) ZxError!void {
 | 
			
		||||
        zx.routes.putNoClobber(MethodRoute.create(.POST, path), handler) catch |err| {
 | 
			
		||||
            std.debug.print("{any}\nCant add another route at {s}\n", .{ err, path });
 | 
			
		||||
            return ZxError.RedefinedRoute;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn GET(zx: *Zx, comptime path: string, handler: ZxRouteFn) ZxError!void {
 | 
			
		||||
        zx.routes.putNoClobber(path, handler) catch |err| {
 | 
			
		||||
    pub fn GET(zx: *Zx, path: string, handler: ZxRouteFn) ZxError!void {
 | 
			
		||||
        zx.routes.putNoClobber(MethodRoute.create(.GET, path), handler) catch |err| {
 | 
			
		||||
            std.debug.print("{any}\nCant add another route at {s}\n", .{ err, path });
 | 
			
		||||
            return ZxError.RedefinedRoute;
 | 
			
		||||
        };
 | 
			
		||||
@ -124,22 +132,27 @@ pub const Zx = struct {
 | 
			
		||||
 | 
			
		||||
        if (response.request.method != .HEAD) {
 | 
			
		||||
            var ctz = ZxContext.init(allocator, response, body);
 | 
			
		||||
            // Check to return html from lib/
 | 
			
		||||
            if (response.request.method == .GET and zx.has_html_route(response.request.target)) {
 | 
			
		||||
                log.info("Returning html file at {s}...", .{response.request.target});
 | 
			
		||||
                try zx.return_html(&ctz);
 | 
			
		||||
            } else {
 | 
			
		||||
                var handler = zx.routes.get(response.request.target);
 | 
			
		||||
                var handler = zx.routes.get(ctz.method_route);
 | 
			
		||||
                if (handler) |h| {
 | 
			
		||||
                    h(&ctz) catch |err| {
 | 
			
		||||
                        errorHandler(response, err);
 | 
			
		||||
                        log.err("Caught error handling route {any}", .{err});
 | 
			
		||||
                        errorHandler(response, .internal_server_error);
 | 
			
		||||
                    };
 | 
			
		||||
                } else {
 | 
			
		||||
                    std.debug.print("No route defined for {s}\n", .{response.request.target});
 | 
			
		||||
                    log.warn("No route defined for {s}\n", .{response.request.target});
 | 
			
		||||
 | 
			
		||||
                    response.status = .not_found;
 | 
			
		||||
                    response.do() catch return ZxError.DoError;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            //
 | 
			
		||||
            // if (response.state != .responded) {
 | 
			
		||||
            //     errorHandler(response, );
 | 
			
		||||
            // }
 | 
			
		||||
            response.finish() catch |err| {
 | 
			
		||||
                log.err("Got finish error {any}\n", .{err});
 | 
			
		||||
                return ZxError.FinishError;
 | 
			
		||||
@ -151,17 +164,28 @@ pub const Zx = struct {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn has_html_route(zx: *Zx, route_target: string) bool {
 | 
			
		||||
        // for (zx.html_files_map.keys()) |key| {
 | 
			
		||||
        //     log.info("html route at {s}", .{key});
 | 
			
		||||
        // }
 | 
			
		||||
        return zx.html_files_map.contains(route_target);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn return_html(zx: *Zx, ctz: *ZxContext) ZxError!void {
 | 
			
		||||
        var html = zx.html_files_map.get(ctz.response.request.target).?;
 | 
			
		||||
        try ctz.html(html);
 | 
			
		||||
        try ctz.html_dynamic(html, .ok);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn errorHandler(response: *http.Server.Response, err: ZxError) void {
 | 
			
		||||
        log.err("Got error while handling route {any}\n", .{err});
 | 
			
		||||
        response.status = .internal_server_error;
 | 
			
		||||
    fn errorHandler(response: *http.Server.Response, status: ?http.Status) void {
 | 
			
		||||
        if (status) |stat| {
 | 
			
		||||
            response.status = stat;
 | 
			
		||||
        } else {
 | 
			
		||||
            response.status = .internal_server_error;
 | 
			
		||||
        }
 | 
			
		||||
        if (response.state == .waited) {
 | 
			
		||||
            response.do() catch {
 | 
			
		||||
                log.err("Already responded to client, cannot send error {any}", .{response.status});
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -169,45 +193,77 @@ pub const ZxContext = struct {
 | 
			
		||||
    allocator: std.mem.Allocator,
 | 
			
		||||
    response: *http.Server.Response,
 | 
			
		||||
    req_body: string,
 | 
			
		||||
    method_route: MethodRoute,
 | 
			
		||||
 | 
			
		||||
    pub fn init(alloc: std.mem.Allocator, res: *http.Server.Response, body: string) ZxContext {
 | 
			
		||||
        var method_route = MethodRoute.create(res.request.method, res.request.target);
 | 
			
		||||
        return ZxContext{
 | 
			
		||||
            .allocator = alloc,
 | 
			
		||||
            .response = res,
 | 
			
		||||
            .req_body = body,
 | 
			
		||||
            .method_route = method_route,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Helper function to return JSON to the client
 | 
			
		||||
    pub fn json(ctz: *ZxContext, content: anytype) ZxError!void {
 | 
			
		||||
    /// Wrapper on ZxContext.respond() to return JSON to the client
 | 
			
		||||
    pub fn json(ctz: *ZxContext, content: anytype, status: ?std.http.Status) ZxError!void {
 | 
			
		||||
        // Add json header
 | 
			
		||||
        ctz.response.headers.append("Content-Type", "application/json") catch return ZxError.Unexpected;
 | 
			
		||||
 | 
			
		||||
        const json_content = std.json.stringifyAlloc(ctz.allocator, content, .{}) catch return ZxError.OutOfMemory;
 | 
			
		||||
        defer ctz.allocator.free(json_content);
 | 
			
		||||
 | 
			
		||||
        try ctz.respond(json_content);
 | 
			
		||||
        try ctz.respond(json_content, status);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    test "json helper function" {}
 | 
			
		||||
 | 
			
		||||
    pub fn html(ctz: *ZxContext, html_content: string) ZxError!void {
 | 
			
		||||
    /// Wrapper on ZxContext.respond() to return html
 | 
			
		||||
    pub fn html_dynamic(ctz: *ZxContext, html_content: string, status: std.http.Status) ZxError!void {
 | 
			
		||||
        var valid = HtmlValidator.validate(html_content);
 | 
			
		||||
        if (!valid) {
 | 
			
		||||
            log.err("Invalid html returned from route", .{});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add html header
 | 
			
		||||
        ctz.response.headers.append("Content-Type", "text/html") catch return ZxError.Unexpected;
 | 
			
		||||
 | 
			
		||||
        try ctz.respond(html_content);
 | 
			
		||||
        try ctz.respond(html_content, status);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helper function to return body to the client
 | 
			
		||||
    pub fn respond(ctz: *ZxContext, body: string) ZxError!void {
 | 
			
		||||
    /// Wrapper on ZxContext.respond() to return html, compile-time checked html
 | 
			
		||||
    pub fn html(ctz: *ZxContext, comptime html_content: string, status: std.http.Status) ZxError!void {
 | 
			
		||||
        comptime {
 | 
			
		||||
            var valid = HtmlValidator.comp_validate(html_content);
 | 
			
		||||
            if (!valid) {
 | 
			
		||||
                @compileError("Trying to return invalid html\n" ++ html_content);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Add html header
 | 
			
		||||
        ctz.response.headers.append("Content-Type", "text/html") catch |err| {
 | 
			
		||||
            log.err("{any} while appending headers to html response", .{err});
 | 
			
		||||
            return ZxError.Unexpected;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try ctz.respond(html_content, status);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Helper function to return `body` and optional `status` to the client
 | 
			
		||||
    pub fn respond(ctz: *ZxContext, body: string, status: ?std.http.Status) ZxError!void {
 | 
			
		||||
        if (!ctz.response.headers.contains("Content-Type")) {
 | 
			
		||||
            ctz.response.headers.append("Content-Type", "text/html") catch return ZxError.Unexpected;
 | 
			
		||||
        }
 | 
			
		||||
        if (status) |stat| {
 | 
			
		||||
            ctz.response.status = stat;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ctz.response.transfer_encoding = .{ .content_length = body.len };
 | 
			
		||||
        // Send headers
 | 
			
		||||
        ctz.response.do() catch return ZxError.DoError;
 | 
			
		||||
        // Send html body
 | 
			
		||||
        ctz.response.writeAll(body) catch return ZxError.Unexpected;
 | 
			
		||||
        ctz.response.writeAll(body) catch |err| {
 | 
			
		||||
            log.err("{any} while writing to response body", .{err});
 | 
			
		||||
            return ZxError.Unexpected;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user