use crate::{NormalizedPath, VfsKeyInput, paths::key_to_path_buf_lossy};
use std::{
fmt,
path::{Path, PathBuf},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct SourceId(usize);
impl SourceId {
#[must_use]
pub const fn from_index(index: usize) -> Self {
Self(index)
}
#[must_use]
pub const fn as_index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NormalizedKey(NormalizedPath);
impl NormalizedKey {
#[must_use]
pub fn new(path: impl AsRef<Path>) -> Self {
Self(path.as_ref().to_vfs_key())
}
#[must_use]
pub fn as_path(&self) -> PathBuf {
key_to_path_buf_lossy(&self.0)
}
#[must_use]
pub fn into_path_buf(self) -> PathBuf {
key_to_path_buf_lossy(&self.0)
}
#[must_use]
pub fn as_normalized_path(&self) -> &NormalizedPath {
&self.0
}
}
impl From<NormalizedPath> for NormalizedKey {
fn from(value: NormalizedPath) -> Self {
Self(value)
}
}
impl From<PathBuf> for NormalizedKey {
fn from(value: PathBuf) -> Self {
Self::new(value)
}
}
impl From<&Path> for NormalizedKey {
fn from(value: &Path) -> Self {
Self::new(value)
}
}
impl fmt::Display for NormalizedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", String::from_utf8_lossy(self.0.as_bytes()))
}
}
#[cfg(feature = "serialize")]
impl serde::Serialize for NormalizedKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&String::from_utf8_lossy(self.0.as_bytes()))
}
}
#[cfg(feature = "serialize")]
impl<'de> serde::Deserialize<'de> for NormalizedKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Ok(Self(NormalizedPath::from(value)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct ContentDigest {
pub algorithm: &'static str,
pub hex: String,
pub size: u64,
}
impl ContentDigest {
#[must_use]
pub fn blake3(bytes: [u8; 32], size: u64) -> Self {
use std::fmt::Write;
let mut hex = String::with_capacity(bytes.len() * 2);
for byte in bytes {
let _ = write!(&mut hex, "{byte:02x}");
}
Self {
algorithm: "blake3",
hex,
size,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalized_key_normalizes_case_and_separators() {
let key = NormalizedKey::new(PathBuf::from("Textures\\FOO.DDS"));
assert_eq!(key.as_path(), Path::new("textures/foo.dds"));
}
#[test]
fn source_id_round_trip_index() {
let id = SourceId::from_index(7);
assert_eq!(id.as_index(), 7);
}
#[test]
fn content_digest_blake3_has_expected_shape() {
let digest = ContentDigest::blake3([0xAB; 32], 123);
assert_eq!(digest.algorithm, "blake3");
assert_eq!(digest.hex.len(), 64);
assert_eq!(digest.size, 123);
}
}