This is probably the sort of thing that I wouldn't actually think of
having explicit test coverage for if I hadn't accidentally ran across
it while fiddling. I will compensate for this easily by not actually
writing tests at all.
This was the point that I realized there's no reason to have the string
variants of the publishing methods. But also there's not really much
point in porting the other getting-started examples, since we've
covered all their functionality in the existing examples
(actually, this one is redundant too, but I have already done it, so
it's getting grandfathered in).
Porting some of the more interesting examples might be a good idea, but
those have a weird argument parser that I don't really want to port
(even though it is very simple in the way that it works). For the most
part, I think writing unit tests will do a better of flexing the
bindings.
There's not much of a reason this should be used externally, but I also
favor having things be exported rather than not exported. There will
probably be more opening up of the other imports here in the future.
After contemplating this for a little bit, there's no point in exposing
these separately from the plain variants. Even nats.c internally calls
its plain variants after calling `strlen` on the input. Zig benefits
from having nicer pointer types than C, so we get "string" handling
for free from the fact that the standard variants take slices anyway.
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.
This approach seems nice, and as a bonus it makes it easier to run the
tests through the module interface rather than by importing the
sources directly, which I think is a good dog food approach.
I believe the standard API surface is now completely wrapped, ignoring
whatever cool copy-paste errors or other mistakes I introduced which
are currently undetectable due to lazy analysis and lack of test
coverage. Cool.
This was kind of tedious, but there were some interesting choices to be
made with regards to how the callback signatures should be adapted,
especially in the few cases that heavily use out-params in C. Ignoring
the lack of test coverage, this puts us pretty close to having the
entire basic API wrapped.
There are a several options that set up callbacks that I did not wrap
in this first pass because I need to decide on whether or not it makes
sense to move the various thunks into a common location. There's one
that could be shared between the subscriptions and here due to having
the same signature, but I'm not sure there are any others that would
really benefit from cross-file sharing.
There is a case-sensitivity issue here. The Windows documentation calls
the library Ws2_32, but the mingw cross-compilation on Linux fails
unless it is called `ws3_32`. For some reason, both work on macOS. I
have not tried on Windows, but I assume this will probably work there
due to everything being extremely insensitive (case-wise) on Windows.
I think I am probably going to move unit tests to a separate
directory/file structure. This will allow me to add a bunch of utility
functions that don't get analyzed for the library compilation and also
avoid testing-only imports in the main modules.
The streaming API is not wrapped because we do not build nats.c with
the necessary libraries. The jetstream API is not wrapped because I am
extremely lazy and it's outside of the minimum viable scope at the
moment. It can happen after the other basic APIs have been wrapped.
I'm going to try out git-subrepo. The main reason for this is that
github's autogenerated tarballs do not include submodules. And
github's autogenerated tarballs are the easiest way to integrate into
the zig package manager. Since git-subrepo checks the files into the
tree directly, new tarballs should include this dependency code.
This is pretty much just the API surface that is used in the demo code
for now, with a couple of minor things I tacked on while getting a
feel for how I was going to go about it. Unfortunately, the diff is
too messy to show the improvement provided by the wrapped API, but I'm
pretty pleased with how it is turning out so far. In general, there
have been no major hiccups.
Most of the time working on this was spent noodling about how to create
the subscription callback thunk to provide type safety and pointer
nullability guarantees for the subscription zig API. I'm satisfied
with the solution in place now: it seems to have good default
ergonomics and very few downsides except that the callback function
must be known at compile time. An earlier iteration of this code
supported runtime function pointers but had a showstopping problem
with memory management. In order to store the runtime callback
pointer, the thunk would need to be allocated, but there was no way of
attaching our allocated memory to the c-library subscription object.
No attachment meant that there were a number of error scenarios where
the allocated thunk couldn't be freed correctly.
Ultimately, if the user needs to use runtime callback pointers they can
write a comptime known callback that calls a runtime known callback.
This way all of the object lifetime and ownership is in the user's
hands, which is really where it needs to belong anyway for this.
This builds a very basic version of the nats.c client (no TLS, no
streaming/jetstream/whatever, since those bring in complex
dependencies and I do not need them at the moment). Right now it
contains a simple test program that demonstrates the functionality
(cool!), but the plan is for the nats.zig to bind the API into a
nicer, zig-like shape and re-export it. Then this becomes a package.
The current function could become a test, though it's a bit complex
for a unit test (and requires connecting to an externally-running NATS
server in order to work).