use super::keys::{read_len_prefixed_ref, write_len_prefixed};
use crate::path::RelPath;
pub const PROBE_HASH_LEN: usize = 8;
pub type ProbeHash = [u8; PROBE_HASH_LEN];
pub const PROBE_MANIFEST_START: u32 = u32::MAX;
pub fn probe_hash(path: &str) -> ProbeHash {
let digest = blake3::hash(path.as_bytes());
let mut out = [0u8; PROBE_HASH_LEN];
out.copy_from_slice(&digest.as_bytes()[..PROBE_HASH_LEN]);
out
}
pub fn import_target(importer: &RelPath, local_start: u32) -> Vec<u8> {
let mut out = import_targets_prefix(importer);
out.extend_from_slice(&local_start.to_be_bytes());
out
}
pub fn import_targets_prefix(importer: &RelPath) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + importer.as_bytes().len() + 4);
let _ = write_len_prefixed(&mut out, importer.as_bytes());
out
}
pub fn import_target_value(def_path: &RelPath, def_start: u32) -> Vec<u8> {
let mut out = Vec::with_capacity(2 + def_path.as_bytes().len() + 4);
let _ = write_len_prefixed(&mut out, def_path.as_bytes());
out.extend_from_slice(&def_start.to_be_bytes());
out
}
pub fn parse_import_target(key: &[u8], value: &[u8]) -> Option<(RelPath, u32, RelPath, u32)> {
let mut c = 0;
let importer = read_len_prefixed_ref(key, &mut c)?;
let local_start = u32::from_be_bytes(key.get(c..c + 4)?.try_into().ok()?);
if local_start == PROBE_MANIFEST_START {
return None;
}
let mut v = 0;
let def_path = read_len_prefixed_ref(value, &mut v)?;
let def_start = u32::from_be_bytes(value.get(v..v + 4)?.try_into().ok()?);
Some((RelPath::from(importer), local_start, RelPath::from(def_path), def_start))
}
pub fn probe_manifest_value(hashes: &[ProbeHash]) -> Vec<u8> {
hashes.concat()
}
pub fn parse_probe_manifest(value: &[u8]) -> impl Iterator<Item = ProbeHash> + '_ {
value.as_chunks::<PROBE_HASH_LEN>().0.iter().copied()
}
pub fn importer_by_probe(hash: &ProbeHash, importer: &RelPath) -> Vec<u8> {
let mut out = Vec::with_capacity(PROBE_HASH_LEN + importer.as_bytes().len());
out.extend_from_slice(hash);
out.extend_from_slice(importer.as_bytes());
out
}
pub fn importers_by_probe_prefix(hash: &ProbeHash) -> Vec<u8> {
hash.to_vec()
}
pub fn parse_importer_by_probe(key: &[u8]) -> Option<RelPath> {
key.get(PROBE_HASH_LEN..)
.filter(|rest| !rest.is_empty())
.map(RelPath::from)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn import_target_roundtrips() {
let importer = RelPath::from("src/app.ts");
let def = RelPath::from("src/impl.ts");
let key = import_target(&importer, 12);
let value = import_target_value(&def, 340);
assert_eq!(parse_import_target(&key, &value), Some((importer, 12, def, 340)));
}
#[test]
fn manifest_row_is_not_a_binding_and_shares_the_importer_prefix() {
let importer = RelPath::from("src/app.ts");
let manifest_key = import_target(&importer, PROBE_MANIFEST_START);
assert!(manifest_key.starts_with(&import_targets_prefix(&importer)));
assert_eq!(
parse_import_target(&manifest_key, &import_target_value(&importer, 0)),
None
);
}
#[test]
fn import_targets_prefix_isolates_importers() {
let a = RelPath::from("src/a.ts");
let ab = RelPath::from("src/a.ts.bak");
assert!(!import_target(&ab, 1).starts_with(&import_targets_prefix(&a)));
}
#[test]
fn manifest_roundtrips() {
let hashes = [probe_hash("a/b.ts"), probe_hash("tsconfig.json")];
let value = probe_manifest_value(&hashes);
assert_eq!(parse_probe_manifest(&value).collect::<Vec<_>>(), hashes);
}
#[test]
fn importers_by_probe_roundtrips_and_isolates_probes() {
let importer = RelPath::from("src/app.ts");
let hash = probe_hash("src/impl.ts");
let key = importer_by_probe(&hash, &importer);
assert_eq!(parse_importer_by_probe(&key), Some(importer.clone()));
assert!(key.starts_with(&importers_by_probe_prefix(&hash)));
assert!(!key.starts_with(&importers_by_probe_prefix(&probe_hash("src/other.ts"))));
}
#[test]
fn probe_hash_is_stable_and_path_sensitive() {
assert_eq!(probe_hash("a.ts"), probe_hash("a.ts"));
assert_ne!(probe_hash("a.ts"), probe_hash("b.ts"));
}
}