use crate::format::{folded_cmp, folded_cmp_key, folded_starts_with, normalize};
use std::fmt;
use std::ops::Range;
#[derive(Debug)]
pub enum Error {
Zstd(std::io::Error),
Utf8(std::string::FromUtf8Error),
TooLarge,
Unsorted {
line: usize,
},
Malformed {
line: usize,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Zstd(e) => write!(f, "zstd decompression failed: {e}"),
Error::Utf8(e) => write!(f, "artifact was not valid utf8: {e}"),
Error::TooLarge => write!(f, "artifact exceeds supported size"),
Error::Unsorted { line } => write!(f, "artifact not sorted by name at line {line}"),
Error::Malformed { line } => write!(f, "malformed artifact line {line}"),
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Entry<'a> {
pub name: &'a str,
pub version: &'a str,
pub rank: u8,
}
struct SortedTsv {
text: String,
offsets: Vec<u32>,
}
impl SortedTsv {
fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
let decompressed = zstd::decode_all(bytes).map_err(Error::Zstd)?;
Self::from_text(String::from_utf8(decompressed).map_err(Error::Utf8)?)
}
fn from_text(text: String) -> Result<Self, Error> {
if text.len() > u32::MAX as usize {
return Err(Error::TooLarge);
}
let mut offsets = vec![0];
for (index, byte) in text.bytes().enumerate() {
if byte == b'\n' {
offsets.push(index as u32 + 1);
}
}
if *offsets.last().unwrap() as usize != text.len() {
offsets.push(text.len() as u32);
} else {
}
let tsv = Self { text, offsets };
for line in 1..tsv.len() {
if folded_cmp(tsv.name(line - 1), tsv.name(line)).is_ge() {
return Err(Error::Unsorted { line });
}
}
Ok(tsv)
}
fn len(&self) -> usize {
self.offsets.len() - 1
}
fn line(&self, index: usize) -> &str {
let start = self.offsets[index] as usize;
let end = self.offsets[index + 1] as usize;
self.text[start..end].trim_end_matches('\n')
}
fn name(&self, index: usize) -> &str {
let line = self.line(index);
line.split('\t').next().unwrap_or(line)
}
fn partition_point(&self, pred: impl Fn(&str) -> bool) -> usize {
let (mut low, mut high) = (0, self.len());
while low < high {
let mid = low + (high - low) / 2;
if pred(self.name(mid)) {
low = mid + 1;
} else {
high = mid;
}
}
low
}
fn prefix_range(&self, prefix: &str) -> Range<usize> {
let key = normalize(prefix);
let start = self.partition_point(|name| folded_cmp_key(name, &key).is_lt());
let end = self.partition_point(|name| {
folded_cmp_key(name, &key).is_lt() || folded_starts_with(name, &key)
});
start..end
}
fn find(&self, name: &str) -> Option<usize> {
let key = normalize(name);
let index = self.partition_point(|candidate| folded_cmp_key(candidate, &key).is_lt());
(index < self.len() && folded_cmp_key(self.name(index), &key).is_eq()).then_some(index)
}
}
pub struct CrateNames(SortedTsv);
impl fmt::Debug for CrateNames {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CrateNames")
.field("len", &self.len())
.finish_non_exhaustive()
}
}
impl CrateNames {
pub fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
Self::validate(SortedTsv::from_zstd(bytes)?)
}
pub fn from_tsv(text: String) -> Result<Self, Error> {
Self::validate(SortedTsv::from_text(text)?)
}
fn validate(tsv: SortedTsv) -> Result<Self, Error> {
let names = Self(tsv);
for line in 0..names.len() {
names.entry(line).ok_or(Error::Malformed { line })?;
}
Ok(names)
}
fn entry(&self, index: usize) -> Option<Entry<'_>> {
let mut fields = self.0.line(index).split('\t');
let name = fields.next()?;
let version = fields.next()?;
let rank = fields.next()?.parse().ok()?;
fields.next().is_none().then_some(Entry {
name,
version,
rank,
})
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, name: &str) -> Option<Entry<'_>> {
self.entry(self.0.find(name)?)
}
pub fn entry_at(&self, index: usize) -> Option<Entry<'_>> {
(index < self.len()).then(|| self.entry(index)).flatten()
}
pub fn prefix_indices(&self, prefix: &str) -> Range<usize> {
self.0.prefix_range(prefix)
}
pub fn count(&self, prefix: &str) -> usize {
self.0.prefix_range(prefix).len()
}
pub fn prefix(&self, prefix: &str) -> impl Iterator<Item = Entry<'_>> {
self.0
.prefix_range(prefix)
.map(|index| self.entry(index).expect("validated at construction"))
}
pub fn typeahead(&self, prefix: &str, limit: usize) -> Vec<Entry<'_>> {
let mut matches: Vec<Entry<'_>> = self.prefix(prefix).collect();
matches.sort_unstable_by(|a, b| b.rank.cmp(&a.rank).then(a.name.cmp(b.name)));
matches.truncate(limit);
matches
}
}
pub struct Descriptions(SortedTsv);
impl fmt::Debug for Descriptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Descriptions")
.field("len", &self.len())
.finish_non_exhaustive()
}
}
impl Descriptions {
pub fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
Ok(Self(SortedTsv::from_zstd(bytes)?))
}
pub fn from_tsv(text: String) -> Result<Self, Error> {
Ok(Self(SortedTsv::from_text(text)?))
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, name: &str) -> Option<&str> {
let index = self.0.find(name)?;
self.0.line(index).split_once('\t').map(|(_, desc)| desc)
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
(0..self.len()).filter_map(|index| self.0.line(index).split_once('\t'))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FacetsEntry<'a> {
pub name: &'a str,
keywords: &'a str,
categories: &'a str,
}
impl<'a> FacetsEntry<'a> {
pub fn keywords(&self) -> impl Iterator<Item = &'a str> {
self.keywords.split_ascii_whitespace()
}
pub fn categories(&self) -> impl Iterator<Item = &'a str> {
self.categories.split_ascii_whitespace()
}
}
pub struct Facets(SortedTsv);
impl fmt::Debug for Facets {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Facets")
.field("len", &self.len())
.finish_non_exhaustive()
}
}
impl Facets {
pub fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
Self::validate(SortedTsv::from_zstd(bytes)?)
}
pub fn from_tsv(text: String) -> Result<Self, Error> {
Self::validate(SortedTsv::from_text(text)?)
}
fn validate(tsv: SortedTsv) -> Result<Self, Error> {
let facets = Self(tsv);
for line in 0..facets.len() {
facets.entry(line).ok_or(Error::Malformed { line })?;
}
Ok(facets)
}
fn entry(&self, index: usize) -> Option<FacetsEntry<'_>> {
let mut fields = self.0.line(index).split('\t');
let name = fields.next()?;
let keywords = fields.next()?;
let categories = fields.next()?;
fields.next().is_none().then_some(FacetsEntry {
name,
keywords,
categories,
})
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, name: &str) -> Option<FacetsEntry<'_>> {
self.entry(self.0.find(name)?)
}
pub fn iter(&self) -> impl Iterator<Item = FacetsEntry<'_>> {
(0..self.len()).map(|index| self.entry(index).expect("validated at construction"))
}
}