arms/core/
blob.rs

1//! # Blob
2//!
3//! Raw payload data attached to a point.
4//!
5//! ARMS doesn't interpret this data - it's yours.
6//! Could be: tensor bytes, text, compressed state, anything.
7//!
8//! Separation of concerns:
9//! - Point = WHERE (position in space)
10//! - Blob = WHAT (the actual data)
11
12/// Raw data attached to a point
13///
14/// ARMS stores this opaquely. You define what it means.
15#[derive(Clone, Debug, PartialEq)]
16pub struct Blob {
17    data: Vec<u8>,
18}
19
20impl Blob {
21    /// Create a new blob from bytes
22    ///
23    /// # Example
24    /// ```
25    /// use arms::Blob;
26    /// let blob = Blob::new(vec![1, 2, 3, 4]);
27    /// assert_eq!(blob.size(), 4);
28    /// ```
29    pub fn new(data: Vec<u8>) -> Self {
30        Self { data }
31    }
32
33    /// Create an empty blob
34    ///
35    /// Useful when you only care about position, not payload.
36    pub fn empty() -> Self {
37        Self { data: vec![] }
38    }
39
40    /// Create a blob from a string (UTF-8 bytes)
41    ///
42    /// # Example
43    /// ```
44    /// use arms::Blob;
45    /// let blob = Blob::from_str("hello");
46    /// assert_eq!(blob.as_str(), Some("hello"));
47    /// ```
48    pub fn from_str(s: &str) -> Self {
49        Self {
50            data: s.as_bytes().to_vec(),
51        }
52    }
53
54    /// Get the raw bytes
55    pub fn data(&self) -> &[u8] {
56        &self.data
57    }
58
59    /// Get the size in bytes
60    pub fn size(&self) -> usize {
61        self.data.len()
62    }
63
64    /// Check if the blob is empty
65    pub fn is_empty(&self) -> bool {
66        self.data.is_empty()
67    }
68
69    /// Try to interpret as UTF-8 string
70    pub fn as_str(&self) -> Option<&str> {
71        std::str::from_utf8(&self.data).ok()
72    }
73
74    /// Consume and return the inner data
75    pub fn into_inner(self) -> Vec<u8> {
76        self.data
77    }
78}
79
80impl From<Vec<u8>> for Blob {
81    fn from(data: Vec<u8>) -> Self {
82        Self::new(data)
83    }
84}
85
86impl From<&[u8]> for Blob {
87    fn from(data: &[u8]) -> Self {
88        Self::new(data.to_vec())
89    }
90}
91
92impl From<&str> for Blob {
93    fn from(s: &str) -> Self {
94        Self::from_str(s)
95    }
96}
97
98impl From<String> for Blob {
99    fn from(s: String) -> Self {
100        Self::new(s.into_bytes())
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_blob_new() {
110        let blob = Blob::new(vec![1, 2, 3]);
111        assert_eq!(blob.data(), &[1, 2, 3]);
112        assert_eq!(blob.size(), 3);
113    }
114
115    #[test]
116    fn test_blob_empty() {
117        let blob = Blob::empty();
118        assert!(blob.is_empty());
119        assert_eq!(blob.size(), 0);
120    }
121
122    #[test]
123    fn test_blob_from_str() {
124        let blob = Blob::from_str("hello world");
125        assert_eq!(blob.as_str(), Some("hello world"));
126    }
127
128    #[test]
129    fn test_blob_as_str_invalid_utf8() {
130        let blob = Blob::new(vec![0xff, 0xfe]);
131        assert_eq!(blob.as_str(), None);
132    }
133
134    #[test]
135    fn test_blob_from_conversions() {
136        let blob1: Blob = vec![1, 2, 3].into();
137        assert_eq!(blob1.size(), 3);
138
139        let blob2: Blob = "test".into();
140        assert_eq!(blob2.as_str(), Some("test"));
141
142        let blob3: Blob = String::from("test").into();
143        assert_eq!(blob3.as_str(), Some("test"));
144    }
145
146    #[test]
147    fn test_blob_into_inner() {
148        let blob = Blob::new(vec![1, 2, 3]);
149        let data = blob.into_inner();
150        assert_eq!(data, vec![1, 2, 3]);
151    }
152}