polls on atproto pollz.waow.tech
atproto zig

zig 0.15 notes#

reference for zig 0.15 patterns used in the backend.

breaking changes from 0.14#

json.stringify → json.fmt#

// old: json.stringify(value, .{}, writer);
// new: use json.fmt formatter
try buffer.print(allocator, "{f}", .{json.fmt(value, .{})});

std.ArrayList is unmanaged by default#

// old 0.14 style:
var list = std.ArrayList(u8).init(allocator);
try list.appendSlice("hello");
list.deinit();

// new 0.15 style:
var list: std.ArrayList(u8) = .{};
try list.appendSlice(allocator, "hello");
list.deinit(allocator);

std.time.sleep removed#

// use posix.nanosleep instead
std.posix.nanosleep(seconds, nanoseconds);

std.Uri.percentDecode → percentDecodeInPlace#

// copy to mutable buffer first, then decode in place
const uri_buf = try alloc.dupe(u8, uri_encoded);
const uri = std.Uri.percentDecodeInPlace(uri_buf);

http server patterns#

net.Stream → http.Server#

var read_buffer: [8192]u8 = undefined;
var write_buffer: [8192]u8 = undefined;

var reader = conn.stream.reader(&read_buffer);
var writer = conn.stream.writer(&write_buffer);

var server = http.Server.init(reader.interface(), &writer.interface);

responding to requests#

try request.respond(body, .{
    .status = .ok,
    .extra_headers = &.{
        .{ .name = "content-type", .value = "application/json" },
    },
});

websocket client (karlseguin/websocket.zig)#

const websocket = @import("websocket");

var client = try websocket.Client.init(allocator, .{
    .host = "example.com",
    .port = 443,
    .tls = true,
});
defer client.deinit();

// Host header must be provided manually
client.handshake("/path", .{ .headers = "Host: example.com\r\n" }) catch |err| {
    // handle error
};

// handler must have serverMessage(self, data) function
var handler = MyHandler{};
try client.readLoop(&handler);

sqlite patterns (zqlite)#

prepared statements with bind#

var stmt = conn.prepare("SELECT * FROM votes WHERE uri = ?") catch return;
defer stmt.deinit();

const row = stmt.bind(.{uri}).step() catch return;
if (row) |r| {
    const subject = r[0].?.text;
    // ...
}

upsert with ON CONFLICT#

conn.exec(
    \\INSERT INTO votes (uri, subject, option, voter, created_at)
    \\VALUES (?, ?, ?, ?, ?)
    \\ON CONFLICT(subject, voter) DO UPDATE SET
    \\  uri = excluded.uri,
    \\  option = excluded.option,
    \\  created_at = excluded.created_at
    \\WHERE excluded.created_at > votes.created_at OR votes.created_at IS NULL
, .{ uri, subject, option, voter, created_at }) catch |err| {
    // handle error
};

build.zig.zon#

.{
    .name = .pollz,
    .version = "0.0.0",
    .fingerprint = 0x...,  // required in 0.15
    .dependencies = .{
        .zqlite = .{ ... },
        .websocket = .{ ... },
    },
}