Skip to content

Version Comparison

updater.zig does not assume any particular versioning scheme. You can use built-in comparators or create your own.

Built-in Comparators

Semantic Versioning

The default comparator follows Semantic Versioning 2.0.0:

zig
const comparator = updater.version.semantic;

Supports:

  • Major.Minor.Patch format (e.g., 1.2.3)
  • Optional v prefix (e.g., v1.2.3)
  • Prerelease identifiers (e.g., 1.0.0-alpha.1)
  • Build metadata (e.g., 1.0.0+build.123)

Examples:

  • 1.0.0 < 1.0.1 < 1.1.0 < 2.0.0
  • 1.0.0-alpha < 1.0.0-beta < 1.0.0
  • v1.0.0 == 1.0.0

Numeric Versioning

Compares versions as sequences of numbers:

zig
const comparator = updater.version.numeric;

Supports:

  • Any number of numeric parts separated by dots
  • Optional v prefix

Examples:

  • 1.2 < 1.10 (10 > 2, not lexical)
  • 1.2.3.4 < 1.2.3.5

Lexical Comparison

Simple string comparison (alphabetical order):

zig
const comparator = updater.version.lexical;

Examples:

  • alpha < beta < gamma
  • release-1 < release-2

Date-based Versioning

For calendar versioning (CalVer):

zig
const comparator = updater.version.date_based;

Supports formats:

  • YYYY.MM.DD
  • YYYYMMDD

Examples:

  • 2024.01.01 < 2024.06.15
  • 20240101 < 20240615

Using Comparators

With Update Checks

zig
const result = try updater.checkForUpdates(allocator, .{
    .provider = updater.providers.github,
    .owner = "username",
    .repo = "project",
    .current_version = "1.0.0",
    .comparator = updater.version.semantic,  // Optional, this is the default
});

Direct Comparison

zig
const relation = try updater.version.compareVersions(
    allocator,
    updater.version.semantic,
    "1.0.0",  // local version
    "2.0.0",  // remote version
);

switch (relation) {
    .local_newer => std.debug.print("You have a newer version\n", .{}),
    .remote_newer => std.debug.print("Update available\n", .{}),
    .equal => std.debug.print("Up to date\n", .{}),
    .unknown => std.debug.print("Cannot compare versions\n", .{}),
}

The VersionRelation Type

zig
pub const VersionRelation = enum {
    local_newer,   // Current version is newer than remote
    remote_newer,  // Remote version is newer (update available)
    equal,         // Versions are the same
    unknown,       // Cannot determine relationship

    pub fn hasUpdate(self: VersionRelation) bool {
        return self == .remote_newer;
    }
};

Creating Custom Comparators

Structure

zig
pub const VersionComparator = struct {
    parse: *const fn (allocator: std.mem.Allocator, raw: []const u8) ParseError![]const u8,
    compare: *const fn (local: []const u8, remote: []const u8) VersionRelation,

    pub const ParseError = error{
        InvalidFormat,
        OutOfMemory,
    };
};

Example: Build Number Comparator

zig
const buildNumberComparator = updater.VersionComparator{
    .parse = parseBuildNumber,
    .compare = compareBuildNumbers,
};

fn parseBuildNumber(allocator: std.mem.Allocator, raw: []const u8) ![]const u8 {
    // Format: "build-123" -> "123"
    if (!std.mem.startsWith(u8, raw, "build-")) {
        return error.InvalidFormat;
    }
    const number = raw[6..];
    // Validate it's a number
    _ = std.fmt.parseInt(u64, number, 10) catch return error.InvalidFormat;
    return allocator.dupe(u8, number);
}

fn compareBuildNumbers(local: []const u8, remote: []const u8) updater.VersionRelation {
    const local_num = std.fmt.parseInt(u64, local, 10) catch return .unknown;
    const remote_num = std.fmt.parseInt(u64, remote, 10) catch return .unknown;

    if (local_num > remote_num) return .local_newer;
    if (local_num < remote_num) return .remote_newer;
    return .equal;
}

Example: Git Commit Hash Comparator

For projects that version by commit hash, you might want to always show updates:

zig
const commitHashComparator = updater.VersionComparator{
    .parse = parseCommitHash,
    .compare = compareCommitHashes,
};

fn parseCommitHash(allocator: std.mem.Allocator, raw: []const u8) ![]const u8 {
    // Validate it looks like a commit hash
    if (raw.len < 7 or raw.len > 40) return error.InvalidFormat;
    for (raw) |c| {
        if (!std.ascii.isHex(c)) return error.InvalidFormat;
    }
    return allocator.dupe(u8, raw);
}

fn compareCommitHashes(local: []const u8, remote: []const u8) updater.VersionRelation {
    // If hashes are different, assume remote is newer
    if (std.mem.eql(u8, local, remote)) return .equal;
    return .remote_newer;
}

Parsing Semantic Versions Directly

For more control, use SemanticVersion directly:

zig
const v = try updater.version.SemanticVersion.parse("1.2.3-alpha+build.456");

std.debug.print("Major: {d}\n", .{v.major});      // 1
std.debug.print("Minor: {d}\n", .{v.minor});      // 2
std.debug.print("Patch: {d}\n", .{v.patch});      // 3
std.debug.print("Pre: {s}\n", .{v.prerelease.?}); // alpha
std.debug.print("Build: {s}\n", .{v.build_metadata.?}); // build.456

// Compare two versions
const v2 = try updater.version.SemanticVersion.parse("2.0.0");
const relation = v.compare(v2);  // .remote_newer

Best Practices

  1. Choose the right comparator: Use semantic for SemVer, numeric for simple versions
  2. Handle unknown results: Always have a fallback for .unknown
  3. Test edge cases: Verify your comparator handles all your version formats
  4. Document your versioning: Make it clear what format you use

Next Steps

Released under the MIT License.