//! C ABI over the pure core.
//!
//! One rule for every export: return a JSON string the caller frees with `usage_free`,
//! and report failure *inside* that JSON. A host never has to tell "no error" apart from
//! "no data", and NULL means only that allocation failed.
//!
//! The core owns no cross-call state; the host owns it via usage_state and passes it back.
const std = @import("std");
const usage = @import("usage.zig");
const alloc = std.heap.c_allocator;
/// Opaque cross-round state struct owned by the host.
pub const usage_state = struct {
inner: usage.State,
};
/// C callers (Swift included) pass NULL for an absent string; treat it as empty rather
/// than dereferencing a null pointer.
fn slice(ptr: ?[*]const u8, len: usize) []const u8 {
const p = ptr orelse return "";
return p[0..len];
}
fn finish(w: *usage.Writer) ?[*:0]u8 {
return alloc.dupeZ(u8, w.items()) catch null;
}
fn errorResult(a: std.mem.Allocator, text: []const u8) ?[*:0]u8 {
var w = usage.Writer.init(a);
w.raw("{\"error\":") catch return null;
w.string(text) catch return null;
w.raw("}") catch return null;
return finish(&w);
}
fn message(error_code: anyerror) []const u8 {
return switch (error_code) {
error.OutOfMemory => "out of memory",
else => "usage query failed",
};
}
export fn usage_abi_version() u32 {
return usage.abi_version;
}
export fn usage_free(ptr: ?[*:0]u8) void {
if (ptr) |p| alloc.free(std.mem.span(p));
}
export fn usage_state_new() ?*usage_state {
const s = alloc.create(usage_state) catch return null;
s.inner = usage.State.init(alloc);
return s;
}
export fn usage_state_free(state: ?*usage_state) void {
if (state) |s| {
s.inner.deinit();
alloc.destroy(s);
}
}
export fn usage_providers() ?[*:0]u8 {
var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit();
const a = arena.allocator();
var w = usage.Writer.init(a);
usage.writeProviders(&w) catch return null;
return finish(&w);
}
export fn usage_advance(
state: ?*usage_state,
provider: ?[*]const u8,
provider_len: usize,
credential_json: ?[*]const u8,
credential_len: usize,
responses_json: ?[*]const u8,
responses_len: usize,
now_ms: i64,
) ?[*:0]u8 {
var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit();
const a = arena.allocator();
const prov_slice = slice(provider, provider_len);
const cred_slice = slice(credential_json, credential_len);
const resp_slice = slice(responses_json, responses_len);
const state_ptr: ?*usage.State = if (state) |s| &s.inner else null;
const outcome = usage.advance(a, state_ptr, prov_slice, cred_slice, resp_slice, now_ms) catch |e| {
return errorResult(a, message(e));
};
var w = usage.Writer.init(a);
usage.writeStep(&w, outcome) catch |e| {
return errorResult(a, message(e));
};
return finish(&w);
}
export fn usage_credentials_from_access_token(jwt: ?[*]const u8, jwt_len: usize) ?[*:0]u8 {
var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit();
const a = arena.allocator();
var w = usage.Writer.init(a);
const outcome = usage.claimsFromAccessToken(a, slice(jwt, jwt_len), &w) catch |e| {
return errorResult(a, message(e));
};
switch (outcome) {
.ok => return finish(&w),
.err => |text| return errorResult(a, text),
}
}