Skip to main content

prikk_object/payload/
node.rs

1//! Node identity and kind types (FDD-03 §9.3.0).
2
3use prikk_error::{PrikkError, Result};
4
5use crate::payload::blob::BlobKind;
6
7/// Length of a node identity in bytes.
8pub const NODE_ID_BYTES: usize = 32;
9
10/// A 32-byte node identity (FDD-03 §9.3). Minted once at node creation and
11/// preserved across rename; encoded as a `bytes` field, not an `object_id`.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct NodeId([u8; NODE_ID_BYTES]);
14
15impl NodeId {
16    /// Wrap raw bytes as a node identity. Unchecked: accepts any 32 bytes, including
17    /// the reserved all-zero value. Intended for fixtures and round-trip
18    /// construction; production decode paths MUST use [`NodeId::try_from_bytes`].
19    #[must_use]
20    pub const fn from_bytes(bytes: [u8; NODE_ID_BYTES]) -> Self {
21        Self(bytes)
22    }
23
24    /// Wrap validated bytes as a node identity, rejecting the all-zero reserved
25    /// value. Production operation decoders use this.
26    pub fn try_from_bytes(bytes: [u8; NODE_ID_BYTES]) -> Result<Self> {
27        if bytes == [0_u8; NODE_ID_BYTES] {
28            return Err(PrikkError::MalformedData(
29                "node_id must be nonzero".to_string(),
30            ));
31        }
32        Ok(Self(bytes))
33    }
34
35    /// Borrow the raw identity bytes.
36    #[must_use]
37    pub const fn as_bytes(&self) -> &[u8; NODE_ID_BYTES] {
38        &self.0
39    }
40
41    /// True if this is the reserved all-zero identity, which is never valid in a
42    /// persisted node-bearing operation.
43    #[must_use]
44    pub fn is_zero(&self) -> bool {
45        self.0 == [0_u8; NODE_ID_BYTES]
46    }
47}
48
49/// Node kind. FDD-03 §9.3.0 `NodeKind` (`enum_u16`). Used by `DeleteNode`
50/// (`old_node_kind`) and state-tree entries (§10.2). `0x0000` is reserved/invalid.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[repr(u16)]
53pub enum NodeKind {
54    /// Text file node.
55    TextFile = 0x0001,
56    /// Binary file node.
57    BinaryFile = 0x0002,
58    /// Symlink node.
59    Symlink = 0x0003,
60}
61
62impl NodeKind {
63    /// Stable code.
64    #[must_use]
65    pub const fn code(self) -> u16 {
66        self as u16
67    }
68
69    /// Parse a stable code. Rejects `0x0000` (INVALID/reserved) and unknown values.
70    pub fn from_code(code: u16) -> Result<Self> {
71        match code {
72            0x0001 => Ok(Self::TextFile),
73            0x0002 => Ok(Self::BinaryFile),
74            0x0003 => Ok(Self::Symlink),
75            other => Err(PrikkError::MalformedData(format!(
76                "unknown or reserved node_kind code: {other:#06x}"
77            ))),
78        }
79    }
80
81    /// Derive a file node's kind from its referenced blob's `blob_kind` (§9.3.0).
82    /// A file node MUST NOT reference a `SNAPSHOT` blob.
83    pub fn from_file_blob_kind(blob_kind: BlobKind) -> Result<Self> {
84        match blob_kind {
85            BlobKind::Text => Ok(Self::TextFile),
86            BlobKind::Binary => Ok(Self::BinaryFile),
87            BlobKind::Snapshot => Err(PrikkError::MalformedData(
88                "file node must not reference a SNAPSHOT blob".to_string(),
89            )),
90        }
91    }
92}