Skip to main content

crate_names/
lib.rs

1//! Compact, transparent artifacts of every crate name on crates.io, for
2//! typeahead and default-version lookup.
3//!
4//! This crate has two halves:
5//!
6//! - **Reading** (always available): [`CrateNames`], [`Descriptions`], and
7//!   [`Facets`] parse the published artifacts. The reader is sans-io: hand
8//!   it bytes you fetched however you like.
9//! - **Building** (behind the `build` feature): [`build_from_dump`] streams
10//!   a crates.io database dump tarball and produces the artifacts. Used by
11//!   the scheduled GitHub Action in this repository; consumers normally
12//!   never need it.
13//!
14//! # Wire format (v2)
15//!
16//! Artifacts are zstd-compressed TSV, one crate per line, sorted by the
17//! crate's *folded* name: ASCII-lowercased, with `-` and `_` treated as the
18//! same character (see [`normalize`]). Names are stored as spelled; only the
19//! ordering is folded. Crate names cannot contain tabs or newlines, and
20//! description whitespace is flattened, so no escaping is required.
21//!
22//! Folding is what makes lookups work the way people type. crates.io will
23//! not let a new crate take a name that folds onto an existing one, so the
24//! folded key is unique across the registry, and the artifacts stay sorted
25//! and unique under it — queries remain two binary searches, and `Tokio`,
26//! `tokio` and `tokio_util` all find what you meant.
27//!
28//! - `names-v2.tsv.zst`: `name \t default_version \t rank` for every crate.
29//!   `rank` is a log-quantized download count in `0..=255`; see
30//!   [`rank_from_downloads`]. Ordering by rank is meaningful, arithmetic
31//!   on it is not.
32//! - `descriptions-v2.tsv.zst`: `name \t description` for every crate with
33//!   a non-empty description, whitespace runs collapsed to single spaces.
34//! - `facets-v1.tsv.zst`: `name \t keywords \t categories` for every crate
35//!   with at least one Cargo.toml keyword or category. Each field is a
36//!   space-separated, sorted list (either may be empty); categories are
37//!   crates.io slugs, nested with `::`. Files are versioned independently,
38//!   which is why this one is `-v1` alongside the `-v2` pair.
39//!
40//! # Getting the artifacts
41//!
42//! All are republished daily (built from that morning's crates.io database
43//! dump) to a rolling GitHub release, at stable URLs also exposed as
44//! [`NAMES_URL_V2`], [`DESCRIPTIONS_URL_V2`], and [`FACETS_URL_V1`]:
45//!
46//! - <https://github.com/jbr/crate-names/releases/download/artifacts/names-v2.tsv.zst>
47//!   (~2 MB)
48//! - <https://github.com/jbr/crate-names/releases/download/artifacts/descriptions-v2.tsv.zst>
49//!   (~5.5 MB)
50//! - <https://github.com/jbr/crate-names/releases/download/artifacts/facets-v1.tsv.zst>
51//!
52//! The URLs redirect to the release asset, so follow redirects. Assets carry
53//! ETags: revalidate with `If-None-Match` rather than re-downloading — the
54//! content changes at most once a day.
55//!
56//! ```no_run
57//! # fn fetch(url: &str) -> Vec<u8> { unimplemented!() }
58//! let bytes = fetch(crate_names::NAMES_URL_V2);
59//! let names = crate_names::CrateNames::from_zstd(&bytes)?;
60//! let top_ten = names.typeahead("serd", 10);
61//! # Ok::<(), crate_names::Error>(())
62//! ```
63#![forbid(unsafe_code)]
64#![deny(
65    clippy::dbg_macro,
66    missing_copy_implementations,
67    rustdoc::missing_crate_level_docs,
68    missing_debug_implementations,
69    missing_docs,
70    nonstandard_style,
71    unused_qualifications
72)]
73
74// Compile the README as a doctest so its examples stay in sync with the crate.
75#[cfg(doctest)]
76#[doc = include_str!("../README.md")]
77mod readme {}
78
79mod format;
80mod read;
81
82pub use format::{
83    DESCRIPTIONS_FILE_V2, DESCRIPTIONS_URL_V2, FACETS_FILE_V1, FACETS_URL_V1, NAMES_FILE_V2,
84    NAMES_URL_V2, normalize, rank_from_downloads,
85};
86pub use read::{CrateNames, Descriptions, Entry, Error, Facets, FacetsEntry};
87
88#[cfg(feature = "build")]
89mod build;
90#[cfg(feature = "build")]
91pub use build::{BuildError, BuildOutput, build_from_dump};