Skip to main content

oxigdal_netcdf/
attribute.rs

1//! NetCDF attribute types and utilities.
2//!
3//! Attributes provide metadata for NetCDF files, groups, and variables.
4//! They can store text, numbers, or arrays of values.
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::{NetCdfError, Result};
9
10/// Attribute value types supported by NetCDF.
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum AttributeValue {
13    /// Text attribute
14    Text(String),
15    /// 8-bit signed integer
16    I8(Vec<i8>),
17    /// 8-bit unsigned integer
18    U8(Vec<u8>),
19    /// 16-bit signed integer
20    I16(Vec<i16>),
21    /// 16-bit unsigned integer (NetCDF-4 only)
22    U16(Vec<u16>),
23    /// 32-bit signed integer
24    I32(Vec<i32>),
25    /// 32-bit unsigned integer (NetCDF-4 only)
26    U32(Vec<u32>),
27    /// 64-bit signed integer (NetCDF-4 only)
28    I64(Vec<i64>),
29    /// 64-bit unsigned integer (NetCDF-4 only)
30    U64(Vec<u64>),
31    /// 32-bit floating point
32    F32(Vec<f32>),
33    /// 64-bit floating point
34    F64(Vec<f64>),
35}
36
37impl AttributeValue {
38    /// Create a text attribute.
39    #[must_use]
40    pub fn text(s: impl Into<String>) -> Self {
41        Self::Text(s.into())
42    }
43
44    /// Create a single i8 attribute.
45    #[must_use]
46    pub fn i8(value: i8) -> Self {
47        Self::I8(vec![value])
48    }
49
50    /// Create an i8 array attribute.
51    #[must_use]
52    pub fn i8_array(values: Vec<i8>) -> Self {
53        Self::I8(values)
54    }
55
56    /// Create a single u8 attribute.
57    #[must_use]
58    pub fn u8(value: u8) -> Self {
59        Self::U8(vec![value])
60    }
61
62    /// Create a u8 array attribute.
63    #[must_use]
64    pub fn u8_array(values: Vec<u8>) -> Self {
65        Self::U8(values)
66    }
67
68    /// Create a single i16 attribute.
69    #[must_use]
70    pub fn i16(value: i16) -> Self {
71        Self::I16(vec![value])
72    }
73
74    /// Create an i16 array attribute.
75    #[must_use]
76    pub fn i16_array(values: Vec<i16>) -> Self {
77        Self::I16(values)
78    }
79
80    /// Create a single u16 attribute (NetCDF-4 only).
81    #[must_use]
82    pub fn u16(value: u16) -> Self {
83        Self::U16(vec![value])
84    }
85
86    /// Create a u16 array attribute (NetCDF-4 only).
87    #[must_use]
88    pub fn u16_array(values: Vec<u16>) -> Self {
89        Self::U16(values)
90    }
91
92    /// Create a single i32 attribute.
93    #[must_use]
94    pub fn i32(value: i32) -> Self {
95        Self::I32(vec![value])
96    }
97
98    /// Create an i32 array attribute.
99    #[must_use]
100    pub fn i32_array(values: Vec<i32>) -> Self {
101        Self::I32(values)
102    }
103
104    /// Create a single u32 attribute (NetCDF-4 only).
105    #[must_use]
106    pub fn u32(value: u32) -> Self {
107        Self::U32(vec![value])
108    }
109
110    /// Create a u32 array attribute (NetCDF-4 only).
111    #[must_use]
112    pub fn u32_array(values: Vec<u32>) -> Self {
113        Self::U32(values)
114    }
115
116    /// Create a single i64 attribute (NetCDF-4 only).
117    #[must_use]
118    pub fn i64(value: i64) -> Self {
119        Self::I64(vec![value])
120    }
121
122    /// Create an i64 array attribute (NetCDF-4 only).
123    #[must_use]
124    pub fn i64_array(values: Vec<i64>) -> Self {
125        Self::I64(values)
126    }
127
128    /// Create a single u64 attribute (NetCDF-4 only).
129    #[must_use]
130    pub fn u64(value: u64) -> Self {
131        Self::U64(vec![value])
132    }
133
134    /// Create a u64 array attribute (NetCDF-4 only).
135    #[must_use]
136    pub fn u64_array(values: Vec<u64>) -> Self {
137        Self::U64(values)
138    }
139
140    /// Create a single f32 attribute.
141    #[must_use]
142    pub fn f32(value: f32) -> Self {
143        Self::F32(vec![value])
144    }
145
146    /// Create an f32 array attribute.
147    #[must_use]
148    pub fn f32_array(values: Vec<f32>) -> Self {
149        Self::F32(values)
150    }
151
152    /// Create a single f64 attribute.
153    #[must_use]
154    pub fn f64(value: f64) -> Self {
155        Self::F64(vec![value])
156    }
157
158    /// Create an f64 array attribute.
159    #[must_use]
160    pub fn f64_array(values: Vec<f64>) -> Self {
161        Self::F64(values)
162    }
163
164    /// Get as text if possible.
165    ///
166    /// # Errors
167    ///
168    /// Returns error if the attribute is not text.
169    pub fn as_text(&self) -> Result<&str> {
170        match self {
171            Self::Text(s) => Ok(s),
172            _ => Err(NetCdfError::AttributeError(
173                "Attribute is not text".to_string(),
174            )),
175        }
176    }
177
178    /// Get as i32 if possible.
179    ///
180    /// # Errors
181    ///
182    /// Returns error if the attribute is not i32 or has multiple values.
183    pub fn as_i32(&self) -> Result<i32> {
184        match self {
185            Self::I32(values) if values.len() == 1 => Ok(values[0]),
186            Self::I32(_) => Err(NetCdfError::AttributeError(
187                "Attribute has multiple values".to_string(),
188            )),
189            _ => Err(NetCdfError::AttributeError(
190                "Attribute is not i32".to_string(),
191            )),
192        }
193    }
194
195    /// Get as f32 if possible.
196    ///
197    /// # Errors
198    ///
199    /// Returns error if the attribute is not f32 or has multiple values.
200    pub fn as_f32(&self) -> Result<f32> {
201        match self {
202            Self::F32(values) if values.len() == 1 => Ok(values[0]),
203            Self::F32(_) => Err(NetCdfError::AttributeError(
204                "Attribute has multiple values".to_string(),
205            )),
206            _ => Err(NetCdfError::AttributeError(
207                "Attribute is not f32".to_string(),
208            )),
209        }
210    }
211
212    /// Get as f64 if possible.
213    ///
214    /// # Errors
215    ///
216    /// Returns error if the attribute is not f64 or has multiple values.
217    pub fn as_f64(&self) -> Result<f64> {
218        match self {
219            Self::F64(values) if values.len() == 1 => Ok(values[0]),
220            Self::F64(_) => Err(NetCdfError::AttributeError(
221                "Attribute has multiple values".to_string(),
222            )),
223            _ => Err(NetCdfError::AttributeError(
224                "Attribute is not f64".to_string(),
225            )),
226        }
227    }
228
229    /// Get the type name.
230    #[must_use]
231    pub const fn type_name(&self) -> &'static str {
232        match self {
233            Self::Text(_) => "text",
234            Self::I8(_) => "i8",
235            Self::U8(_) => "u8",
236            Self::I16(_) => "i16",
237            Self::U16(_) => "u16",
238            Self::I32(_) => "i32",
239            Self::U32(_) => "u32",
240            Self::I64(_) => "i64",
241            Self::U64(_) => "u64",
242            Self::F32(_) => "f32",
243            Self::F64(_) => "f64",
244        }
245    }
246
247    /// Get the number of values.
248    #[must_use]
249    pub fn len(&self) -> usize {
250        match self {
251            Self::Text(s) => s.len(),
252            Self::I8(v) => v.len(),
253            Self::U8(v) => v.len(),
254            Self::I16(v) => v.len(),
255            Self::U16(v) => v.len(),
256            Self::I32(v) => v.len(),
257            Self::U32(v) => v.len(),
258            Self::I64(v) => v.len(),
259            Self::U64(v) => v.len(),
260            Self::F32(v) => v.len(),
261            Self::F64(v) => v.len(),
262        }
263    }
264
265    /// Check if empty.
266    #[must_use]
267    pub fn is_empty(&self) -> bool {
268        self.len() == 0
269    }
270}
271
272/// An attribute with a name and value.
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub struct Attribute {
275    /// Name of the attribute
276    name: String,
277    /// Value of the attribute
278    value: AttributeValue,
279}
280
281impl Attribute {
282    /// Create a new attribute.
283    ///
284    /// # Arguments
285    ///
286    /// * `name` - Name of the attribute
287    /// * `value` - Value of the attribute
288    ///
289    /// # Errors
290    ///
291    /// Returns error if the name is empty.
292    pub fn new(name: impl Into<String>, value: AttributeValue) -> Result<Self> {
293        let name = name.into();
294        if name.is_empty() {
295            return Err(NetCdfError::AttributeError(
296                "Attribute name cannot be empty".to_string(),
297            ));
298        }
299        Ok(Self { name, value })
300    }
301
302    /// Get the attribute name.
303    #[must_use]
304    pub fn name(&self) -> &str {
305        &self.name
306    }
307
308    /// Get the attribute value.
309    #[must_use]
310    pub const fn value(&self) -> &AttributeValue {
311        &self.value
312    }
313
314    /// Get a mutable reference to the attribute value.
315    pub fn value_mut(&mut self) -> &mut AttributeValue {
316        &mut self.value
317    }
318}
319
320/// Collection of attributes.
321#[derive(Debug, Clone, Default, Serialize, Deserialize)]
322pub struct Attributes {
323    attributes: Vec<Attribute>,
324}
325
326impl Attributes {
327    /// Create a new empty attribute collection.
328    #[must_use]
329    pub const fn new() -> Self {
330        Self {
331            attributes: Vec::new(),
332        }
333    }
334
335    /// Create from a vector of attributes.
336    #[must_use]
337    pub const fn from_vec(attributes: Vec<Attribute>) -> Self {
338        Self { attributes }
339    }
340
341    /// Add an attribute.
342    ///
343    /// # Errors
344    ///
345    /// Returns error if an attribute with the same name already exists.
346    pub fn add(&mut self, attribute: Attribute) -> Result<()> {
347        if self.contains(attribute.name()) {
348            return Err(NetCdfError::AttributeError(format!(
349                "Attribute '{}' already exists",
350                attribute.name()
351            )));
352        }
353        self.attributes.push(attribute);
354        Ok(())
355    }
356
357    /// Add or replace an attribute.
358    pub fn set(&mut self, attribute: Attribute) {
359        if let Some(existing) = self.get_mut(attribute.name()) {
360            *existing = attribute;
361        } else {
362            self.attributes.push(attribute);
363        }
364    }
365
366    /// Get an attribute by name.
367    #[must_use]
368    pub fn get(&self, name: &str) -> Option<&Attribute> {
369        self.attributes.iter().find(|a| a.name() == name)
370    }
371
372    /// Get a mutable reference to an attribute by name.
373    pub fn get_mut(&mut self, name: &str) -> Option<&mut Attribute> {
374        self.attributes.iter_mut().find(|a| a.name() == name)
375    }
376
377    /// Get an attribute value by name.
378    #[must_use]
379    pub fn get_value(&self, name: &str) -> Option<&AttributeValue> {
380        self.get(name).map(|a| a.value())
381    }
382
383    /// Check if an attribute exists.
384    #[must_use]
385    pub fn contains(&self, name: &str) -> bool {
386        self.attributes.iter().any(|a| a.name() == name)
387    }
388
389    /// Get the number of attributes.
390    #[must_use]
391    pub fn len(&self) -> usize {
392        self.attributes.len()
393    }
394
395    /// Check if empty.
396    #[must_use]
397    pub fn is_empty(&self) -> bool {
398        self.attributes.is_empty()
399    }
400
401    /// Get an iterator over attributes.
402    pub fn iter(&self) -> impl Iterator<Item = &Attribute> {
403        self.attributes.iter()
404    }
405
406    /// Get names of all attributes.
407    #[must_use]
408    pub fn names(&self) -> Vec<&str> {
409        self.attributes.iter().map(|a| a.name()).collect()
410    }
411
412    /// Remove an attribute by name.
413    pub fn remove(&mut self, name: &str) -> Option<Attribute> {
414        self.attributes
415            .iter()
416            .position(|a| a.name() == name)
417            .map(|index| self.attributes.remove(index))
418    }
419}
420
421impl IntoIterator for Attributes {
422    type Item = Attribute;
423    type IntoIter = std::vec::IntoIter<Attribute>;
424
425    fn into_iter(self) -> Self::IntoIter {
426        self.attributes.into_iter()
427    }
428}
429
430impl<'a> IntoIterator for &'a Attributes {
431    type Item = &'a Attribute;
432    type IntoIter = std::slice::Iter<'a, Attribute>;
433
434    fn into_iter(self) -> Self::IntoIter {
435        self.attributes.iter()
436    }
437}
438
439impl FromIterator<Attribute> for Attributes {
440    fn from_iter<T: IntoIterator<Item = Attribute>>(iter: T) -> Self {
441        Self {
442            attributes: iter.into_iter().collect(),
443        }
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    // Allow panic and expect in tests - per project policy
450    #![allow(clippy::panic)]
451    #![allow(clippy::expect_used)]
452
453    use super::*;
454
455    #[test]
456    fn test_text_attribute() {
457        let attr = Attribute::new("title", AttributeValue::text("Test Data"))
458            .expect("Failed to create text attribute");
459        assert_eq!(attr.name(), "title");
460        assert_eq!(
461            attr.value().as_text().expect("Failed to get text value"),
462            "Test Data"
463        );
464    }
465
466    #[test]
467    fn test_numeric_attribute() {
468        let attr = Attribute::new("scale_factor", AttributeValue::f64(1.5))
469            .expect("Failed to create numeric attribute");
470        assert_eq!(attr.name(), "scale_factor");
471        assert_eq!(attr.value().as_f64().expect("Failed to get f64 value"), 1.5);
472    }
473
474    #[test]
475    fn test_array_attribute() {
476        let values = vec![1.0, 2.0, 3.0];
477        let attr = Attribute::new("coefficients", AttributeValue::f64_array(values.clone()))
478            .expect("Failed to create array attribute");
479        assert_eq!(attr.name(), "coefficients");
480        match attr.value() {
481            AttributeValue::F64(v) => assert_eq!(v, &values),
482            other => {
483                panic!("Expected F64 attribute value, got {:?}", other);
484            }
485        }
486    }
487
488    #[test]
489    fn test_attribute_collection() {
490        let mut attrs = Attributes::new();
491        attrs
492            .add(
493                Attribute::new("title", AttributeValue::text("Test"))
494                    .expect("Failed to create title attribute"),
495            )
496            .expect("Failed to add title attribute");
497        attrs
498            .add(
499                Attribute::new("version", AttributeValue::i32(1))
500                    .expect("Failed to create version attribute"),
501            )
502            .expect("Failed to add version attribute");
503
504        assert_eq!(attrs.len(), 2);
505        assert!(attrs.contains("title"));
506        assert!(attrs.contains("version"));
507
508        let title = attrs.get("title").expect("Failed to get title attribute");
509        assert_eq!(
510            title.value().as_text().expect("Failed to get text value"),
511            "Test"
512        );
513    }
514
515    #[test]
516    fn test_empty_attribute_name() {
517        let result = Attribute::new("", AttributeValue::text("test"));
518        assert!(result.is_err());
519    }
520
521    #[test]
522    fn test_duplicate_attribute() {
523        let mut attrs = Attributes::new();
524        attrs
525            .add(
526                Attribute::new("test", AttributeValue::i32(1))
527                    .expect("Failed to create first test attribute"),
528            )
529            .expect("Failed to add first test attribute");
530        let result = attrs.add(
531            Attribute::new("test", AttributeValue::i32(2))
532                .expect("Failed to create second test attribute"),
533        );
534        assert!(result.is_err());
535    }
536
537    #[test]
538    fn test_attribute_set() {
539        let mut attrs = Attributes::new();
540        attrs.set(
541            Attribute::new("test", AttributeValue::i32(1))
542                .expect("Failed to create test attribute with value 1"),
543        );
544        assert_eq!(
545            attrs
546                .get("test")
547                .expect("Failed to get test attribute")
548                .value()
549                .as_i32()
550                .expect("Failed to get i32 value"),
551            1
552        );
553
554        // Replace existing
555        attrs.set(
556            Attribute::new("test", AttributeValue::i32(2))
557                .expect("Failed to create test attribute with value 2"),
558        );
559        assert_eq!(
560            attrs
561                .get("test")
562                .expect("Failed to get test attribute after replacement")
563                .value()
564                .as_i32()
565                .expect("Failed to get i32 value after replacement"),
566            2
567        );
568        assert_eq!(attrs.len(), 1);
569    }
570
571    #[test]
572    fn test_attribute_remove() {
573        let mut attrs = Attributes::new();
574        attrs.set(
575            Attribute::new("test", AttributeValue::i32(1))
576                .expect("Failed to create test attribute for removal test"),
577        );
578        assert!(attrs.contains("test"));
579
580        let removed = attrs.remove("test");
581        assert!(removed.is_some());
582        assert!(!attrs.contains("test"));
583    }
584}