Devolutions/UniGetUI · error · NullResponseException

Null response for package {packageId}

Error message

Null response for package {packageId}

What it means

CratesIOClient.GetManifest throws NullResponseException when the /crates/{id} JSON deserializes successfully but the 'crate' object is null. This indicates crates.io returned a well-formed but semantically empty payload (e.g. the crate was yanked/deleted, or an unexpected schema).

Source

Thrown at src/UniGetUI.PackageEngine.Managers.Cargo/CratesIOClient.cs:74

internal sealed class CargoManifestPublisher
{
    public string? avatar { get; init; }
    public required string name { get; init; }
    public string? url { get; init; }
}

internal sealed class CratesIOClient
{
    public const string ApiUrl = "https://crates.io/api/v1";
    internal static string? TEST_ApiUrlOverride { private get; set; }

    public static Tuple<Uri, CargoManifest> GetManifest(string packageId)
    {
        var manifestUrl = new Uri($"{GetApiUrl()}/crates/{packageId}");
        var manifest = Fetch<CargoManifest>(manifestUrl);
        if (manifest.crate is null)
        {
            throw new NullResponseException($"Null response for package {packageId}");
        }
        return Tuple.Create(manifestUrl, manifest);
    }

    public static CargoManifestVersion GetManifestVersion(string packageId, string version)
    {
        var manifestUrl = new Uri($"{GetApiUrl()}/crates/{packageId}/{version}");
        var manifest = Fetch<CargoManifestVersionWrapper>(manifestUrl);
        if (manifest.version is null)
        {
            throw new NullResponseException($"Null response for package {packageId}");
        }
        return manifest.version;
    }

    private static string GetApiUrl() => TEST_ApiUrlOverride ?? ApiUrl;

    internal static T Fetch<T>(Uri url)

View on GitHub (pinned to 9b1d7d0eab)

Solutions

  1. Verify the crate exists at https://crates.io/crates/{packageId} before fetching.
  2. Catch NullResponseException in the Cargo details helper and return a degraded details object.
  3. Retry once on transient empty responses; if it persists, surface 'details unavailable'.

Example fix

// before
var (url, manifest) = CratesIOClient.GetManifest(packageId);
// after
try { (url, manifest) = CratesIOClient.GetManifest(packageId); }
catch (NullResponseException) { return details with { Name = package.Id }; }
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate crate exists before fetching the manifest.
// (No cheap check; crates.io has no 'exists' endpoint — rely on try-catch.)

Try / catch

try
{
    var (url, manifest) = CratesIOClient.GetManifest(packageId);
}
catch (NullResponseException ex)
{
    Logger.Warn($"No crate manifest for {packageId}: {ex.Message}");
    return details with { Name = package.Id, Description = "Details unavailable" };
}

Prevention

When it happens

Trigger: Fetching Cargo package details for an id whose crates.io manifest has no 'crate' field, or a 200 response with an error-shaped body.

Common situations: Looking up a crate that was renamed/yanked; transient crates.io anomaly; packageId casing/typo that resolves to an empty manifest.

Related errors


AI-assisted analysis of Devolutions/UniGetUI@9b1d7d0eab (2026-08-13). Data as JSON: /api/errors/4d44f1cce2081898. Report an issue: GitHub.