use crate::error::{Error, Result};
pub const CHECKPOINT_PATH: &str = "checkpoint";
pub const TILE_HEIGHT: u64 = 8;
pub const TILE_WIDTH: u64 = 1 << TILE_HEIGHT;
pub const ENTRY_BUNDLE_WIDTH: u64 = TILE_WIDTH;
pub fn format_n(mut n: u64) -> String {
let mut result = format!("{:03}", n % 1000);
n /= 1000;
while n > 0 {
result = format!("x{:03}/{}", n % 1000, result);
n /= 1000;
}
result
}
pub fn format_n_with_suffix(n: u64, partial: u8) -> String {
let base = format_n(n);
if partial > 0 {
format!("{}.p/{}", base, partial)
} else {
base
}
}
pub fn tile_path(level: u64, index: u64, partial: u8) -> String {
format!("tile/{}/{}", level, format_n_with_suffix(index, partial))
}
pub fn entries_path(index: u64, partial: u8) -> String {
format!("tile/entries/{}", format_n_with_suffix(index, partial))
}
pub fn entries_path_for_log_index(seq: u64, log_size: u64) -> String {
let bundle_index = seq / ENTRY_BUNDLE_WIDTH;
let partial = partial_tile_size(0, bundle_index, log_size);
entries_path(bundle_index, partial)
}
pub fn partial_tile_size(level: u64, index: u64, log_size: u64) -> u8 {
let size_at_level = log_size >> (level * TILE_HEIGHT);
let full_tiles = size_at_level / TILE_WIDTH;
if index < full_tiles {
0
} else {
(size_at_level % TILE_WIDTH) as u8
}
}
pub fn parse_tile_level(level: &str) -> Result<u64> {
let l: u64 = level
.parse()
.map_err(|_| Error::InvalidPath("invalid tile level".into()))?;
if l > 63 {
return Err(Error::InvalidPath("tile level must be 0-63".into()));
}
Ok(l)
}
pub fn parse_tile_index(index_str: &str) -> Result<(u64, u8)> {
let (index_part, partial) = if let Some(pos) = index_str.find(".p/") {
let partial_str = &index_str[pos + 3..];
let partial: u64 = partial_str
.parse()
.map_err(|_| Error::InvalidPath("invalid partial size".into()))?;
if !(1..TILE_WIDTH).contains(&partial) {
return Err(Error::InvalidPath("partial size must be 1-255".into()));
}
(&index_str[..pos], partial as u8)
} else {
(index_str, 0u8)
};
let parts: Vec<&str> = index_part.split('/').collect();
for (i, part) in parts.iter().enumerate() {
if i < parts.len() - 1 {
if !part.starts_with('x') {
return Err(Error::InvalidPath("invalid index format".into()));
}
} else if part.starts_with('x') {
return Err(Error::InvalidPath(
"last index component should not have x prefix".into(),
));
}
}
let mut index: u64 = 0;
for part in parts {
let digits = part.strip_prefix('x').unwrap_or(part);
if digits.len() != 3 {
return Err(Error::InvalidPath(
"each index component must be 3 digits".into(),
));
}
let n: u64 = digits
.parse()
.map_err(|_| Error::InvalidPath("invalid index digits".into()))?;
if n >= 1000 {
return Err(Error::InvalidPath("index component must be < 1000".into()));
}
if index > (u64::MAX - n) / 1000 {
return Err(Error::InvalidPath("index overflow".into()));
}
index = index * 1000 + n;
}
Ok((index, partial))
}
pub fn parse_tile_path(level: &str, index: &str) -> Result<(u64, u64, u8)> {
let l = parse_tile_level(level)?;
let (i, p) = parse_tile_index(index)?;
Ok((l, i, p))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_n() {
assert_eq!(format_n(0), "000");
assert_eq!(format_n(1), "001");
assert_eq!(format_n(123), "123");
assert_eq!(format_n(999), "999");
assert_eq!(format_n(1000), "x001/000");
assert_eq!(format_n(1234), "x001/234");
assert_eq!(format_n(1234067), "x001/x234/067");
assert_eq!(format_n(999999999), "x999/x999/999");
}
#[test]
fn test_format_n_with_suffix() {
assert_eq!(format_n_with_suffix(123, 0), "123");
assert_eq!(format_n_with_suffix(123, 8), "123.p/8");
assert_eq!(format_n_with_suffix(1234067, 42), "x001/x234/067.p/42");
}
#[test]
fn test_tile_path() {
assert_eq!(tile_path(0, 0, 0), "tile/0/000");
assert_eq!(tile_path(0, 123, 0), "tile/0/123");
assert_eq!(tile_path(1, 1234067, 0), "tile/1/x001/x234/067");
assert_eq!(tile_path(0, 1234067, 8), "tile/0/x001/x234/067.p/8");
}
#[test]
fn test_tile_path_tessera_vectors() {
let test_cases = [
(0, 0, 0, "tile/0/000"),
(0, 0, 255, "tile/0/000.p/255"),
(1, 0, 0, "tile/1/000"),
(15, 455667, 0, "tile/15/x455/667"),
(15, 123456789, 41, "tile/15/x123/x456/789.p/41"),
];
for (level, index, partial, want_path) in test_cases {
let got_path = tile_path(level, index, partial);
assert_eq!(
got_path, want_path,
"tile_path({}, {}, {}) mismatch",
level, index, partial
);
}
}
#[test]
fn test_entries_path() {
assert_eq!(entries_path(0, 0), "tile/entries/000");
assert_eq!(entries_path(123, 0), "tile/entries/123");
assert_eq!(entries_path(123, 8), "tile/entries/123.p/8");
}
#[test]
fn test_entries_path_tessera_vectors() {
let test_cases = [
(0, 0, "tile/entries/000"),
(0, 8, "tile/entries/000.p/8"),
(255, 0, "tile/entries/255"),
(255, 253, "tile/entries/255.p/253"),
];
for (n, partial, want_path) in test_cases {
let got_path = entries_path(n, partial);
assert_eq!(
got_path, want_path,
"entries_path({}, {}) mismatch",
n, partial
);
}
}
#[test]
fn test_entries_path_for_log_index_tessera_vectors() {
let test_cases = [
(0, 256, "tile/entries/000"),
(255, 256, "tile/entries/000"),
(251, 255, "tile/entries/000.p/255"),
(256, 512, "tile/entries/001"),
(3, 257, "tile/entries/000"),
(256, 257, "tile/entries/001.p/1"),
(
123456789 * 256,
123456790 * 256,
"tile/entries/x123/x456/789",
),
];
for (seq, log_size, want_path) in test_cases {
let got_path = entries_path_for_log_index(seq, log_size);
assert_eq!(
got_path, want_path,
"entries_path_for_log_index({}, {}) mismatch",
seq, log_size
);
}
}
#[test]
fn test_partial_tile_size() {
assert_eq!(partial_tile_size(0, 0, 256), 0);
assert_eq!(partial_tile_size(0, 0, 512), 0);
assert_eq!(partial_tile_size(0, 1, 512), 0);
assert_eq!(partial_tile_size(0, 0, 100), 100);
assert_eq!(partial_tile_size(0, 1, 300), 44); assert_eq!(partial_tile_size(0, 0, 1), 1);
}
#[test]
fn test_parse_tile_index() {
assert_eq!(parse_tile_index("000").unwrap(), (0, 0));
assert_eq!(parse_tile_index("123").unwrap(), (123, 0));
assert_eq!(parse_tile_index("x001/234").unwrap(), (1234, 0));
assert_eq!(parse_tile_index("x001/x234/067").unwrap(), (1234067, 0));
assert_eq!(parse_tile_index("123.p/8").unwrap(), (123, 8));
assert_eq!(
parse_tile_index("x001/x234/067.p/42").unwrap(),
(1234067, 42)
);
}
#[test]
fn test_parse_tile_level() {
assert_eq!(parse_tile_level("0").unwrap(), 0);
assert_eq!(parse_tile_level("63").unwrap(), 63);
assert!(parse_tile_level("64").is_err());
assert!(parse_tile_level("abc").is_err());
}
#[test]
fn test_roundtrip() {
for n in [0, 1, 123, 999, 1000, 1234, 1234067, 999999999] {
for p in [0, 1, 8, 42, 255] {
let path = format_n_with_suffix(n, p);
let (parsed_n, parsed_p) = parse_tile_index(&path).unwrap();
assert_eq!(n, parsed_n, "index mismatch for {}", path);
assert_eq!(p, parsed_p, "partial mismatch for {}", path);
}
}
}
}