use std::collections::BTreeMap;
use ciborium::Value as CborValue;
use netcdf::AttributeValue;
pub(crate) const CF_ATTRIBUTES: &[&str] = &[
"standard_name",
"long_name",
"units",
"calendar",
"cell_methods",
"coordinates",
"axis",
"positive",
"valid_min",
"valid_max",
"valid_range",
"bounds",
"grid_mapping",
"ancillary_variables",
"flag_values",
"flag_meanings",
];
pub(crate) fn attr_value_to_cbor(val: &AttributeValue) -> CborValue {
match val {
AttributeValue::Str(s) => CborValue::Text(s.clone()),
AttributeValue::Strs(v) => {
CborValue::Array(v.iter().map(|s| CborValue::Text(s.clone())).collect())
}
AttributeValue::Double(f) => CborValue::Float(*f),
AttributeValue::Doubles(v) => {
CborValue::Array(v.iter().map(|f| CborValue::Float(*f)).collect())
}
AttributeValue::Float(f) => CborValue::Float(*f as f64),
AttributeValue::Floats(v) => {
CborValue::Array(v.iter().map(|f| CborValue::Float(*f as f64)).collect())
}
AttributeValue::Int(i) => CborValue::Integer((*i as i64).into()),
AttributeValue::Ints(v) => CborValue::Array(
v.iter()
.map(|i| CborValue::Integer((*i as i64).into()))
.collect(),
),
AttributeValue::Short(i) => CborValue::Integer((*i as i64).into()),
AttributeValue::Shorts(v) => CborValue::Array(
v.iter()
.map(|i| CborValue::Integer((*i as i64).into()))
.collect(),
),
AttributeValue::Ushort(i) => CborValue::Integer((*i as i64).into()),
AttributeValue::Ushorts(v) => CborValue::Array(
v.iter()
.map(|i| CborValue::Integer((*i as i64).into()))
.collect(),
),
AttributeValue::Uint(i) => CborValue::Integer((*i as i64).into()),
AttributeValue::Uints(v) => CborValue::Array(
v.iter()
.map(|i| CborValue::Integer((*i as i64).into()))
.collect(),
),
AttributeValue::Longlong(i) => CborValue::Integer((*i).into()),
AttributeValue::Longlongs(v) => {
CborValue::Array(v.iter().map(|i| CborValue::Integer((*i).into())).collect())
}
AttributeValue::Ulonglong(i) => CborValue::Integer((*i).into()),
AttributeValue::Ulonglongs(v) => {
CborValue::Array(v.iter().map(|i| CborValue::Integer((*i).into())).collect())
}
AttributeValue::Schar(i) => CborValue::Integer((*i as i64).into()),
AttributeValue::Schars(v) => CborValue::Array(
v.iter()
.map(|i| CborValue::Integer((*i as i64).into()))
.collect(),
),
AttributeValue::Uchar(i) => CborValue::Integer((*i as i64).into()),
AttributeValue::Uchars(v) => CborValue::Array(
v.iter()
.map(|i| CborValue::Integer((*i as i64).into()))
.collect(),
),
}
}
pub(crate) fn extract_var_attrs(var: &netcdf::Variable<'_>) -> BTreeMap<String, CborValue> {
let mut map = BTreeMap::new();
let var_name = var.name();
for attr in var.attributes() {
let name = attr.name();
match attr.value() {
Ok(val) => {
map.insert(name.to_string(), attr_value_to_cbor(&val));
}
Err(e) => {
eprintln!("warning: variable '{var_name}': failed to read attribute '{name}': {e}");
}
}
}
map
}
pub(crate) fn extract_cf_attrs(var: &netcdf::Variable<'_>) -> BTreeMap<String, CborValue> {
let mut map = BTreeMap::new();
let var_name = var.name();
for attr in var.attributes() {
let name = attr.name();
if !CF_ATTRIBUTES.contains(&name) {
continue;
}
match attr.value() {
Ok(val) => {
map.insert(name.to_string(), attr_value_to_cbor(&val));
}
Err(e) => {
eprintln!(
"warning: variable '{var_name}': failed to read CF attribute '{name}': {e}"
);
}
}
}
map
}
#[cfg(test)]
mod tests {
use super::*;
fn expect_text(val: &CborValue, want: &str) {
match val {
CborValue::Text(s) => assert_eq!(s, want),
other => panic!("expected Text, got {other:?}"),
}
}
fn expect_float(val: &CborValue, want: f64) {
match val {
CborValue::Float(f) => assert!((*f - want).abs() < 1e-12, "expected {want}, got {f}"),
other => panic!("expected Float, got {other:?}"),
}
}
fn expect_int(val: &CborValue, want: i64) {
match val {
CborValue::Integer(i) => {
let n: i128 = (*i).into();
assert_eq!(n, want as i128);
}
other => panic!("expected Integer, got {other:?}"),
}
}
fn expect_array_len(val: &CborValue, want: usize) -> &Vec<CborValue> {
match val {
CborValue::Array(arr) => {
assert_eq!(arr.len(), want);
arr
}
other => panic!("expected Array, got {other:?}"),
}
}
#[test]
fn attr_value_to_cbor_str() {
expect_text(
&attr_value_to_cbor(&AttributeValue::Str("K".to_string())),
"K",
);
}
#[test]
fn attr_value_to_cbor_strs() {
let v = attr_value_to_cbor(&AttributeValue::Strs(vec![
"a".to_string(),
"bb".to_string(),
]));
let arr = expect_array_len(&v, 2);
expect_text(&arr[0], "a");
expect_text(&arr[1], "bb");
}
#[test]
fn attr_value_to_cbor_double() {
expect_float(&attr_value_to_cbor(&AttributeValue::Double(273.15)), 273.15);
}
#[test]
fn attr_value_to_cbor_doubles() {
let v = attr_value_to_cbor(&AttributeValue::Doubles(vec![1.0, -2.5, 3.75]));
let arr = expect_array_len(&v, 3);
expect_float(&arr[0], 1.0);
expect_float(&arr[1], -2.5);
expect_float(&arr[2], 3.75);
}
#[test]
fn attr_value_to_cbor_float() {
expect_float(&attr_value_to_cbor(&AttributeValue::Float(2.5_f32)), 2.5);
}
#[test]
fn attr_value_to_cbor_floats() {
let v = attr_value_to_cbor(&AttributeValue::Floats(vec![1.0_f32, 2.0_f32]));
let arr = expect_array_len(&v, 2);
expect_float(&arr[0], 1.0);
expect_float(&arr[1], 2.0);
}
#[test]
fn attr_value_to_cbor_int() {
expect_int(&attr_value_to_cbor(&AttributeValue::Int(-42_i32)), -42);
}
#[test]
fn attr_value_to_cbor_ints() {
let v = attr_value_to_cbor(&AttributeValue::Ints(vec![1_i32, -1_i32, 999_i32]));
let arr = expect_array_len(&v, 3);
expect_int(&arr[0], 1);
expect_int(&arr[1], -1);
expect_int(&arr[2], 999);
}
#[test]
fn attr_value_to_cbor_short() {
expect_int(
&attr_value_to_cbor(&AttributeValue::Short(-32768_i16)),
-32768,
);
}
#[test]
fn attr_value_to_cbor_shorts() {
let v = attr_value_to_cbor(&AttributeValue::Shorts(vec![0_i16, -1_i16]));
let arr = expect_array_len(&v, 2);
expect_int(&arr[0], 0);
expect_int(&arr[1], -1);
}
#[test]
fn attr_value_to_cbor_ushort() {
expect_int(
&attr_value_to_cbor(&AttributeValue::Ushort(65535_u16)),
65535,
);
}
#[test]
fn attr_value_to_cbor_ushorts() {
let v = attr_value_to_cbor(&AttributeValue::Ushorts(vec![1_u16, 65535_u16]));
let arr = expect_array_len(&v, 2);
expect_int(&arr[0], 1);
expect_int(&arr[1], 65535);
}
#[test]
fn attr_value_to_cbor_uint() {
expect_int(
&attr_value_to_cbor(&AttributeValue::Uint(4_000_000_000_u32)),
4_000_000_000,
);
}
#[test]
fn attr_value_to_cbor_uints() {
let v = attr_value_to_cbor(&AttributeValue::Uints(vec![0_u32, 1_000_000_u32]));
let arr = expect_array_len(&v, 2);
expect_int(&arr[0], 0);
expect_int(&arr[1], 1_000_000);
}
#[test]
fn attr_value_to_cbor_longlong() {
expect_int(
&attr_value_to_cbor(&AttributeValue::Longlong(i64::MIN)),
i64::MIN,
);
}
#[test]
fn attr_value_to_cbor_longlongs() {
let v = attr_value_to_cbor(&AttributeValue::Longlongs(vec![0_i64, i64::MAX, i64::MIN]));
let arr = expect_array_len(&v, 3);
expect_int(&arr[0], 0);
expect_int(&arr[1], i64::MAX);
expect_int(&arr[2], i64::MIN);
}
#[test]
fn attr_value_to_cbor_ulonglong() {
expect_int(
&attr_value_to_cbor(&AttributeValue::Ulonglong(12345_u64)),
12345,
);
}
#[test]
fn attr_value_to_cbor_ulonglong_above_i64_max() {
let large = (i64::MAX as u64) + 42;
let cbor = attr_value_to_cbor(&AttributeValue::Ulonglong(large));
match cbor {
CborValue::Integer(i) => {
let n: i128 = i.into();
assert_eq!(n, large as i128);
assert!(n > 0, "u64 > i64::MAX must not wrap to negative");
}
other => panic!("expected Integer, got {other:?}"),
}
}
#[test]
fn attr_value_to_cbor_ulonglongs() {
let v = attr_value_to_cbor(&AttributeValue::Ulonglongs(vec![
0_u64,
42_u64,
1_000_000_u64,
u64::MAX,
]));
let arr = expect_array_len(&v, 4);
expect_int(&arr[0], 0);
expect_int(&arr[1], 42);
expect_int(&arr[2], 1_000_000);
match &arr[3] {
CborValue::Integer(i) => {
let n: i128 = (*i).into();
assert_eq!(n, u64::MAX as i128);
}
other => panic!("expected Integer for u64::MAX, got {other:?}"),
}
}
#[test]
fn attr_value_to_cbor_schar() {
expect_int(&attr_value_to_cbor(&AttributeValue::Schar(-128_i8)), -128);
}
#[test]
fn attr_value_to_cbor_schars() {
let v = attr_value_to_cbor(&AttributeValue::Schars(vec![-1_i8, 0_i8, 127_i8]));
let arr = expect_array_len(&v, 3);
expect_int(&arr[0], -1);
expect_int(&arr[1], 0);
expect_int(&arr[2], 127);
}
#[test]
fn attr_value_to_cbor_uchar() {
expect_int(&attr_value_to_cbor(&AttributeValue::Uchar(255_u8)), 255);
}
#[test]
fn attr_value_to_cbor_uchars() {
let v = attr_value_to_cbor(&AttributeValue::Uchars(vec![0_u8, 128_u8, 255_u8]));
let arr = expect_array_len(&v, 3);
expect_int(&arr[0], 0);
expect_int(&arr[1], 128);
expect_int(&arr[2], 255);
}
#[test]
fn cf_attributes_contains_all_16() {
assert_eq!(CF_ATTRIBUTES.len(), 16);
for expected in &[
"standard_name",
"long_name",
"units",
"calendar",
"cell_methods",
"coordinates",
"axis",
"positive",
"valid_min",
"valid_max",
"valid_range",
"bounds",
"grid_mapping",
"ancillary_variables",
"flag_values",
"flag_meanings",
] {
assert!(
CF_ATTRIBUTES.contains(expected),
"CF_ATTRIBUTES should contain '{expected}'"
);
}
}
}