use super::{ByteSourceHandle, Error, Result};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NamespaceNodeId(Box<[u8]>);
impl NamespaceNodeId {
pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Self {
Self(bytes.into().into_boxed_slice())
}
pub fn from_u64(value: u64) -> Self {
Self::from_bytes(value.to_le_bytes().to_vec())
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NamespaceNodeKind {
File,
Directory,
Symlink,
Special,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamespaceNodeRecord {
pub id: NamespaceNodeId,
pub kind: NamespaceNodeKind,
pub size: u64,
pub path: String,
}
impl NamespaceNodeRecord {
pub fn new(id: NamespaceNodeId, kind: NamespaceNodeKind, size: u64) -> Self {
Self {
id,
kind,
size,
path: String::new(),
}
}
pub fn with_path(mut self, path: impl Into<String>) -> Self {
self.path = path.into();
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamespaceDirectoryEntry {
pub name: String,
pub node_id: NamespaceNodeId,
pub kind: NamespaceNodeKind,
}
impl NamespaceDirectoryEntry {
pub fn new(name: impl Into<String>, node_id: NamespaceNodeId, kind: NamespaceNodeKind) -> Self {
Self {
name: name.into(),
node_id,
kind,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NamespaceStreamKind {
Data,
NamedData,
Fork,
ExtendedAttribute,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NamespaceStreamId {
pub kind: NamespaceStreamKind,
pub name: Option<String>,
}
impl NamespaceStreamId {
pub fn data() -> Self {
Self {
kind: NamespaceStreamKind::Data,
name: None,
}
}
pub fn named_data(name: impl Into<String>) -> Self {
Self {
kind: NamespaceStreamKind::NamedData,
name: Some(name.into()),
}
}
pub fn fork(name: impl Into<String>) -> Self {
Self {
kind: NamespaceStreamKind::Fork,
name: Some(name.into()),
}
}
pub fn xattr(name: impl Into<String>) -> Self {
Self {
kind: NamespaceStreamKind::ExtendedAttribute,
name: Some(name.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NamespaceStreamRecord {
pub id: NamespaceStreamId,
pub size: u64,
}
impl NamespaceStreamRecord {
pub fn new(id: NamespaceStreamId, size: u64) -> Self {
Self { id, size }
}
}
pub trait NamespaceSource: Send + Sync {
fn root_node_id(&self) -> NamespaceNodeId;
fn node(&self, node_id: &NamespaceNodeId) -> Result<NamespaceNodeRecord>;
fn read_dir(&self, directory_id: &NamespaceNodeId) -> Result<Vec<NamespaceDirectoryEntry>>;
fn lookup_name(
&self, directory_id: &NamespaceNodeId, name: &str,
) -> Result<NamespaceDirectoryEntry> {
self
.read_dir(directory_id)?
.into_iter()
.find(|entry| entry.name == name)
.ok_or_else(|| Error::not_found(format!("namespace entry not found: {name}")))
}
fn resolve_path(&self, path: &str) -> Result<NamespaceNodeRecord> {
let mut current = self.root_node_id();
for component in path.split('/') {
if component.is_empty() || component == "." {
continue;
}
if component == ".." {
return Err(Error::unsupported(
"generic namespace path resolution does not support parent traversal".to_string(),
));
}
current = self.lookup_name(¤t, component)?.node_id;
}
self.node(¤t)
}
fn data_streams(&self, node_id: &NamespaceNodeId) -> Result<Vec<NamespaceStreamRecord>>;
fn open_stream(
&self, node_id: &NamespaceNodeId, stream_id: &NamespaceStreamId,
) -> Result<ByteSourceHandle>;
fn open_content(&self, node_id: &NamespaceNodeId) -> Result<ByteSourceHandle> {
self.open_stream(node_id, &NamespaceStreamId::data())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn namespace_node_id_round_trips_bytes() {
let node_id = NamespaceNodeId::from_u64(42);
assert_eq!(node_id.as_bytes().len(), 8);
}
#[test]
fn directory_entry_preserves_name_and_kind() {
let entry = NamespaceDirectoryEntry::new(
"readme.txt",
NamespaceNodeId::from_u64(1),
NamespaceNodeKind::File,
);
assert_eq!(entry.name, "readme.txt");
assert_eq!(entry.kind, NamespaceNodeKind::File);
}
#[test]
fn default_stream_id_is_unnamed_data() {
let stream_id = NamespaceStreamId::data();
assert_eq!(stream_id.kind, NamespaceStreamKind::Data);
assert_eq!(stream_id.name, None);
}
}