idpath/
lib.rs

1//! Path encoding utilities / 路径编码工具
2//!
3//! Encodes IDs into hierarchical paths like `xx/xx/xx` (low bits first) to improve 
4//! load balancing in storage systems.
5//! 
6//! 将 ID 编码为 `xx/xx/xx` 格式的路径(低位在前),有利于在存储系统中实现负载均衡。
7
8mod error;
9
10pub use error::{Error, Result};
11use std::path::MAIN_SEPARATOR;
12
13use fast32::base32::CROCKFORD_LOWER;
14use hipstr::HipStr;
15
16/// Depth suffixes / 深度后缀
17pub const DEPTH1: u8 = b'_'; // len 0-2: 1 level
18pub const DEPTH2: u8 = b'-'; // len 3: 2 levels
19pub const DEPTH3: u8 = b'~'; // len 4: 2 levels
20
21const SEP: u8 = MAIN_SEPARATOR as u8;
22
23/// Encode id to path: prefix/xx/xx/xx (low bits first)
24/// 编码 ID 为路径(低位在前)
25#[inline]
26pub fn encode(prefix: impl AsRef<str>, id: u64) -> HipStr<'static> {
27  let prefix = prefix.as_ref();
28  let encoded = CROCKFORD_LOWER.encode_u64(id);
29  let enc = encoded.as_bytes();
30  let len = enc.len();
31
32  let cap = prefix.len() + (len + 3).max(6);
33  let mut buf = Vec::with_capacity(cap);
34
35  buf.extend_from_slice(prefix.as_bytes());
36  buf.push(SEP);
37
38  match len {
39    0..=2 => {
40      buf.extend_from_slice(enc);
41      buf.push(DEPTH1);
42    }
43    3 => {
44      buf.extend_from_slice(&enc[1..3]);
45      buf.push(SEP);
46      buf.push(enc[0]);
47      buf.push(DEPTH2);
48    }
49    4 => {
50      buf.extend_from_slice(&enc[2..4]);
51      buf.push(SEP);
52      buf.extend_from_slice(&enc[..2]);
53      buf.push(DEPTH3);
54    }
55    5 => {
56      buf.extend_from_slice(&enc[3..5]);
57      buf.push(SEP);
58      buf.extend_from_slice(&enc[1..3]);
59      buf.push(SEP);
60      buf.push(enc[0]);
61    }
62    _ => {
63      buf.extend_from_slice(&enc[len - 2..]);
64      buf.push(SEP);
65      buf.extend_from_slice(&enc[len - 4..len - 2]);
66      buf.push(SEP);
67      buf.extend_from_slice(&enc[..len - 4]);
68    }
69  }
70
71  // SAFETY: all bytes are valid UTF-8 (ASCII from Crockford base32 and separator)
72  unsafe { HipStr::from_utf8_unchecked(buf.into()) }
73}
74
75/// Decode path to id / 解码路径为 ID
76pub fn decode(path: impl AsRef<str>) -> Result<u64> {
77  let path_str = path.as_ref();
78  let mut iter = path_str.rsplit(MAIN_SEPARATOR);
79  let last_part = iter.next().ok_or_else(|| Error::InvalidPath(path_str.into()))?;
80
81  let mut buf = [0u8; 16];
82  let mut pos = 0;
83
84  let mut push = |src: &[u8]| -> Result<()> {
85    let len = src.len();
86    if pos + len > buf.len() {
87      return Err(Error::InvalidPath(path_str.into()));
88    }
89    buf[pos..pos + len].copy_from_slice(src);
90    pos += len;
91    Ok(())
92  };
93
94  if let Some(&suffix) = last_part.as_bytes().last() {
95    match suffix {
96      DEPTH1 => {
97        let name = &last_part[..last_part.len() - 1];
98        push(name.as_bytes())?;
99      }
100      DEPTH2 | DEPTH3 => {
101        let name = &last_part[..last_part.len() - 1];
102        let d2 =
103          iter.next().ok_or_else(|| Error::InvalidPath(path_str.into()))?;
104        if d2.len() != 2 {
105          return Err(Error::InvalidPath(path_str.into()));
106        }
107        push(name.as_bytes())?;
108        push(d2.as_bytes())?;
109      }
110      _ => {
111        let d2 =
112          iter.next().ok_or_else(|| Error::InvalidPath(path_str.into()))?;
113        let d1 =
114          iter.next().ok_or_else(|| Error::InvalidPath(path_str.into()))?;
115        if d2.len() != 2 || d1.len() != 2 {
116          return Err(Error::InvalidPath(path_str.into()));
117        }
118        push(last_part.as_bytes())?;
119        push(d2.as_bytes())?;
120        push(d1.as_bytes())?;
121      }
122    }
123  } else {
124    return Err(Error::InvalidPath(path_str.into()));
125  }
126
127  CROCKFORD_LOWER
128    .decode_u64(&buf[..pos])
129    .map_err(|_| Error::DecodeFailed(path_str.into()))
130}