Skip to content

Commit 317ee55

Browse files
committed
fix: load runtime config and escape --from-json output
1 parent 0ce92c9 commit 317ee55

3 files changed

Lines changed: 86 additions & 9 deletions

File tree

src/config.zig

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const std = @import("std");
2+
3+
pub const Config = struct {
4+
port: u16 = 7700,
5+
db: []const u8 = "nulltickets.db",
6+
};
7+
8+
/// Load runtime config from JSON file. Missing file means defaults.
9+
/// The caller should provide an arena allocator since returned slices may point
10+
/// to parser-owned allocations.
11+
pub fn loadFromFile(allocator: std.mem.Allocator, path: []const u8) !Config {
12+
const file = std.fs.cwd().openFile(path, .{}) catch |err| {
13+
if (err == error.FileNotFound) return Config{};
14+
return err;
15+
};
16+
defer file.close();
17+
18+
const contents = try file.readToEndAlloc(allocator, 1024 * 1024);
19+
const parsed = try std.json.parseFromSlice(Config, allocator, contents, .{ .ignore_unknown_fields = true });
20+
return parsed.value;
21+
}
22+
23+
test "loadFromFile returns defaults when missing" {
24+
const cfg = try loadFromFile(std.testing.allocator, "nonexistent-config-file-12345.json");
25+
try std.testing.expectEqual(@as(u16, 7700), cfg.port);
26+
try std.testing.expectEqualStrings("nulltickets.db", cfg.db);
27+
}
28+
29+
test "loadFromFile reads config values" {
30+
var tmp = std.testing.tmpDir(.{});
31+
defer tmp.cleanup();
32+
33+
try tmp.dir.writeFile(.{
34+
.sub_path = "config.json",
35+
.data =
36+
\\{
37+
\\ "port": 7788,
38+
\\ "db": "tickets.db"
39+
\\}
40+
,
41+
});
42+
43+
const cfg_path = try tmp.dir.realpathAlloc(std.testing.allocator, "config.json");
44+
defer std.testing.allocator.free(cfg_path);
45+
46+
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
47+
defer arena.deinit();
48+
49+
const cfg = try loadFromFile(arena.allocator(), cfg_path);
50+
try std.testing.expectEqual(@as(u16, 7788), cfg.port);
51+
try std.testing.expectEqualStrings("tickets.db", cfg.db);
52+
}

src/from_json.zig

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ pub fn run(allocator: std.mem.Allocator, json_str: []const u8) !void {
1212
};
1313
defer parsed.deinit();
1414

15-
var buf: std.ArrayList(u8) = .{};
16-
defer buf.deinit(allocator);
17-
const w = buf.writer(allocator);
18-
try std.fmt.format(w, "{{\n \"port\": {d},\n \"db\": \"{s}\"\n}}\n", .{ parsed.value.port, parsed.value.db_path });
15+
const config_json = try std.json.Stringify.valueAlloc(allocator, .{
16+
.port = parsed.value.port,
17+
.db = parsed.value.db_path,
18+
}, .{ .whitespace = .indent_2 });
19+
defer allocator.free(config_json);
1920

2021
const file = try std.fs.cwd().createFile("config.json", .{});
2122
defer file.close();
22-
try file.writeAll(buf.items);
23+
try file.writeAll(config_json);
24+
try file.writeAll("\n");
2325

2426
const stdout = std.fs.File.stdout();
2527
try stdout.writeAll("{\"status\":\"ok\"}\n");

src/main.zig

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const std = @import("std");
22
const Store = @import("store.zig").Store;
33
const api = @import("api.zig");
4+
const config = @import("config.zig");
45

56
const version = "0.1.0";
67

@@ -35,27 +36,49 @@ pub fn main() !void {
3536
defer args2.deinit();
3637
_ = args2.next(); // skip program name
3738

38-
var port: u16 = 7700;
39-
var db_path: [:0]const u8 = "nulltickets.db";
39+
var port_override: ?u16 = null;
40+
var db_override: ?[:0]const u8 = null;
41+
var config_path: []const u8 = "config.json";
4042

4143
while (args2.next()) |arg| {
4244
if (std.mem.eql(u8, arg, "--port")) {
4345
if (args2.next()) |val| {
44-
port = std.fmt.parseInt(u16, val, 10) catch {
46+
port_override = std.fmt.parseInt(u16, val, 10) catch {
4547
std.debug.print("invalid port: {s}\n", .{val});
4648
return;
4749
};
4850
}
4951
} else if (std.mem.eql(u8, arg, "--db")) {
5052
if (args2.next()) |val| {
51-
db_path = val;
53+
db_override = val;
54+
}
55+
} else if (std.mem.eql(u8, arg, "--config")) {
56+
if (args2.next()) |val| {
57+
config_path = val;
5258
}
5359
} else if (std.mem.eql(u8, arg, "--version")) {
5460
std.debug.print("nulltickets v{s}\n", .{version});
5561
return;
5662
}
5763
}
5864

65+
var cfg_arena = std.heap.ArenaAllocator.init(allocator);
66+
defer cfg_arena.deinit();
67+
const cfg = config.loadFromFile(cfg_arena.allocator(), config_path) catch |err| {
68+
std.debug.print("failed to load config from {s}: {}\n", .{ config_path, err });
69+
return;
70+
};
71+
72+
const port = port_override orelse cfg.port;
73+
const db_path: [:0]const u8 = db_override orelse blk: {
74+
const db_z = cfg_arena.allocator().allocSentinel(u8, cfg.db.len, 0) catch {
75+
std.debug.print("out of memory\n", .{});
76+
return;
77+
};
78+
@memcpy(db_z, cfg.db);
79+
break :blk db_z;
80+
};
81+
5982
std.debug.print("nulltickets v{s}\n", .{version});
6083
std.debug.print("opening database: {s}\n", .{db_path});
6184

0 commit comments

Comments
 (0)