use eccodes_sys::codes_keys_iterator;
use fallible_iterator::FallibleIterator;
use std::{fmt::Debug, marker::PhantomData, ptr::null_mut};
use tracing::{Level, event, instrument};
use crate::{
codes_message::CodesMessage,
errors::CodesError,
intermediate_bindings::{
codes_keys_iterator_delete, codes_keys_iterator_get_name, codes_keys_iterator_new,
codes_keys_iterator_next,
},
};
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct KeysIterator<'a> {
parent_message: PhantomData<&'a ()>,
iterator_handle: *mut codes_keys_iterator,
next_item_exists: bool,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum KeysIteratorFlags {
AllKeys = eccodes_sys::CODES_KEYS_ITERATOR_ALL_KEYS as isize,
DumpOnly = eccodes_sys::CODES_KEYS_ITERATOR_DUMP_ONLY as isize,
SkipCoded = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_CODED as isize,
SkipComputed = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_COMPUTED as isize,
SkipFunction = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_FUNCTION as isize,
SkipOptional = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_OPTIONAL as isize,
SkipReadOnly = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_READ_ONLY as isize,
SkipDuplicates = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_DUPLICATES as isize,
SkipEditionSpecific = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC as isize,
}
impl<P: Debug> CodesMessage<P> {
#[instrument(level = "trace")]
pub fn new_keys_iterator<'a>(
&'a mut self,
flags: &[KeysIteratorFlags],
namespace: &str,
) -> Result<KeysIterator<'a>, CodesError> {
let flags = flags.iter().map(|f| *f as u32).sum();
let iterator_handle =
unsafe { codes_keys_iterator_new(self.message_handle, flags, namespace)? };
let next_item_exists = unsafe { codes_keys_iterator_next(iterator_handle)? };
Ok(KeysIterator {
parent_message: PhantomData,
iterator_handle,
next_item_exists,
})
}
pub fn default_keys_iterator(&mut self) -> Result<KeysIterator<'_>, CodesError> {
let iterator_handle = unsafe { codes_keys_iterator_new(self.message_handle, 0, "")? };
let next_item_exists = unsafe { codes_keys_iterator_next(iterator_handle)? };
Ok(KeysIterator {
parent_message: PhantomData,
iterator_handle,
next_item_exists,
})
}
}
impl FallibleIterator for KeysIterator<'_> {
type Item = String;
type Error = CodesError;
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
if self.next_item_exists {
let key_name;
let next_item_exists;
unsafe {
key_name = codes_keys_iterator_get_name(self.iterator_handle)?;
next_item_exists = codes_keys_iterator_next(self.iterator_handle)?;
}
self.next_item_exists = next_item_exists;
Ok(Some(key_name))
} else {
Ok(None)
}
}
}
#[doc(hidden)]
impl Drop for KeysIterator<'_> {
#[instrument(level = "trace")]
fn drop(&mut self) {
unsafe {
codes_keys_iterator_delete(self.iterator_handle).unwrap_or_else(|error| {
event!(
Level::ERROR,
"codes_keys_iterator_delete() returned an error: {:?}",
&error
);
#[cfg(test)]
panic!("Error in KeysIterator::drop");
});
}
self.iterator_handle = null_mut();
}
}
#[cfg(test)]
mod tests {
use anyhow::{Context, Result};
use crate::FallibleIterator;
use crate::codes_file::{CodesFile, ProductKind};
use std::path::Path;
use super::KeysIteratorFlags;
#[test]
fn keys_iterator_parameters() -> Result<()> {
let file_path = Path::new("./data/iceland.grib");
let product_kind = ProductKind::GRIB;
let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
let mut current_message = handle
.ref_message_iter()
.next()?
.context("Message not some")?;
let flags = [
KeysIteratorFlags::AllKeys, KeysIteratorFlags::SkipOptional, KeysIteratorFlags::SkipReadOnly, KeysIteratorFlags::SkipDuplicates, ];
let namespace = "geography";
let mut kiter = current_message.new_keys_iterator(&flags, namespace)?;
assert!(kiter.next()?.is_some());
while let Some(key_name) = kiter.next()? {
assert!(!key_name.is_empty());
}
Ok(())
}
#[test]
fn invalid_namespace() -> Result<()> {
let file_path = Path::new("./data/iceland.grib");
let product_kind = ProductKind::GRIB;
let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
let mut current_message = handle
.ref_message_iter()
.next()?
.context("Message not some")?;
let flags = vec![
KeysIteratorFlags::AllKeys, ];
let namespace = "blabla";
let mut kiter = current_message.new_keys_iterator(&flags, namespace)?;
while let Some(key_name) = kiter.next()? {
assert!(!key_name.is_empty());
}
Ok(())
}
#[test]
fn destructor() -> Result<()> {
let file_path = Path::new("./data/iceland.grib");
let product_kind = ProductKind::GRIB;
let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
let mut current_message = handle
.ref_message_iter()
.next()?
.context("Message not some")?;
let _kiter = current_message.default_keys_iterator()?;
Ok(())
}
#[test]
fn destructor_null() -> Result<()> {
let file_path = Path::new("./data/iceland.grib");
let product_kind = ProductKind::GRIB;
let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
let mut current_message = handle
.ref_message_iter()
.next()?
.context("Message not some")?;
let mut kiter = current_message.default_keys_iterator()?;
kiter.iterator_handle = std::ptr::null_mut();
Ok(())
}
}