Skip to main content

rust_hdf5/
attribute.rs

1//! Attribute support.
2//!
3//! Attributes are small metadata items attached to datasets (or groups).
4//! They are created via the [`AttrBuilder`] API obtained from
5//! [`H5Dataset::new_attr`](crate::dataset::H5Dataset::new_attr).
6//!
7//! # Example
8//!
9//! ```no_run
10//! use rust_hdf5::H5File;
11//! use rust_hdf5::types::VarLenUnicode;
12//!
13//! let file = H5File::create("attrs.h5").unwrap();
14//! let ds = file.new_dataset::<f32>().shape(&[10]).create("data").unwrap();
15//! let attr = ds.new_attr::<VarLenUnicode>().shape(()).create("units").unwrap();
16//! attr.write_scalar(&VarLenUnicode("meters".to_string())).unwrap();
17//! ```
18
19use std::marker::PhantomData;
20
21use crate::format::messages::attribute::AttributeMessage;
22
23use crate::error::{Hdf5Error, Result};
24use crate::file::{borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
25use crate::types::VarLenUnicode;
26
27/// A handle to an HDF5 attribute.
28///
29/// After creating an attribute via [`AttrBuilder::create`], use
30/// [`write_scalar`](Self::write_scalar) or [`write_string`](Self::write_string)
31/// to set its value.
32///
33/// In read mode, use [`read_string`](Self::read_string) to read string attributes.
34pub struct H5Attribute {
35    file_inner: SharedInner,
36    ds_index: usize,
37    name: String,
38    /// Cached data for read-mode attributes.
39    read_data: Option<Vec<u8>>,
40}
41
42impl H5Attribute {
43    /// Create a read-mode attribute handle with cached data.
44    pub(crate) fn new_reader(file_inner: SharedInner, name: String, data: Vec<u8>) -> Self {
45        Self {
46            file_inner,
47            ds_index: usize::MAX,
48            name,
49            read_data: Some(data),
50        }
51    }
52
53    /// Return the attribute name.
54    pub fn name(&self) -> &str {
55        &self.name
56    }
57
58    /// Write a scalar value to the attribute.
59    ///
60    /// For `VarLenUnicode`, this writes a fixed-length string attribute
61    /// whose size is determined by the string value.
62    pub fn write_scalar(&self, value: &VarLenUnicode) -> Result<()> {
63        let attr_msg = AttributeMessage::scalar_string(&self.name, &value.0);
64
65        let mut inner = borrow_inner_mut(&self.file_inner);
66        match &mut *inner {
67            H5FileInner::Writer(writer) => {
68                writer.add_dataset_attribute(self.ds_index, attr_msg)?;
69                Ok(())
70            }
71            H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
72                "cannot write attributes in read mode".into(),
73            )),
74            H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
75        }
76    }
77
78    /// Write a string value to the attribute (convenience method).
79    pub fn write_string(&self, value: &str) -> Result<()> {
80        self.write_scalar(&VarLenUnicode(value.to_string()))
81    }
82
83    /// Write a numeric scalar attribute.
84    ///
85    /// ```no_run
86    /// # use rust_hdf5::H5File;
87    /// let file = H5File::create("num_attr.h5").unwrap();
88    /// let ds = file.new_dataset::<f32>().shape(&[10]).create("data").unwrap();
89    /// ds.write_raw(&[0.0f32; 10]).unwrap();
90    /// let attr = ds.new_attr::<f64>().shape(()).create("scale").unwrap();
91    /// attr.write_numeric(&3.14f64).unwrap();
92    /// ```
93    pub fn write_numeric<T: crate::types::H5Type>(&self, value: &T) -> Result<()> {
94        let es = T::element_size();
95        let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
96        let attr_msg = AttributeMessage::scalar_numeric(&self.name, T::hdf5_type(), raw.to_vec());
97
98        let mut inner = borrow_inner_mut(&self.file_inner);
99        match &mut *inner {
100            H5FileInner::Writer(writer) => {
101                writer.add_dataset_attribute(self.ds_index, attr_msg)?;
102                Ok(())
103            }
104            H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
105                "cannot write attributes in read mode".into(),
106            )),
107            H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
108        }
109    }
110
111    /// Read a numeric scalar attribute.
112    ///
113    /// ```no_run
114    /// # use rust_hdf5::H5File;
115    /// let file = H5File::open("num_attr.h5").unwrap();
116    /// let ds = file.dataset("data").unwrap();
117    /// let attr = ds.attr("scale").unwrap();
118    /// let val: f64 = attr.read_numeric().unwrap();
119    /// ```
120    pub fn read_numeric<T: crate::types::H5Type>(&self) -> Result<T> {
121        let data = self
122            .read_data
123            .as_ref()
124            .ok_or_else(|| Hdf5Error::InvalidState("attribute has no read data".into()))?;
125        let es = T::element_size();
126        if data.len() < es {
127            return Err(Hdf5Error::TypeMismatch(format!(
128                "attribute data {} bytes, need {} for type",
129                data.len(),
130                es
131            )));
132        }
133        unsafe {
134            let mut val = std::mem::MaybeUninit::<T>::uninit();
135            std::ptr::copy_nonoverlapping(data.as_ptr(), val.as_mut_ptr() as *mut u8, es);
136            Ok(val.assume_init())
137        }
138    }
139
140    /// Read the attribute value as a string.
141    ///
142    /// Works for fixed-length string attributes (as written by this library)
143    /// and returns the string value with any trailing null bytes stripped.
144    pub fn read_string(&self) -> Result<String> {
145        let data = self.read_data.as_ref().ok_or_else(|| {
146            Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
147        })?;
148        // Strip trailing null bytes
149        let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
150        Ok(String::from_utf8_lossy(&data[..end]).to_string())
151    }
152
153    /// Read the raw attribute data bytes.
154    pub fn read_raw(&self) -> Result<Vec<u8>> {
155        self.read_data.clone().ok_or_else(|| {
156            Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
157        })
158    }
159}
160
161/// A fluent builder for creating attributes on datasets.
162///
163/// Obtained from [`H5Dataset::new_attr::<T>()`](crate::dataset::H5Dataset::new_attr).
164pub struct AttrBuilder<'a, T> {
165    file_inner: &'a SharedInner,
166    ds_index: usize,
167    _shape_set: bool,
168    _marker: PhantomData<T>,
169}
170
171impl<'a, T> AttrBuilder<'a, T> {
172    pub(crate) fn new(file_inner: &'a SharedInner, ds_index: usize) -> Self {
173        Self {
174            file_inner,
175            ds_index,
176            _shape_set: false,
177            _marker: PhantomData,
178        }
179    }
180
181    /// Set the attribute shape. Use `()` for a scalar attribute.
182    #[must_use]
183    pub fn shape<S>(mut self, _shape: S) -> Self {
184        // For now we only support scalar attributes.
185        self._shape_set = true;
186        self
187    }
188
189    /// Create the attribute with the given name.
190    ///
191    /// The attribute is created but does not yet have a value.
192    /// Call [`H5Attribute::write_scalar`] to set the value.
193    pub fn create(self, name: &str) -> Result<H5Attribute> {
194        Ok(H5Attribute {
195            file_inner: clone_inner(self.file_inner),
196            ds_index: self.ds_index,
197            name: name.to_string(),
198            read_data: None,
199        })
200    }
201}