Custom Providers
Create fully custom providers for any Git hosting solution.
Creating a Custom Provider
zig
const myProvider = updater.providers.custom(
"my-git-server", // Provider name
buildMyUrl, // URL builder function
parseMyResponse, // Response parser function
&myHeaders, // HTTP headers
);Required Functions
URL Builder
zig
fn buildMyUrl(
allocator: std.mem.Allocator,
owner: []const u8,
repo: []const u8,
) std.mem.Allocator.Error![]const u8 {
return std.fmt.allocPrint(
allocator,
"https://git.example.com/api/repos/{s}/{s}/latest",
.{ owner, repo },
);
}Response Parser
zig
fn parseMyResponse(
allocator: std.mem.Allocator,
body: []const u8,
) updater.GitProvider.ParseError!updater.ReleaseInfo {
const parsed = std.json.parseFromSlice(
std.json.Value,
allocator,
body,
.{},
) catch return error.InvalidJson;
defer parsed.deinit();
// Extract fields from JSON
const root = parsed.value;
if (root != .object) return error.InvalidFormat;
const tag = extractString(root, "version") orelse return error.MissingField;
const url = extractString(root, "url") orelse return error.MissingField;
return updater.ReleaseInfo{
.tag = try allocator.dupe(u8, tag),
.url = try allocator.dupe(u8, url),
};
}
fn extractString(value: std.json.Value, key: []const u8) ?[]const u8 {
if (value != .object) return null;
const field = value.object.get(key) orelse return null;
if (field != .string) return null;
return field.string;
}Complete Example
zig
const std = @import("std");
const updater = @import("updater");
// Custom headers
const myHeaders = [_]updater.HttpHeader{
.{ .name = "Accept", .value = "application/json" },
.{ .name = "User-Agent", .value = "my-app/1.0" },
.{ .name = "X-Api-Key", .value = "secret-key" },
};
fn buildMyUrl(
allocator: std.mem.Allocator,
owner: []const u8,
repo: []const u8,
) std.mem.Allocator.Error![]const u8 {
return std.fmt.allocPrint(
allocator,
"https://releases.example.com/api/v2/{s}/{s}/current",
.{ owner, repo },
);
}
fn parseMyResponse(
allocator: std.mem.Allocator,
body: []const u8,
) updater.GitProvider.ParseError!updater.ReleaseInfo {
// Parse custom JSON format:
// {"version": "1.2.3", "download": "https://...", "notes": "..."}
const parsed = std.json.parseFromSlice(
std.json.Value,
allocator,
body,
.{},
) catch return error.InvalidJson;
defer parsed.deinit();
const root = parsed.value;
if (root != .object) return error.InvalidFormat;
// Required: version
const version_field = root.object.get("version") orelse return error.MissingField;
if (version_field != .string) return error.InvalidFormat;
const tag = allocator.dupe(u8, version_field.string) catch return error.OutOfMemory;
errdefer allocator.free(tag);
// Required: download URL
const url_field = root.object.get("download") orelse return error.MissingField;
if (url_field != .string) return error.InvalidFormat;
const url = allocator.dupe(u8, url_field.string) catch return error.OutOfMemory;
errdefer allocator.free(url);
// Optional: notes
var body_text: ?[]const u8 = null;
if (root.object.get("notes")) |notes| {
if (notes == .string) {
body_text = allocator.dupe(u8, notes.string) catch return error.OutOfMemory;
}
}
return updater.ReleaseInfo{
.tag = tag,
.url = url,
.body = body_text,
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const myProvider = updater.providers.custom(
"my-releases",
buildMyUrl,
parseMyResponse,
&myHeaders,
);
const result = try updater.checkForUpdates(allocator, .{
.provider = myProvider,
.owner = "company",
.repo = "product",
.current_version = "1.0.0",
});
if (result.has_update) {
std.debug.print("Update: {s}\n", .{result.latest_version.?});
}
}Error Handling
Your parser should return appropriate errors:
zig
pub const ParseError = error{
InvalidJson, // JSON parsing failed
MissingField, // Required field not present
InvalidFormat, // Field has wrong type
OutOfMemory, // Allocation failed
};Memory Management
Important: All strings returned in ReleaseInfo must be allocated using the provided allocator. The caller will free them using ReleaseInfo.deinit().
zig
// Good: allocate copies
const tag = allocator.dupe(u8, raw_tag) catch return error.OutOfMemory;
// Bad: returning slice from parsed JSON (will be freed)
const tag = raw_tag; // DON'T DO THISTesting
Test your provider with mock responses:
zig
test "custom provider parses response" {
const allocator = std.testing.allocator;
const mock_response =
\\{"version": "2.0.0", "download": "https://example.com/v2.0.0"}
;
var info = try parseMyResponse(allocator, mock_response);
defer info.deinit(allocator);
try std.testing.expectEqualStrings("2.0.0", info.tag);
}