- Zig 95.8%
- Nix 4.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
test / test (push) Successful in 2m17s
Moving to a new Forgejo release was a curl piped through a python one-liner
in the README, retyped by hand each time and with nothing checking the
result. It is a build step now:
zig build update-spec # the latest release
zig build update-spec -Dforgejo-version=v16.0.3
The tag is optional because Forgejo's own API answers which release is the
latest; it is available because Forgejo maintains several release branches
at once, so the newest release is not always a successor to the one being
replaced.
The step reaches the network, so it stands alone and nothing else depends
on it -- an ordinary build never runs it. It is also marked as having side
effects, since what a URL answers is not a function of the command line
and must not be cached on it.
Rendering the template is by substitution of its two Go placeholders, and
both are required to be present: the day Forgejo renames one or adds a
third, this fails rather than vendoring a file with a template expression
in the middle of it. What comes back is then parsed and checked -- that it
is JSON, that it is Swagger 2.0 against /api/v1, that its version is the
release that was asked for, and that it describes any paths at all --
before `addUpdateSourceFiles` is allowed to put it in the source tree.
Checked against v15.0.7 as well as v16.0.3: the specification swaps, the
bindings regenerate, and the tests pass on both, which is also a fair sign
the tests are not quietly pinned to one release's peculiarities.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NghACEDMEDVNfKVf1EGfwH
|
||
| .forgejo/workflows | ||
| api | ||
| LICENSES | ||
| src | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| REUSE.toml | ||
zfj
Zig bindings for the Forgejo API. All 506 operations and 246 models of Forgejo v16.0.3, typed, with the response of every call parsed into the model the API documentation names for it.
Requires Zig 0.16.
The bindings are generated, not written
This repository holds the API description — api/swagger.v1.json, taken from
the Forgejo v16.0.3 release — and a small program that turns it into Zig. The
build runs that program and compiles what it prints; no generated source is
checked in, so the bindings cannot drift from the specification they came from.
Updating to a new Forgejo release is therefore a matter of replacing one file. See Updating the specification.
zig build leaves the generated source at zig-out/api.zig, which is the
reference for what the library actually exposes. zig build docs renders the
same thing as browsable documentation.
Using it
Add the dependency:
zig fetch --save https://git.ocjtech.us/jeff/zfj/archive/main.tar.gz
then wire the module up in build.zig:
const zfj = b.dependency("zfj", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zfj", zfj.module("zfj"));
Operations are grouped the way the Forgejo API documentation groups them, one
client per tag — IssueClient, RepositoryClient, UserClient,
OrganizationClient, AdminClient, NotificationClient, PackageClient,
SettingsClient, MiscellaneousClient, ActivitypubClient:
const std = @import("std");
const zfj = @import("zfj");
pub fn main(init: std.process.Init) !u8 {
const allocator = init.gpa;
var client = zfj.Client.init(allocator, init.io, std.posix.getenv("FORGEJO_TOKEN").?);
defer client.deinit();
client.withBaseUrl("https://codeberg.org/api/v1");
var issues = zfj.IssueClient.init(&client);
var issue = try issues.issueGetIssue(.{
.owner = "forgejo",
.repo = "forgejo",
.index = 42,
});
defer issue.deinit();
std.debug.print("{s}\n", .{issue.value().title.?});
return 0;
}
Every operation is also a plain function taking the client as its first
argument, so the call above can equally be written
zfj.issueGetIssue(&client, .{ ... }). The tag clients are a grouping over
those functions and nothing more.
Three forms of each operation are generated:
| Form | Returns | For |
|---|---|---|
issueGetIssue |
Owned(Issue) |
the usual case: a parsed model, or error.ResponseError |
issueGetIssueResult |
ApiResult(Issue) |
when the failing status code matters |
issueGetIssueRaw |
RawResponse |
when the bytes matter |
Owned(T) owns both the response body and the parsed model; deinit() frees
both, and value() borrows the model out of it. Model fields are optional
and default to null, because the API omits rather than nulls what it has no
value for.
Operations whose only successful response is empty — the deletes, mostly —
return !void.
Authentication
The token passed to Client.init is sent as Authorization: Bearer <token>.
Forgejo accepts that spelling as well as the token <token> its documentation
describes; both are read by the same code path. Pass an empty string to make
requests unauthenticated.
Cloning with Radicle
The repository is also published on the Radicle peer-to-peer network, where a repository is found by its Repository ID rather than by a server name:
rad:z2LTU53rqByAo7pj2BM1vJHeKYJSb
With a local Radicle node running, clone it with:
$ rad clone rad:z2LTU53rqByAo7pj2BM1vJHeKYJSb
That fetches the repository, checks out the default branch, and starts seeding
it so other peers can fetch from you. To follow the repository without checking
out a working copy, use rad seed instead:
$ rad seed rad:z2LTU53rqByAo7pj2BM1vJHeKYJSb
If you already have a clone from the git remote above, you can attach it to the same Radicle repository rather than cloning again:
$ git remote add rad rad://z2LTU53rqByAo7pj2BM1vJHeKYJSb
$ git fetch rad
Updating the specification
$ zig build update-spec
fetches Forgejo's latest release and rewrites api/swagger.v1.json from it.
To take a particular release instead — an older one, or a newer one that is
not yet the latest across all of Forgejo's parallel release branches — name
its tag:
$ zig build update-spec -Dforgejo-version=v16.0.3
Then run zig build test. Nothing else in the repository names a Forgejo
version, so that is the whole of it.
The step is the only thing here that reaches the network, and nothing else
depends on it: an ordinary build never runs it. What it fetches is
templates/swagger/v1_json.tmpl from the release tag, which is the document
an instance renders and serves at /swagger.v1.json. Taking it from the tag
rather than from a running instance is what makes the vendored file say which
Forgejo it describes — an instance's copy is stamped with whatever build
happens to be deployed there.
The template is plain JSON but for two Go placeholders, the instance's version and its sub-URL, and both are required to be present. If a future Forgejo renames one or adds a third, the step fails rather than vendoring a file with a template expression left in the middle of it. What it fetched is also parsed and checked over — that it is JSON, that it is Swagger 2.0, that its version is the release asked for, and that it describes any paths at all — before it is allowed to replace the file in the source tree.
License
MIT, and the repository follows the REUSE
specification — reuse lint checks it. api/swagger.v1.json is Forgejo's own,
distributed by them under the MIT license for the purpose of interoperability.