This repository was archived by the owner on Mar 24, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2k
Zig Dusty framework #10806
Merged
Merged
Zig Dusty framework #10806
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| .zig-cache | ||
| zig-out |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
|
|
||
| # [Dusty](https://github.com/lalinsky/dusty) - Zig HTTP client/server library | ||
|
|
||
| ## Description | ||
|
|
||
| Zig HTTP client/server library built on top of zio (coroutine/async engine) | ||
|
|
||
| ## Test URLs | ||
|
|
||
| ### Test 1: JSON Encoding | ||
|
|
||
| http://localhost:3000/json | ||
|
|
||
| ### Test 2: Plaintext | ||
|
|
||
| http://localhost:3000/plaintext | ||
|
|
||
| ### Test 2: Single Row Query | ||
|
|
||
| http://localhost:3000/db | ||
|
|
||
| ### Test 4: Fortunes (Template rendering) | ||
|
|
||
| http://localhost:3000/fortunes | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| { | ||
| "framework": "dusty", | ||
| "maintainers": ["dragosv"], | ||
| "tests": [{ | ||
| "default": { | ||
| "json_url": "/json", | ||
| "plaintext_url": "/plaintext", | ||
| "db_url": "/db", | ||
| "fortune_url": "/fortunes", | ||
| "port": 3000, | ||
| "approach": "Realistic", | ||
| "classification": "Fullstack", | ||
| "database": "Postgres", | ||
| "framework": "dusty", | ||
| "language": "Zig", | ||
| "flavor": "None", | ||
| "orm": "raw", | ||
| "platform": "None", | ||
| "webserver": "None", | ||
| "os": "Linux", | ||
| "database_os": "Linux", | ||
| "display_name": "Dusty (Zig)", | ||
| "notes": "", | ||
| "versus": "" | ||
| } | ||
| }] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| const std = @import("std"); | ||
|
|
||
| pub fn build(b: *std.Build) !void { | ||
| const target = b.standardTargetOptions(.{}); | ||
| const optimize = b.standardOptimizeOption(.{}); | ||
|
|
||
| const dep_opts = .{ .target = target, .optimize = optimize }; | ||
|
|
||
| const root_module = b.addModule("root_mod", .{ | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .root_source_file = b.path("src/main.zig"), | ||
| }); | ||
|
|
||
| const dusty_dep = b.dependency("dusty", dep_opts); | ||
| const dusty_module = dusty_dep.module("dusty"); | ||
| const zio_module = dusty_dep.builder.dependency("zio", dep_opts).module("zio"); | ||
| const pg_module = b.dependency("pg", dep_opts).module("pg"); | ||
| const datetimez_module = b.dependency("datetimez", dep_opts).module("datetime"); | ||
|
|
||
| const exe = b.addExecutable(.{ | ||
| .name = "dusty", | ||
| .root_module = root_module, | ||
| }); | ||
|
|
||
| exe.root_module.addImport("dusty", dusty_module); | ||
| exe.root_module.addImport("zio", zio_module); | ||
| exe.root_module.addImport("pg", pg_module); | ||
| exe.root_module.addImport("datetimez", datetimez_module); | ||
|
|
||
| // This declares intent for the executable to be installed into the | ||
| // standard location when the user invokes the "install" step (the default | ||
| // step when running `zig build`). | ||
| b.installArtifact(exe); | ||
|
|
||
| // This *creates* a Run step in the build graph, to be executed when another | ||
| // step is evaluated that depends on it. The next line below will establish | ||
| // such a dependency. | ||
| const run_cmd = b.addRunArtifact(exe); | ||
|
|
||
| // By making the run step depend on the install step, it will be run from the | ||
| // installation directory rather than directly from within the cache directory. | ||
| // This is not necessary, however, if the application depends on other installed | ||
| // files, this ensures they will be present and in the expected location. | ||
| run_cmd.step.dependOn(b.getInstallStep()); | ||
|
|
||
| // This allows the user to pass arguments to the application in the build | ||
| // command itself, like this: `zig build run -- arg1 arg2 etc` | ||
| if (b.args) |args| { | ||
| run_cmd.addArgs(args); | ||
| } | ||
|
|
||
| // This creates a build step. It will be visible in the `zig build --help` menu, | ||
| // and can be selected like this: `zig build run` | ||
| // This will evaluate the `run` step rather than the default, which is "install". | ||
| const run_step = b.step("run", "Run the app"); | ||
| run_step.dependOn(&run_cmd.step); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| .{ | ||
| .name = .dusty_testing, | ||
| .fingerprint = 0x402c0022f1133ce0, | ||
| .version = "0.1.1", | ||
| .paths = .{ | ||
| "build.zig", | ||
| "build.zig.zon", | ||
| "src", | ||
| }, | ||
| .dependencies = .{ | ||
| .pg = .{ | ||
| .url = "git+https://github.com/karlseguin/pg.zig.git#f8d4892387fbad2abdf775783e101e50a7114335", | ||
| .hash = "pg-0.0.0-Wp_7gag6BgD_QAZrPhNNEGpnUZR_LEkKT40Ura3p-4yX", | ||
| }, | ||
| .dusty = .{ | ||
| .url = "git+https://github.com/lalinsky/dusty.git#8aaedf71a069758bd88437d117ccd89069dc9bf8", | ||
| .hash = "dusty-0.0.0-Qdw7Rqh_CQDJNptlxOVIRgT4DxHnAKT9KohxfNhSH9bC", | ||
| }, | ||
| .datetimez = .{ | ||
| .url = "git+https://github.com/frmdstryr/zig-datetime.git#3a39a21e6e34dcb0ade0ff828d0914d40ba535f3", | ||
| .hash = "datetime-0.8.0-cJNXzP_YAQBxQ5hkNNP6ScnG5XsqciJmeP5RVV4xwCBA", | ||
| }, | ||
| }, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| FROM debian:12.9 AS build | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY src src | ||
| COPY build.zig.zon build.zig.zon | ||
| COPY build.zig build.zig | ||
|
|
||
| ARG ZIG_VER=0.15.2 | ||
|
|
||
| RUN apt-get update && apt-get install -y wget xz-utils ca-certificates git | ||
|
|
||
| RUN wget https://ziglang.org/download/${ZIG_VER}/zig-$(uname -m)-linux-${ZIG_VER}.tar.xz | ||
|
|
||
| RUN tar -xvf zig-$(uname -m)-linux-${ZIG_VER}.tar.xz | ||
|
|
||
| RUN mv zig-$(uname -m)-linux-${ZIG_VER} /usr/local/zig | ||
|
|
||
| ENV PATH="/usr/local/zig:$PATH" | ||
| RUN zig build -Doptimize=ReleaseFast | ||
|
|
||
|
|
||
| FROM debian:12-slim | ||
|
|
||
| ENV PG_USER=benchmarkdbuser | ||
| ENV PG_PASS=benchmarkdbpass | ||
| ENV PG_DB=hello_world | ||
| ENV PG_HOST=tfb-database | ||
| ENV PG_PORT=5432 | ||
|
|
||
| RUN apt-get -qq update | ||
| RUN apt-get -qy install ca-certificates | ||
|
|
||
| COPY --from=build /app/zig-out/bin/dusty /server | ||
|
|
||
| EXPOSE 3000 | ||
|
|
||
| ENTRYPOINT ["/server"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| const std = @import("std"); | ||
| const dusty = @import("dusty"); | ||
| const pg = @import("pg"); | ||
| const datetimez = @import("datetimez"); | ||
|
|
||
| pub var date_str: [29]u8 = undefined; | ||
|
|
||
| pub const Global = struct { | ||
| pool: *pg.Pool, | ||
| rand: *std.Random, | ||
| }; | ||
|
|
||
| const World = struct { | ||
| id: i32, | ||
| randomNumber: i32, | ||
| }; | ||
|
|
||
| const Fortune = struct { | ||
| id: i32, | ||
| message: []const u8, | ||
| }; | ||
|
|
||
| pub fn plaintext(_: *Global, _: *dusty.Request, res: *dusty.Response) !void { | ||
| try setHeaders(res); | ||
|
|
||
| try res.header("Content-Type", "text/plain"); | ||
| res.body = "Hello, World!"; | ||
| } | ||
|
|
||
| pub fn json(_: *Global, _: *dusty.Request, res: *dusty.Response) !void { | ||
| try setHeaders(res); | ||
|
|
||
| try res.json(.{ .message = "Hello, World!" }, .{}); | ||
| } | ||
|
|
||
| pub fn db(global: *Global, _: *dusty.Request, res: *dusty.Response) !void { | ||
| try setHeaders(res); | ||
|
|
||
| const random_number = 1 + (global.rand.uintAtMostBiased(u32, 9999)); | ||
|
|
||
| const world = getWorld(global.pool, random_number) catch |err| { | ||
| std.debug.print("Error querying database: {}\n", .{err}); | ||
| return; | ||
| }; | ||
|
|
||
| try res.json(world, .{}); | ||
| } | ||
|
|
||
| pub fn fortune(global: *Global, _: *dusty.Request, res: *dusty.Response) !void { | ||
| try setHeaders(res); | ||
|
|
||
| const fortunes_html = try getFortunesHtml(res.arena, global.pool); | ||
|
|
||
| try res.header("Content-Type", "text/html; charset=utf-8"); | ||
| res.body = fortunes_html; | ||
| } | ||
|
|
||
| fn getWorld(pool: *pg.Pool, random_number: u32) !World { | ||
| var conn = try pool.acquire(); | ||
| defer conn.release(); | ||
|
|
||
| const row_result = try conn.row("SELECT id, randomNumber FROM World WHERE id = $1", .{random_number}); | ||
|
|
||
| var row = row_result.?; | ||
| defer row.deinit() catch {}; | ||
|
|
||
| return World{ .id = row.get(i32, 0), .randomNumber = row.get(i32, 1) }; | ||
| } | ||
|
|
||
| fn setHeaders(res: *dusty.Response) !void { | ||
| try res.header("Server", "Dusty"); | ||
| try res.header("Date", try res.arena.dupe(u8, &date_str)); | ||
| } | ||
|
|
||
| fn getFortunesHtml(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const u8 { | ||
| const fortunes = try getFortunes(allocator, pool); | ||
|
|
||
| var sb = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0); | ||
|
|
||
| const writer = sb.writer(allocator); | ||
| try sb.appendSlice(allocator, "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"); | ||
|
|
||
| for (fortunes) |ft| { | ||
| try writer.print("<tr><td>{d}</td><td>{s}</td></tr>", .{ | ||
| ft.id, | ||
| try escape_html(allocator, ft.message), | ||
| }); | ||
| } | ||
|
|
||
| try sb.appendSlice(allocator, "</table></body></html>"); | ||
|
|
||
| return sb.toOwnedSlice(allocator); | ||
| } | ||
|
|
||
| fn getFortunes(allocator: std.mem.Allocator, pool: *pg.Pool) ![]const Fortune { | ||
| var conn = try pool.acquire(); | ||
| defer conn.release(); | ||
|
|
||
| var rows = try conn.query("SELECT id, message FROM Fortune", .{}); | ||
| defer rows.deinit(); | ||
|
|
||
| var fortunes = try std.ArrayListUnmanaged(Fortune).initCapacity(allocator, 0); | ||
| defer fortunes.deinit(allocator); | ||
|
|
||
| while (try rows.next()) |row| { | ||
| const current_fortune = Fortune{ .id = row.get(i32, 0), .message = row.get([]const u8, 1) }; | ||
| try fortunes.append(allocator, current_fortune); | ||
| } | ||
|
|
||
| const zero_fortune = Fortune{ .id = 0, .message = "Additional fortune added at request time." }; | ||
| try fortunes.append(allocator, zero_fortune); | ||
|
|
||
| const fortunes_slice = try fortunes.toOwnedSlice(allocator); | ||
| std.mem.sort(Fortune, fortunes_slice, {}, cmpFortuneByMessage); | ||
|
|
||
| return fortunes_slice; | ||
| } | ||
|
|
||
| fn cmpFortuneByMessage(_: void, a: Fortune, b: Fortune) bool { | ||
| return std.mem.order(u8, a.message, b.message).compare(std.math.CompareOperator.lt); | ||
| } | ||
|
|
||
| fn escape_html(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { | ||
| var output = try std.ArrayListUnmanaged(u8).initCapacity(allocator, 0); | ||
| defer output.deinit(allocator); | ||
|
|
||
| for (input) |char| { | ||
| switch (char) { | ||
| '<' => try output.appendSlice(allocator, "<"), | ||
| '>' => try output.appendSlice(allocator, ">"), | ||
| else => try output.append(allocator, char), | ||
| } | ||
| } | ||
|
|
||
| return output.toOwnedSlice(allocator); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.