1use hdf5_reader::group::Group;
14use hdf5_reader::messages::datatype::Datatype;
15
16use crate::error::{Error, Result};
17use crate::types::{NcAttrValue, NcAttribute};
18
19const INTERNAL_ATTRIBUTES: &[&str] = &[
21 "_NCProperties",
22 "_Netcdf4Dimid",
23 "_Netcdf4Coordinates",
24 "_nc3_strict",
25 "DIMENSION_LIST",
26 "REFERENCE_LIST",
27 "CLASS",
28 "NAME",
29];
30
31pub fn is_internal_attribute(name: &str) -> bool {
34 INTERNAL_ATTRIBUTES.contains(&name)
35}
36
37pub fn extract_group_attributes(
40 group: &Group,
41 metadata_mode: crate::NcMetadataMode,
42) -> Result<Vec<NcAttribute>> {
43 collect_visible_attributes(&group.attributes()?, metadata_mode)
44}
45
46pub fn extract_variable_attributes(
49 dataset: &hdf5_reader::Dataset,
50 metadata_mode: crate::NcMetadataMode,
51) -> Result<Vec<NcAttribute>> {
52 collect_visible_attributes(&dataset.attributes(), metadata_mode)
53}
54
55fn collect_visible_attributes(
56 attrs: &[hdf5_reader::Attribute],
57 metadata_mode: crate::NcMetadataMode,
58) -> Result<Vec<NcAttribute>> {
59 let mut nc_attrs = Vec::new();
60 for attr in attrs {
61 if is_internal_attribute(&attr.name) {
62 continue;
63 }
64 if let Some(nc_attr) = convert_attribute(attr, metadata_mode)? {
65 nc_attrs.push(nc_attr);
66 }
67 }
68 Ok(nc_attrs)
69}
70
71pub fn convert_visible_attribute(
72 attr: &hdf5_reader::Attribute,
73 metadata_mode: crate::NcMetadataMode,
74) -> Result<Option<NcAttribute>> {
75 if is_internal_attribute(&attr.name) {
76 return Ok(None);
77 }
78 convert_attribute(attr, metadata_mode)
79}
80
81fn convert_attribute(
83 attr: &hdf5_reader::Attribute,
84 metadata_mode: crate::NcMetadataMode,
85) -> Result<Option<NcAttribute>> {
86 let Some(value) = convert_attribute_value(attr, metadata_mode)? else {
87 return Ok(None);
88 };
89
90 Ok(Some(NcAttribute {
91 name: attr.name.clone(),
92 value,
93 }))
94}
95
96fn convert_attribute_value(
98 attr: &hdf5_reader::Attribute,
99 metadata_mode: crate::NcMetadataMode,
100) -> Result<Option<NcAttrValue>> {
101 let strict = metadata_mode == crate::NcMetadataMode::Strict;
102
103 let unsupported = || {
104 Error::InvalidData(format!(
105 "attribute '{}' uses unsupported NetCDF-4 datatype {:?}",
106 attr.name, attr.datatype
107 ))
108 };
109
110 let read_attr = |result: hdf5_reader::error::Result<NcAttrValue>| match result {
111 Ok(value) => Ok(Some(value)),
112 Err(err) if strict => Err(Error::InvalidData(format!(
113 "attribute '{}' could not be decoded: {err}",
114 attr.name
115 ))),
116 Err(_) => Ok(None),
117 };
118
119 match &attr.datatype {
120 Datatype::FixedPoint { size, signed, .. } => match (size, signed) {
121 (1, true) => read_attr(attr.read_1d::<i8>().map(NcAttrValue::Bytes)),
122 (1, false) => read_attr(attr.read_1d::<u8>().map(NcAttrValue::UBytes)),
123 (2, true) => read_attr(attr.read_1d::<i16>().map(NcAttrValue::Shorts)),
124 (2, false) => read_attr(attr.read_1d::<u16>().map(NcAttrValue::UShorts)),
125 (4, true) => read_attr(attr.read_1d::<i32>().map(NcAttrValue::Ints)),
126 (4, false) => read_attr(attr.read_1d::<u32>().map(NcAttrValue::UInts)),
127 (8, true) => read_attr(attr.read_1d::<i64>().map(NcAttrValue::Int64s)),
128 (8, false) => read_attr(attr.read_1d::<u64>().map(NcAttrValue::UInt64s)),
129 _ if strict => Err(unsupported()),
130 _ => Ok(None),
131 },
132 Datatype::FloatingPoint { size, .. } => match size {
133 4 => read_attr(attr.read_1d::<f32>().map(NcAttrValue::Floats)),
134 8 => read_attr(attr.read_1d::<f64>().map(NcAttrValue::Doubles)),
135 _ if strict => Err(unsupported()),
136 _ => Ok(None),
137 },
138 Datatype::String { .. } => {
139 if attr.num_elements() == 1 {
140 read_attr(attr.read_string().map(NcAttrValue::Chars))
141 } else {
142 read_attr(attr.read_strings().map(NcAttrValue::Strings))
143 }
144 }
145 Datatype::VarLen { base }
146 if attr.num_elements() == 1
147 && matches!(
148 base.as_ref(),
149 Datatype::FixedPoint {
150 size: 1,
151 signed: false,
152 ..
153 }
154 ) =>
155 {
156 read_attr(attr.read_string().map(NcAttrValue::Chars))
157 }
158 _ if strict => Err(unsupported()),
159 _ => Ok(None),
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use hdf5_reader::Attribute;
166 use hdf5_reader::ByteOrder;
167
168 use super::*;
169
170 #[test]
171 fn strict_mode_rejects_unsupported_attribute_types() {
172 let attr = Attribute {
173 name: "opaque_attr".to_string(),
174 datatype: Datatype::Opaque {
175 size: 4,
176 tag: "test".to_string(),
177 },
178 shape: vec![1],
179 raw_data: vec![0, 0, 0, 0],
180 };
181
182 let err = convert_attribute_value(&attr, crate::NcMetadataMode::Strict).unwrap_err();
183 assert!(matches!(err, Error::InvalidData(_)));
184 }
185
186 #[test]
187 fn lossy_mode_skips_unsupported_attribute_types() {
188 let attr = Attribute {
189 name: "bad_int".to_string(),
190 datatype: Datatype::FixedPoint {
191 size: 16,
192 signed: true,
193 byte_order: ByteOrder::LittleEndian,
194 },
195 shape: vec![1],
196 raw_data: vec![0; 16],
197 };
198
199 assert!(convert_attribute_value(&attr, crate::NcMetadataMode::Lossy)
200 .unwrap()
201 .is_none());
202 }
203}