Handle versions of an unknown scheme
When versions arrive from purls or an SBOM, you may not know the scheme at compile time — one row is PyPI, the next is npm, the next Debian. The sum types AnyVersion and AnyRange (in sparkles.versions.any) hold a version of any shipped scheme, and compareAny compares them safely.
Hold a version of any scheme
parsePurlVersion and parseVersAny already return these sum types, so you get an AnyVersion without choosing a scheme yourself:
auto a = parsePurlVersion("pkg:pypi/django@3.13.0a1").value; // AnyVersionCompare safely with compareAny
There is no universal order across schemes — see No cross-scheme order. So compareAny is partial: it returns a Nullable!int that holds a three-way result when both operands are the same scheme, and is null when they differ. The null is the defined contract, not an error:
auto b = parsePurlVersion("pkg:pypi/django@4.0.0").value;
auto same = compareAny(a, b);
writeln("same-scheme compare null? ", same.isNull); // false
if (!same.isNull)
writeln(" 3.13.0a1 vs 4.0.0: ", same.get); // -1
auto c = parsePurlVersion("pkg:npm/leftpad@1.3.0").value;
writeln("cross-scheme compare null? ", compareAny(a, c).isNull); // trueThis is why you cannot accidentally mis-order an SBOM: a PyPI version and an npm version simply have no ordering, and the API makes you handle that null explicitly.
Recover the concrete type
When you need scheme-specific behaviour, match on the sum type to get the concrete struct back (this is std.sumtype.match):
import std.sumtype : match;
a.match!(v => writeln("a is a ", typeof(v).stringof)); // a is a PypiVersionA match with one handler per scheme lets you branch per ecosystem; a generic match!(v => …) handler runs the same generic code (order, satisfies, toString) against whichever type is inside.
Complete example
#!/usr/bin/env dub
/+ dub.sdl:
name "unknown_schemes"
dependency "sparkles:versions" version="*"
+/
import std.stdio : writeln;
import std.sumtype : match;
import sparkles.versions;
void main()
{
// Two versions of the same scheme, arriving as purls.
auto a = parsePurlVersion("pkg:pypi/django@3.13.0a1").value;
auto b = parsePurlVersion("pkg:pypi/django@4.0.0").value;
// compareAny is partial: same scheme → a real ordering...
auto same = compareAny(a, b);
writeln("same-scheme compare null? ", same.isNull);
if (!same.isNull)
writeln(" 3.13.0a1 vs 4.0.0: ", same.get);
// ...different schemes → null (no cross-scheme order exists).
auto c = parsePurlVersion("pkg:npm/leftpad@1.3.0").value;
writeln("cross-scheme compare null? ", compareAny(a, c).isNull);
// Recover the concrete type when you need scheme-specific logic.
a.match!(v => writeln("a is a ", typeof(v).stringof));
}same-scheme compare null? false
3.13.0a1 vs 4.0.0: -1
cross-scheme compare null? true
a is a PypiVersionNotes
AnyRangeis the analogous sum overRanges!Schemefor every scheme;parseVersAnyreturns it.- Why partial and not a total fallback order? A single universal comparator is silently wrong on exactly the schemes that differ most (Debian epochs, PEP 440 local versions, Maven qualifiers). The reasoning is in No cross-scheme order.