crate-seq-registry

Overview

crate-seq-registry is a blocking HTTP client for the crates.io v1 API. It handles metadata fetching, version existence checks, index polling, cargo publish subprocess execution, and exponential backoff retries.

CratesIoClientclient.rs

Construction

pub struct CratesIoClient {
    inner: reqwest::blocking::Client,
    base_url: String,
}

impl CratesIoClient {
    /// production client with crates.io ToS-compliant User-Agent.
    pub fn new(crate_version: &str) -> Result<Self, Error>;

    /// test client redirected to a mock server.
    pub fn with_base_url(crate_version: &str, base_url: &str) -> Result<Self, Error>;
}

The User-Agent is "crate-seq/{version} (https://github.com/floppypancake/crate-seq)", which is required by crates.io Terms of Service.

fetch_crate_metadata

pub fn fetch_crate_metadata(
    &self, crate_name: &str
) -> Result<Option<CrateMetadata>, Error>

Hits GET {base_url}/api/v1/crates/{crate_name}. Returns None on 404 (crate doesn't exist). Deserializes the response into CrateMetadata, silently skipping entries whose num field is not valid SemVer.

check_version_exists

pub fn check_version_exists(
    &self, crate_name: &str, version: &semver::Version
) -> Result<bool, Error>

Delegates to fetch_crate_metadata and checks if any version matches. Returns false if the crate doesn't exist at all.

wait_for_index

pub fn wait_for_index(
    &self, crate_name: &str, version: &semver::Version, backoff: &BackoffConfig
) -> Result<(), Error>

Polls check_version_exists with the same backoff timing as publish retries. Returns Ok(()) once the version appears. Returns Error::IndexTimeout after backoff.max_retries attempts. Used between workspace tiers to wait for index propagation before publishing dependent crates.

Publish — publish.rs

PublishOutcome

pub enum PublishOutcome {
    Success,
    AlreadyPublished,
    RateLimited,
    Failed(String),
}

run_cargo_publish

pub fn run_cargo_publish(
    dir: &Path, token: Option<&str>
) -> Result<PublishOutcome, Error>

Runs cargo publish --allow-dirty in dir, optionally passing --token. Captures stderr and classifies the outcome:

  • Exit success → Success
  • Stderr contains "already exists" or "already uploaded"AlreadyPublished
  • Stderr contains "429", "rate limit", or "too many requests" (case-insensitive) → RateLimited
  • Otherwise → Failed(stderr)

Backoff — backoff.rs

BackoffConfig

pub struct BackoffConfig {
    pub base_ms: u64,        // default: 1000
    pub cap_ms: u64,         // default: 60000
    pub max_retries: u32,    // default: 5
    pub jitter_max_ms: u64,  // default: 1000
}

Implements Default with the values shown above.

backoff_publish

pub fn backoff_publish(
    dir: &Path, token: Option<&str>, config: &BackoffConfig
) -> Result<PublishOutcome, Error>

Wraps run_cargo_publish in an exponential backoff retry loop:

  • Success or AlreadyPublished → returns Ok(Success) immediately
  • Failed → returns Ok(Failed(...)) immediately (no retry)
  • RateLimited → sleeps and retries up to max_retries times
  • If all retries exhausted → returns Ok(RateLimited)

Delay calculation

min(cap_ms, base_ms × 2^attempt) + jitter

Uses overflow-safe arithmetic. Jitter is deterministic — derived from a Knuth multiplicative hash of the attempt index, avoiding a random crate dependency.

Types — types.rs

pub struct CrateVersionInfo {
    pub version: semver::Version,
    pub yanked: bool,
}

pub struct CrateMetadata {
    pub name: String,
    pub versions: Vec<CrateVersionInfo>,
}

CrateVersionInfo derives serde::Deserialize for JSON parsing from the crates.io API response.

Error type — error.rs

#[derive(Debug, thiserror::Error)]
pub enum Error {
    Http(#[from] reqwest::Error),
    Json(#[from] serde_json::Error),
    Subprocess(std::io::Error),          // not #[from] — manual construction
    IndexTimeout { crate_name: String, version: semver::Version },
}

Note: Subprocess wraps std::io::Error but is not #[from] because std::io::Error is already claimed by the snapshot crate in the unified error type. It is constructed manually.

Source files

File Responsibility
client.rs HTTP client construction, metadata fetch, version check, index polling
publish.rs cargo publish subprocess execution and outcome classification
backoff.rs Exponential backoff retry loop with deterministic jitter
types.rs Public data types (CrateMetadata, CrateVersionInfo)
error.rs Crate error enum
lib.rs Module declarations and re-exports