Compare commits

...

2 Commits

Author SHA1 Message Date
ebd3e64111
examples: port header example to zig
This looks a lot nicer than its c counterpart, in my opinion.
2023-08-23 01:05:37 -07:00
3e5840bd84
message: fix header method types and add header iterator
As I ascertained from reading the code, nats.c does in fact let you set
message headers with null values. However, the library segfaults if
you try to actually send such a message over the wire. So we just
forbid it with stricter typing and assume it's not possible for
someone to send us a message containing headers with a null value as
well.
2023-08-23 01:03:29 -07:00
3 changed files with 115 additions and 11 deletions

View File

@ -53,6 +53,7 @@ const Example = struct {
const examples = [_]Example{
.{ .name = "request_reply", .file = "examples/request_reply.zig" },
.{ .name = "headers", .file = "examples/headers.zig" },
};
pub fn add_examples(b: *std.build, options: ExampleOptions) void {

62
examples/headers.zig Normal file
View File

@ -0,0 +1,62 @@
const std = @import("std");
const nats = @import("nats");
pub fn main() !void {
const connection = try nats.Connection.connectTo(nats.default_server_url);
defer connection.destroy();
const message = try nats.Message.create("subject", null, "message");
defer message.destroy();
try message.setHeaderValue("My-Key1", "value1");
try message.setHeaderValue("My-Key2", "value2");
try message.addHeaderValue("My-Key1", "value3");
try message.setHeaderValue("My-Key3", "value4");
try message.deleteHeader("My-Key3");
var iter = try message.headerIterator();
defer iter.destroy();
while (try iter.next()) |pair| {
std.debug.print(
"Key: '{s}', Value: '{s}'\n",
.{ pair.key, pair.value orelse "null" },
);
}
const subscription = try connection.subscribeSync("subject");
defer subscription.destroy();
try connection.publishMessage(message);
const received = try subscription.nextMessage(1000);
defer received.destroy();
{
const vals1 = try received.getAllHeaderValues("My-Key1");
defer std.heap.raw_c_allocator.free(vals1);
std.debug.print("For key 'My-Key1' got: ", .{});
for (vals1) |value| {
const val = std.mem.sliceTo(value, 0);
std.debug.print("'{s}', ", .{val});
}
std.debug.print("\n", .{});
}
_ = received.getHeaderValue("key-does-not-exist") catch |err| switch (err) {
nats.Error.NotFound => {},
else => {
std.debug.print("Should not have found that key!", .{});
return err;
},
};
received.deleteHeader("key-does-not-exist") catch |err| switch (err) {
nats.Error.NotFound => {},
else => {
std.debug.print("Should not have found that key!", .{});
return err;
},
};
}

View File

@ -45,36 +45,39 @@ pub const Message = opaque {
return @intCast(nats_c.natsMsg_GetDataLength(@ptrCast(self)));
}
pub fn setHeaderValue(self: *Message, key: [:0]const u8, value: ?[:0]const u8) Error!void {
pub fn setHeaderValue(self: *Message, key: [:0]const u8, value: [:0]const u8) Error!void {
const status = Status.fromInt(nats_c.natsMsgHeader_Set(@ptrCast(self), key.ptr, value.ptr));
return status.raise();
}
pub fn addHeaderValue(self: *Message, key: [:0]const u8, value: ?[:0]const u8) Error!void {
pub fn addHeaderValue(self: *Message, key: [:0]const u8, value: [:0]const u8) Error!void {
const status = Status.fromInt(nats_c.natsMsgHeader_Add(@ptrCast(self), key.ptr, value.ptr));
return status.raise();
}
pub fn getHeaderValue(self: *Message, key: [:0]const u8) Error!?[:0]const u8 {
var value: ?[*]u8 = null;
pub fn getHeaderValue(self: *Message, key: [:0]const u8) Error![:0]const u8 {
var value: ?[*:0]u8 = null;
const status = Status.fromInt(nats_c.natsMsgHeader_Get(@ptrCast(self), key.ptr, &value));
return status.toError() orelse if (value) |val| std.mem.sliceTo(u8, val, 0) else null;
return status.toError() orelse std.mem.sliceTo(value.?, 0);
}
pub fn getAllHeaderValues(self: *Message, key: [:0]const u8) Error![]?[*]const u8 {
var values: [*]?[*]const u8 = undefined;
pub fn getAllHeaderValues(self: *Message, key: [:0]const u8) Error![][*:0]const u8 {
var values: [*c][*c]const u8 = undefined;
var count: c_int = 0;
const status = Status.fromInt(nats_c.natsMsgHeader_Values(@ptrCast(self), key.ptr, &values, &count));
// the user must use std.mem.spanTo on each item they want to read to get a
// slice, since we can't do that automatically without having to allocate.
return status.toError() orelse values[0..@intCast(count)];
return status.toError() orelse blk: {
const coerced: [*][*:0]const u8 = @ptrFromInt(@intFromPtr(values));
break :blk coerced[0..@intCast(count)];
};
}
pub fn getAllHeaderKeys(self: *Message) Error![][*]const u8 {
var keys: [*][*]const u8 = undefined;
pub fn getAllHeaderKeys(self: *Message) Error![][*:0]const u8 {
var keys: [*c][*c]const u8 = undefined;
var count: c_int = 0;
const status = Status.fromInt(nats_c.natsMsgHeader_Keys(@ptrCast(self), &keys, &count));
@ -83,7 +86,45 @@ pub const Message = opaque {
// the user must use std.mem.spanTo on each item they want to read to get a
// slice, since we can't do that automatically without having to allocate.
return status.toError() orelse keys[0..@intCast(count)];
// the returned slice
return status.toError() orelse blk: {
const coerced: [*][*:0]const u8 = @ptrFromInt(@intFromPtr(keys));
break :blk coerced[0..@intCast(count)];
};
}
pub const HeaderIterator = struct {
message: *Message,
keys: [][*:0]const u8,
index: usize = 0,
pub fn destroy(self: *HeaderIterator) void {
std.heap.raw_c_allocator.free(self.keys);
}
pub fn next(self: *HeaderIterator) Error!?struct { key: [:0]const u8, value: ?[:0]const u8 } {
if (self.index >= self.keys.len) return null;
defer self.index += 1;
const sliced = std.mem.sliceTo(self.keys[self.index], 0);
return .{
.key = sliced,
.value = try self.message.getHeaderValue(sliced),
};
}
pub fn nextKey(self: *HeaderIterator) ?[:0]const u8 {
if (self.index >= self.keys.len) return null;
defer self.index += 1;
return std.mem.sliceTo(self.keys[self.index], 0);
}
};
pub fn headerIterator(self: *Message) Error!HeaderIterator {
return .{
.message = self,
.keys = try self.getAllHeaderKeys(),
};
}
pub fn deleteHeader(self: *Message, key: [:0]const u8) Error!void {