use std::collections::BTreeMap;
use ciborium::Value as CborValue;
use eccodes::{DynamicKeyType, FallibleIterator, KeysIteratorFlags, RefMessage};
const GRIB_NAMESPACES: &[&str] = &[
"ls",
"geography",
"time",
"vertical",
"parameter",
"statistics",
];
#[derive(Debug, Clone)]
pub(crate) struct GribKeySet {
pub(crate) keys: BTreeMap<String, CborValue>,
}
fn read_namespace_keys(
msg: &mut RefMessage,
namespace: &str,
) -> Result<BTreeMap<String, CborValue>, eccodes::errors::CodesError> {
let key_names: Vec<String> = {
let mut iter = msg.new_keys_iterator(
&[
KeysIteratorFlags::AllKeys,
KeysIteratorFlags::SkipDuplicates,
],
namespace,
)?;
let mut names = Vec::new();
while let Some(name) = iter.next()? {
names.push(name);
}
names
};
let mut keys = BTreeMap::new();
for key_name in &key_names {
if let Ok(val) = msg.read_key_dynamic(key_name)
&& let Some(cbor) = dynamic_to_cbor(val)
{
keys.insert(key_name.clone(), cbor);
}
}
Ok(keys)
}
fn dynamic_to_cbor(val: DynamicKeyType) -> Option<CborValue> {
match val {
DynamicKeyType::Str(s) if s != "MISSING" && s != "not_found" => Some(CborValue::Text(s)),
DynamicKeyType::Int(i) if i != 2147483647 && i != -2147483647 => {
Some(CborValue::Integer(i.into()))
}
DynamicKeyType::Float(f) if f.is_finite() => Some(CborValue::Float(f)),
_ => None, }
}
pub(crate) fn extract_mars_keys(
msg: &mut RefMessage,
) -> Result<GribKeySet, eccodes::errors::CodesError> {
let keys = read_namespace_keys(msg, "mars")?;
Ok(GribKeySet { keys })
}
pub(crate) fn extract_all_namespace_keys(
msg: &mut RefMessage,
) -> Result<BTreeMap<String, BTreeMap<String, CborValue>>, eccodes::errors::CodesError> {
let mut result = BTreeMap::new();
for &ns in GRIB_NAMESPACES {
let keys = read_namespace_keys(msg, ns)?;
if !keys.is_empty() {
result.insert(ns.to_string(), keys);
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn str_non_sentinel_becomes_text() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Str("2t".to_string())),
Some(CborValue::Text("2t".to_string())),
);
}
#[test]
fn str_missing_sentinel_returns_none() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Str("MISSING".to_string())),
None,
);
}
#[test]
fn str_not_found_sentinel_returns_none() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Str("not_found".to_string())),
None,
);
}
#[test]
fn int_non_sentinel_becomes_integer() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Int(42)),
Some(CborValue::Integer(42_i64.into())),
);
}
#[test]
fn int_positive_sentinel_returns_none() {
assert_eq!(dynamic_to_cbor(DynamicKeyType::Int(2147483647)), None);
}
#[test]
fn int_negative_sentinel_returns_none() {
assert_eq!(dynamic_to_cbor(DynamicKeyType::Int(-2147483647)), None);
}
#[test]
fn float_finite_becomes_float() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Float(1.5)),
Some(CborValue::Float(1.5)),
);
}
#[test]
fn float_nan_returns_none() {
assert_eq!(dynamic_to_cbor(DynamicKeyType::Float(f64::NAN)), None);
}
#[test]
fn float_positive_infinity_returns_none() {
assert_eq!(dynamic_to_cbor(DynamicKeyType::Float(f64::INFINITY)), None);
}
#[test]
fn float_negative_infinity_returns_none() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Float(f64::NEG_INFINITY)),
None,
);
}
#[test]
fn float_array_returns_none() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::FloatArray(vec![1.0, 2.0, 3.0])),
None,
);
}
#[test]
fn int_array_returns_none() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::IntArray(vec![1, 2, 3])),
None,
);
}
#[test]
fn bytes_returns_none() {
assert_eq!(
dynamic_to_cbor(DynamicKeyType::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])),
None,
);
}
}