1use hdf5_reader::group::Group;
14use hdf5_reader::messages::datatype::{Datatype, VarLenKind};
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 {
146 base,
147 kind: VarLenKind::String,
148 ..
149 } if attr.num_elements() == 1
150 && matches!(
151 base.as_ref(),
152 Datatype::FixedPoint {
153 size: 1,
154 signed: false,
155 ..
156 }
157 ) =>
158 {
159 read_attr(attr.read_string().map(NcAttrValue::Chars))
160 }
161 _ if strict => Err(unsupported()),
162 _ => Ok(None),
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use hdf5_reader::Attribute;
169 use hdf5_reader::ByteOrder;
170
171 use super::*;
172
173 #[test]
174 fn strict_mode_rejects_unsupported_attribute_types() {
175 let attr = Attribute {
176 name: "opaque_attr".to_string(),
177 datatype: Datatype::Opaque {
178 size: 4,
179 tag: "test".to_string(),
180 },
181 shape: vec![1],
182 raw_data: vec![0, 0, 0, 0],
183 };
184
185 let err = convert_attribute_value(&attr, crate::NcMetadataMode::Strict).unwrap_err();
186 assert!(matches!(err, Error::InvalidData(_)));
187 }
188
189 #[test]
190 fn lossy_mode_skips_unsupported_attribute_types() {
191 let attr = Attribute {
192 name: "bad_int".to_string(),
193 datatype: Datatype::FixedPoint {
194 size: 16,
195 signed: true,
196 byte_order: ByteOrder::LittleEndian,
197 },
198 shape: vec![1],
199 raw_data: vec![0; 16],
200 };
201
202 assert!(convert_attribute_value(&attr, crate::NcMetadataMode::Lossy)
203 .unwrap()
204 .is_none());
205 }
206}