Skip to main content

oxigdal_hdf5/
attribute.rs

1//! HDF5 attribute handling for metadata storage and retrieval.
2//!
3//! Attributes are small datasets attached to groups or datasets that store metadata.
4
5use crate::datatype::Datatype;
6use crate::error::{Hdf5Error, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// HDF5 attribute value
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum AttributeValue {
13    /// 8-bit signed integer
14    Int8(i8),
15    /// 8-bit unsigned integer
16    UInt8(u8),
17    /// 16-bit signed integer
18    Int16(i16),
19    /// 16-bit unsigned integer
20    UInt16(u16),
21    /// 32-bit signed integer
22    Int32(i32),
23    /// 32-bit unsigned integer
24    UInt32(u32),
25    /// 64-bit signed integer
26    Int64(i64),
27    /// 64-bit unsigned integer
28    UInt64(u64),
29    /// 32-bit floating-point
30    Float32(f32),
31    /// 64-bit floating-point
32    Float64(f64),
33    /// String value
34    String(String),
35    /// Array of 8-bit signed integers
36    Int8Array(Vec<i8>),
37    /// Array of 8-bit unsigned integers
38    UInt8Array(Vec<u8>),
39    /// Array of 16-bit signed integers
40    Int16Array(Vec<i16>),
41    /// Array of 16-bit unsigned integers
42    UInt16Array(Vec<u16>),
43    /// Array of 32-bit signed integers
44    Int32Array(Vec<i32>),
45    /// Array of 32-bit unsigned integers
46    UInt32Array(Vec<u32>),
47    /// Array of 64-bit signed integers
48    Int64Array(Vec<i64>),
49    /// Array of 64-bit unsigned integers
50    UInt64Array(Vec<u64>),
51    /// Array of 32-bit floating-point
52    Float32Array(Vec<f32>),
53    /// Array of 64-bit floating-point
54    Float64Array(Vec<f64>),
55    /// Array of strings
56    StringArray(Vec<String>),
57}
58
59impl AttributeValue {
60    /// Get the datatype of this attribute value
61    pub fn datatype(&self) -> Datatype {
62        match self {
63            Self::Int8(_) | Self::Int8Array(_) => Datatype::Int8,
64            Self::UInt8(_) | Self::UInt8Array(_) => Datatype::UInt8,
65            Self::Int16(_) | Self::Int16Array(_) => Datatype::Int16,
66            Self::UInt16(_) | Self::UInt16Array(_) => Datatype::UInt16,
67            Self::Int32(_) | Self::Int32Array(_) => Datatype::Int32,
68            Self::UInt32(_) | Self::UInt32Array(_) => Datatype::UInt32,
69            Self::Int64(_) | Self::Int64Array(_) => Datatype::Int64,
70            Self::UInt64(_) | Self::UInt64Array(_) => Datatype::UInt64,
71            Self::Float32(_) | Self::Float32Array(_) => Datatype::Float32,
72            Self::Float64(_) | Self::Float64Array(_) => Datatype::Float64,
73            Self::String(s) => Datatype::FixedString {
74                length: s.len(),
75                padding: crate::datatype::StringPadding::NullTerminated,
76            },
77            Self::StringArray(arr) => Datatype::FixedString {
78                length: arr.first().map(|s| s.len()).unwrap_or(0),
79                padding: crate::datatype::StringPadding::NullTerminated,
80            },
81        }
82    }
83
84    /// Get the shape (dimensions) of this attribute value
85    pub fn shape(&self) -> Vec<usize> {
86        match self {
87            Self::Int8(_)
88            | Self::UInt8(_)
89            | Self::Int16(_)
90            | Self::UInt16(_)
91            | Self::Int32(_)
92            | Self::UInt32(_)
93            | Self::Int64(_)
94            | Self::UInt64(_)
95            | Self::Float32(_)
96            | Self::Float64(_)
97            | Self::String(_) => vec![],
98            Self::Int8Array(v) => vec![v.len()],
99            Self::UInt8Array(v) => vec![v.len()],
100            Self::Int16Array(v) => vec![v.len()],
101            Self::UInt16Array(v) => vec![v.len()],
102            Self::Int32Array(v) => vec![v.len()],
103            Self::UInt32Array(v) => vec![v.len()],
104            Self::Int64Array(v) => vec![v.len()],
105            Self::UInt64Array(v) => vec![v.len()],
106            Self::Float32Array(v) => vec![v.len()],
107            Self::Float64Array(v) => vec![v.len()],
108            Self::StringArray(v) => vec![v.len()],
109        }
110    }
111
112    /// Convert to i32 if possible
113    pub fn as_i32(&self) -> Result<i32> {
114        match self {
115            Self::Int8(v) => Ok(*v as i32),
116            Self::UInt8(v) => Ok(*v as i32),
117            Self::Int16(v) => Ok(*v as i32),
118            Self::UInt16(v) => Ok(*v as i32),
119            Self::Int32(v) => Ok(*v),
120            Self::UInt32(v) => {
121                i32::try_from(*v).map_err(|_| Hdf5Error::type_conversion("u32", "i32"))
122            }
123            Self::Int64(v) => {
124                i32::try_from(*v).map_err(|_| Hdf5Error::type_conversion("i64", "i32"))
125            }
126            Self::UInt64(v) => {
127                i32::try_from(*v).map_err(|_| Hdf5Error::type_conversion("u64", "i32"))
128            }
129            _ => Err(Hdf5Error::type_conversion(self.datatype().name(), "i32")),
130        }
131    }
132
133    /// Convert to f64 if possible
134    pub fn as_f64(&self) -> Result<f64> {
135        match self {
136            Self::Int8(v) => Ok(*v as f64),
137            Self::UInt8(v) => Ok(*v as f64),
138            Self::Int16(v) => Ok(*v as f64),
139            Self::UInt16(v) => Ok(*v as f64),
140            Self::Int32(v) => Ok(*v as f64),
141            Self::UInt32(v) => Ok(*v as f64),
142            Self::Int64(v) => Ok(*v as f64),
143            Self::UInt64(v) => Ok(*v as f64),
144            Self::Float32(v) => Ok(*v as f64),
145            Self::Float64(v) => Ok(*v),
146            _ => Err(Hdf5Error::type_conversion(self.datatype().name(), "f64")),
147        }
148    }
149
150    /// Convert to string if possible
151    pub fn as_string(&self) -> Result<String> {
152        match self {
153            Self::String(s) => Ok(s.clone()),
154            Self::Int8(v) => Ok(v.to_string()),
155            Self::UInt8(v) => Ok(v.to_string()),
156            Self::Int16(v) => Ok(v.to_string()),
157            Self::UInt16(v) => Ok(v.to_string()),
158            Self::Int32(v) => Ok(v.to_string()),
159            Self::UInt32(v) => Ok(v.to_string()),
160            Self::Int64(v) => Ok(v.to_string()),
161            Self::UInt64(v) => Ok(v.to_string()),
162            Self::Float32(v) => Ok(v.to_string()),
163            Self::Float64(v) => Ok(v.to_string()),
164            _ => Err(Hdf5Error::type_conversion(self.datatype().name(), "string")),
165        }
166    }
167
168    /// Convert to i32 array if possible
169    pub fn as_i32_array(&self) -> Result<Vec<i32>> {
170        match self {
171            Self::Int32Array(v) => Ok(v.clone()),
172            Self::Int8Array(v) => Ok(v.iter().map(|&x| x as i32).collect()),
173            Self::UInt8Array(v) => Ok(v.iter().map(|&x| x as i32).collect()),
174            Self::Int16Array(v) => Ok(v.iter().map(|&x| x as i32).collect()),
175            Self::UInt16Array(v) => Ok(v.iter().map(|&x| x as i32).collect()),
176            _ => Err(Hdf5Error::type_conversion(self.datatype().name(), "i32[]")),
177        }
178    }
179
180    /// Convert to f64 array if possible
181    pub fn as_f64_array(&self) -> Result<Vec<f64>> {
182        match self {
183            Self::Float64Array(v) => Ok(v.clone()),
184            Self::Float32Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
185            Self::Int8Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
186            Self::UInt8Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
187            Self::Int16Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
188            Self::UInt16Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
189            Self::Int32Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
190            Self::UInt32Array(v) => Ok(v.iter().map(|&x| x as f64).collect()),
191            _ => Err(Hdf5Error::type_conversion(self.datatype().name(), "f64[]")),
192        }
193    }
194
195    /// Convert to string array if possible
196    pub fn as_string_array(&self) -> Result<Vec<String>> {
197        match self {
198            Self::StringArray(v) => Ok(v.clone()),
199            _ => Err(Hdf5Error::type_conversion(
200                self.datatype().name(),
201                "string[]",
202            )),
203        }
204    }
205}
206
207/// HDF5 attribute
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209pub struct Attribute {
210    /// Attribute name
211    name: String,
212    /// Attribute value
213    value: AttributeValue,
214}
215
216impl Attribute {
217    /// Create a new attribute
218    pub fn new(name: String, value: AttributeValue) -> Self {
219        Self { name, value }
220    }
221
222    /// Create an i32 attribute
223    pub fn i32(name: impl Into<String>, value: i32) -> Self {
224        Self::new(name.into(), AttributeValue::Int32(value))
225    }
226
227    /// Create a f64 attribute
228    pub fn f64(name: impl Into<String>, value: f64) -> Self {
229        Self::new(name.into(), AttributeValue::Float64(value))
230    }
231
232    /// Create a string attribute
233    pub fn string(name: impl Into<String>, value: impl Into<String>) -> Self {
234        Self::new(name.into(), AttributeValue::String(value.into()))
235    }
236
237    /// Create an i32 array attribute
238    pub fn i32_array(name: impl Into<String>, value: Vec<i32>) -> Self {
239        Self::new(name.into(), AttributeValue::Int32Array(value))
240    }
241
242    /// Create a f64 array attribute
243    pub fn f64_array(name: impl Into<String>, value: Vec<f64>) -> Self {
244        Self::new(name.into(), AttributeValue::Float64Array(value))
245    }
246
247    /// Create a string array attribute
248    pub fn string_array(name: impl Into<String>, value: Vec<String>) -> Self {
249        Self::new(name.into(), AttributeValue::StringArray(value))
250    }
251
252    /// Get the attribute name
253    pub fn name(&self) -> &str {
254        &self.name
255    }
256
257    /// Get the attribute value
258    pub fn value(&self) -> &AttributeValue {
259        &self.value
260    }
261
262    /// Get the datatype
263    pub fn datatype(&self) -> Datatype {
264        self.value.datatype()
265    }
266
267    /// Get the shape
268    pub fn shape(&self) -> Vec<usize> {
269        self.value.shape()
270    }
271
272    /// Get as i32 if possible
273    pub fn as_i32(&self) -> Result<i32> {
274        self.value.as_i32()
275    }
276
277    /// Get as f64 if possible
278    pub fn as_f64(&self) -> Result<f64> {
279        self.value.as_f64()
280    }
281
282    /// Get as string if possible
283    pub fn as_string(&self) -> Result<String> {
284        self.value.as_string()
285    }
286
287    /// Get as i32 array if possible
288    pub fn as_i32_array(&self) -> Result<Vec<i32>> {
289        self.value.as_i32_array()
290    }
291
292    /// Get as f64 array if possible
293    pub fn as_f64_array(&self) -> Result<Vec<f64>> {
294        self.value.as_f64_array()
295    }
296
297    /// Get as string array if possible
298    pub fn as_string_array(&self) -> Result<Vec<String>> {
299        self.value.as_string_array()
300    }
301}
302
303/// Collection of attributes
304#[derive(Debug, Clone, Default, Serialize, Deserialize)]
305pub struct Attributes {
306    /// Map of attribute name to attribute
307    attributes: HashMap<String, Attribute>,
308}
309
310impl Attributes {
311    /// Create a new attributes collection
312    pub fn new() -> Self {
313        Self {
314            attributes: HashMap::new(),
315        }
316    }
317
318    /// Add an attribute
319    pub fn add(&mut self, attribute: Attribute) {
320        self.attributes
321            .insert(attribute.name().to_string(), attribute);
322    }
323
324    /// Get an attribute by name
325    pub fn get(&self, name: &str) -> Result<&Attribute> {
326        self.attributes
327            .get(name)
328            .ok_or_else(|| Hdf5Error::attribute_not_found(name))
329    }
330
331    /// Check if an attribute exists
332    pub fn contains(&self, name: &str) -> bool {
333        self.attributes.contains_key(name)
334    }
335
336    /// Get all attribute names
337    pub fn names(&self) -> Vec<&str> {
338        self.attributes.keys().map(|s| s.as_str()).collect()
339    }
340
341    /// Get the number of attributes
342    pub fn len(&self) -> usize {
343        self.attributes.len()
344    }
345
346    /// Check if empty
347    pub fn is_empty(&self) -> bool {
348        self.attributes.is_empty()
349    }
350
351    /// Iterate over attributes
352    pub fn iter(&self) -> impl Iterator<Item = &Attribute> {
353        self.attributes.values()
354    }
355
356    /// Remove an attribute
357    pub fn remove(&mut self, name: &str) -> Option<Attribute> {
358        self.attributes.remove(name)
359    }
360
361    /// Clear all attributes
362    pub fn clear(&mut self) {
363        self.attributes.clear();
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_attribute_value_i32() {
373        let value = AttributeValue::Int32(42);
374        assert_eq!(value.as_i32().ok(), Some(42));
375        assert_eq!(value.as_f64().ok(), Some(42.0));
376        assert_eq!(value.as_string().ok(), Some("42".to_string()));
377        assert_eq!(value.shape(), Vec::<usize>::new());
378    }
379
380    #[test]
381    fn test_attribute_value_f64() {
382        let value = AttributeValue::Float64(3.125);
383        assert_eq!(value.as_f64().ok(), Some(3.125));
384        assert!(value.as_i32().is_err());
385        assert_eq!(value.shape(), Vec::<usize>::new());
386    }
387
388    #[test]
389    fn test_attribute_value_string() {
390        let value = AttributeValue::String("hello".to_string());
391        assert_eq!(value.as_string().ok(), Some("hello".to_string()));
392        assert!(value.as_i32().is_err());
393        assert_eq!(value.shape(), Vec::<usize>::new());
394    }
395
396    #[test]
397    fn test_attribute_value_array() {
398        let value = AttributeValue::Int32Array(vec![1, 2, 3, 4, 5]);
399        assert_eq!(value.as_i32_array().ok(), Some(vec![1, 2, 3, 4, 5]));
400        assert_eq!(
401            value.as_f64_array().ok(),
402            Some(vec![1.0, 2.0, 3.0, 4.0, 5.0])
403        );
404        assert_eq!(value.shape(), vec![5]);
405    }
406
407    #[test]
408    fn test_attribute_creation() {
409        let attr = Attribute::i32("version", 1);
410        assert_eq!(attr.name(), "version");
411        assert_eq!(attr.as_i32().ok(), Some(1));
412
413        let attr = Attribute::f64("scale", 0.5);
414        assert_eq!(attr.name(), "scale");
415        assert_eq!(attr.as_f64().ok(), Some(0.5));
416
417        let attr = Attribute::string("units", "meters");
418        assert_eq!(attr.name(), "units");
419        assert_eq!(attr.as_string().ok(), Some("meters".to_string()));
420    }
421
422    #[test]
423    fn test_attributes_collection() {
424        let mut attrs = Attributes::new();
425        assert!(attrs.is_empty());
426
427        attrs.add(Attribute::i32("version", 1));
428        attrs.add(Attribute::f64("scale", 0.5));
429        attrs.add(Attribute::string("units", "meters"));
430
431        assert_eq!(attrs.len(), 3);
432        assert!(attrs.contains("version"));
433        assert!(attrs.contains("scale"));
434        assert!(attrs.contains("units"));
435        assert!(!attrs.contains("nonexistent"));
436
437        let version = attrs.get("version").expect("version not found");
438        assert_eq!(version.as_i32().ok(), Some(1));
439
440        let scale = attrs.get("scale").expect("scale not found");
441        assert_eq!(scale.as_f64().ok(), Some(0.5));
442
443        let units = attrs.get("units").expect("units not found");
444        assert_eq!(units.as_string().ok(), Some("meters".to_string()));
445
446        assert!(attrs.get("nonexistent").is_err());
447
448        attrs.remove("version");
449        assert_eq!(attrs.len(), 2);
450        assert!(!attrs.contains("version"));
451
452        attrs.clear();
453        assert!(attrs.is_empty());
454    }
455
456    #[test]
457    fn test_attributes_names() {
458        let mut attrs = Attributes::new();
459        attrs.add(Attribute::i32("a", 1));
460        attrs.add(Attribute::i32("b", 2));
461        attrs.add(Attribute::i32("c", 3));
462
463        let mut names = attrs.names();
464        names.sort();
465        assert_eq!(names, vec!["a", "b", "c"]);
466    }
467}