1use crate::format::{folded_cmp, folded_cmp_key, folded_starts_with, normalize};
4use std::fmt;
5use std::ops::Range;
6
7#[derive(Debug)]
9pub enum Error {
10 Zstd(std::io::Error),
12 Utf8(std::string::FromUtf8Error),
14 TooLarge,
16 Unsorted {
18 line: usize,
20 },
21 Malformed {
23 line: usize,
25 },
26}
27
28impl fmt::Display for Error {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Error::Zstd(e) => write!(f, "zstd decompression failed: {e}"),
32 Error::Utf8(e) => write!(f, "artifact was not valid utf8: {e}"),
33 Error::TooLarge => write!(f, "artifact exceeds supported size"),
34 Error::Unsorted { line } => write!(f, "artifact not sorted by name at line {line}"),
35 Error::Malformed { line } => write!(f, "malformed artifact line {line}"),
36 }
37 }
38}
39
40impl std::error::Error for Error {}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct Entry<'a> {
45 pub name: &'a str,
47 pub version: &'a str,
50 pub rank: u8,
52}
53
54struct SortedTsv {
58 text: String,
59 offsets: Vec<u32>,
61}
62
63impl SortedTsv {
64 fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
65 let decompressed = zstd::decode_all(bytes).map_err(Error::Zstd)?;
66 Self::from_text(String::from_utf8(decompressed).map_err(Error::Utf8)?)
67 }
68
69 fn from_text(text: String) -> Result<Self, Error> {
70 if text.len() > u32::MAX as usize {
71 return Err(Error::TooLarge);
72 }
73 let mut offsets = vec![0];
74 for (index, byte) in text.bytes().enumerate() {
75 if byte == b'\n' {
76 offsets.push(index as u32 + 1);
77 }
78 }
79 if *offsets.last().unwrap() as usize != text.len() {
80 offsets.push(text.len() as u32);
81 } else {
82 }
84 let tsv = Self { text, offsets };
85 for line in 1..tsv.len() {
86 if folded_cmp(tsv.name(line - 1), tsv.name(line)).is_ge() {
87 return Err(Error::Unsorted { line });
88 }
89 }
90 Ok(tsv)
91 }
92
93 fn len(&self) -> usize {
94 self.offsets.len() - 1
95 }
96
97 fn line(&self, index: usize) -> &str {
98 let start = self.offsets[index] as usize;
99 let end = self.offsets[index + 1] as usize;
100 self.text[start..end].trim_end_matches('\n')
101 }
102
103 fn name(&self, index: usize) -> &str {
104 let line = self.line(index);
105 line.split('\t').next().unwrap_or(line)
106 }
107
108 fn partition_point(&self, pred: impl Fn(&str) -> bool) -> usize {
111 let (mut low, mut high) = (0, self.len());
112 while low < high {
113 let mid = low + (high - low) / 2;
114 if pred(self.name(mid)) {
115 low = mid + 1;
116 } else {
117 high = mid;
118 }
119 }
120 low
121 }
122
123 fn prefix_range(&self, prefix: &str) -> Range<usize> {
127 let key = normalize(prefix);
128 let start = self.partition_point(|name| folded_cmp_key(name, &key).is_lt());
129 let end = self.partition_point(|name| {
130 folded_cmp_key(name, &key).is_lt() || folded_starts_with(name, &key)
131 });
132 start..end
133 }
134
135 fn find(&self, name: &str) -> Option<usize> {
136 let key = normalize(name);
137 let index = self.partition_point(|candidate| folded_cmp_key(candidate, &key).is_lt());
138 (index < self.len() && folded_cmp_key(self.name(index), &key).is_eq()).then_some(index)
139 }
140}
141
142pub struct CrateNames(SortedTsv);
145
146impl fmt::Debug for CrateNames {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.debug_struct("CrateNames")
149 .field("len", &self.len())
150 .finish_non_exhaustive()
151 }
152}
153
154impl CrateNames {
155 pub fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
157 Self::validate(SortedTsv::from_zstd(bytes)?)
158 }
159
160 pub fn from_tsv(text: String) -> Result<Self, Error> {
162 Self::validate(SortedTsv::from_text(text)?)
163 }
164
165 fn validate(tsv: SortedTsv) -> Result<Self, Error> {
166 let names = Self(tsv);
167 for line in 0..names.len() {
168 names.entry(line).ok_or(Error::Malformed { line })?;
169 }
170 Ok(names)
171 }
172
173 fn entry(&self, index: usize) -> Option<Entry<'_>> {
174 let mut fields = self.0.line(index).split('\t');
175 let name = fields.next()?;
176 let version = fields.next()?;
177 let rank = fields.next()?.parse().ok()?;
178 fields.next().is_none().then_some(Entry {
179 name,
180 version,
181 rank,
182 })
183 }
184
185 pub fn len(&self) -> usize {
187 self.0.len()
188 }
189
190 pub fn is_empty(&self) -> bool {
192 self.len() == 0
193 }
194
195 pub fn get(&self, name: &str) -> Option<Entry<'_>> {
199 self.entry(self.0.find(name)?)
200 }
201
202 pub fn entry_at(&self, index: usize) -> Option<Entry<'_>> {
207 (index < self.len()).then(|| self.entry(index)).flatten()
208 }
209
210 pub fn prefix_indices(&self, prefix: &str) -> Range<usize> {
215 self.0.prefix_range(prefix)
216 }
217
218 pub fn count(&self, prefix: &str) -> usize {
222 self.0.prefix_range(prefix).len()
223 }
224
225 pub fn prefix(&self, prefix: &str) -> impl Iterator<Item = Entry<'_>> {
228 self.0
229 .prefix_range(prefix)
230 .map(|index| self.entry(index).expect("validated at construction"))
231 }
232
233 pub fn typeahead(&self, prefix: &str, limit: usize) -> Vec<Entry<'_>> {
236 let mut matches: Vec<Entry<'_>> = self.prefix(prefix).collect();
237 matches.sort_unstable_by(|a, b| b.rank.cmp(&a.rank).then(a.name.cmp(b.name)));
238 matches.truncate(limit);
239 matches
240 }
241}
242
243pub struct Descriptions(SortedTsv);
245
246impl fmt::Debug for Descriptions {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.debug_struct("Descriptions")
249 .field("len", &self.len())
250 .finish_non_exhaustive()
251 }
252}
253
254impl Descriptions {
255 pub fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
257 Ok(Self(SortedTsv::from_zstd(bytes)?))
258 }
259
260 pub fn from_tsv(text: String) -> Result<Self, Error> {
262 Ok(Self(SortedTsv::from_text(text)?))
263 }
264
265 pub fn len(&self) -> usize {
267 self.0.len()
268 }
269
270 pub fn is_empty(&self) -> bool {
272 self.len() == 0
273 }
274
275 pub fn get(&self, name: &str) -> Option<&str> {
278 let index = self.0.find(name)?;
279 self.0.line(index).split_once('\t').map(|(_, desc)| desc)
280 }
281
282 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
284 (0..self.len()).filter_map(|index| self.0.line(index).split_once('\t'))
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub struct FacetsEntry<'a> {
291 pub name: &'a str,
293 keywords: &'a str,
294 categories: &'a str,
295}
296
297impl<'a> FacetsEntry<'a> {
298 pub fn keywords(&self) -> impl Iterator<Item = &'a str> {
301 self.keywords.split_ascii_whitespace()
302 }
303
304 pub fn categories(&self) -> impl Iterator<Item = &'a str> {
308 self.categories.split_ascii_whitespace()
309 }
310}
311
312pub struct Facets(SortedTsv);
315
316impl fmt::Debug for Facets {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 f.debug_struct("Facets")
319 .field("len", &self.len())
320 .finish_non_exhaustive()
321 }
322}
323
324impl Facets {
325 pub fn from_zstd(bytes: &[u8]) -> Result<Self, Error> {
327 Self::validate(SortedTsv::from_zstd(bytes)?)
328 }
329
330 pub fn from_tsv(text: String) -> Result<Self, Error> {
332 Self::validate(SortedTsv::from_text(text)?)
333 }
334
335 fn validate(tsv: SortedTsv) -> Result<Self, Error> {
336 let facets = Self(tsv);
337 for line in 0..facets.len() {
338 facets.entry(line).ok_or(Error::Malformed { line })?;
339 }
340 Ok(facets)
341 }
342
343 fn entry(&self, index: usize) -> Option<FacetsEntry<'_>> {
344 let mut fields = self.0.line(index).split('\t');
345 let name = fields.next()?;
346 let keywords = fields.next()?;
347 let categories = fields.next()?;
348 fields.next().is_none().then_some(FacetsEntry {
349 name,
350 keywords,
351 categories,
352 })
353 }
354
355 pub fn len(&self) -> usize {
358 self.0.len()
359 }
360
361 pub fn is_empty(&self) -> bool {
363 self.len() == 0
364 }
365
366 pub fn get(&self, name: &str) -> Option<FacetsEntry<'_>> {
369 self.entry(self.0.find(name)?)
370 }
371
372 pub fn iter(&self) -> impl Iterator<Item = FacetsEntry<'_>> {
374 (0..self.len()).map(|index| self.entry(index).expect("validated at construction"))
375 }
376}