From 7eb1fe0b3fe540b3a38fe51d62ee773b4a397b82 Mon Sep 17 00:00:00 2001 From: Jakub Lewandowski <22771850+Quba1@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:43:29 +0100 Subject: [PATCH] Non-specific API improvements (#15) * handoff message cloning directly to ecCodes * add test iter beyond none * remove need for clone to find nearest * (tests not working) remove need for clone for KeysIter * fix tests for new KeysIterator * add initial null pointer check * refactor intermediate bindings * add libc null check * refactor out codes_nearest * refactor out keys_iterator * refactor out keyed_message * codes_index module as a file * refactor Key definition * rustfmt * remove unwraps in tests * ignore failing tests in main branch --- .github/workflows/rust-dev.yml | 4 +- benches/main.rs | 7 +- src/codes_handle/iterator.rs | 76 ++- src/codes_handle/keyed_message/mod.rs | 234 ------- src/codes_handle/mod.rs | 188 ++---- src/{codes_index/mod.rs => codes_index.rs} | 44 +- src/codes_nearest.rs | 142 ++++ src/errors.rs | 18 + src/intermediate_bindings/codes_get.rs | 248 +++++++ src/intermediate_bindings/codes_handle.rs | 94 +++ src/intermediate_bindings/codes_index.rs | 46 +- src/intermediate_bindings/codes_keys.rs | 69 ++ src/intermediate_bindings/codes_set.rs | 148 +++++ src/intermediate_bindings/grib_nearest.rs | 95 +++ src/intermediate_bindings/mod.rs | 605 ++---------------- src/keyed_message/mod.rs | 158 +++++ src/{codes_handle => }/keyed_message/read.rs | 45 +- src/{codes_handle => }/keyed_message/write.rs | 26 +- .../iterator.rs => keys_iterator.rs} | 249 +++---- src/lib.rs | 11 +- src/pointer_guard.rs | 45 ++ tests/handle.rs | 43 +- tests/index.rs | 142 ++-- 23 files changed, 1448 insertions(+), 1289 deletions(-) delete mode 100644 src/codes_handle/keyed_message/mod.rs rename src/{codes_index/mod.rs => codes_index.rs} (82%) create mode 100644 src/codes_nearest.rs create mode 100644 src/intermediate_bindings/codes_get.rs create mode 100644 src/intermediate_bindings/codes_handle.rs create mode 100644 src/intermediate_bindings/codes_keys.rs create mode 100644 src/intermediate_bindings/codes_set.rs create mode 100644 src/intermediate_bindings/grib_nearest.rs create mode 100644 src/keyed_message/mod.rs rename src/{codes_handle => }/keyed_message/read.rs (87%) rename src/{codes_handle => }/keyed_message/write.rs (91%) rename src/{codes_handle/keyed_message/iterator.rs => keys_iterator.rs} (54%) create mode 100644 src/pointer_guard.rs diff --git a/.github/workflows/rust-dev.yml b/.github/workflows/rust-dev.yml index 5ab6fe2..566ce0b 100644 --- a/.github/workflows/rust-dev.yml +++ b/.github/workflows/rust-dev.yml @@ -29,7 +29,7 @@ jobs: cargo clippy --features "experimental_index" - name: Build release run: | - cargo build --release --features "experimental_index" + cargo test --features "experimental_index" -- --include-ignored build-macos: @@ -51,4 +51,4 @@ jobs: cargo clippy --features "experimental_index" - name: Build release run: | - cargo build --release --features "experimental_index" + cargo test --features "experimental_index" -- --include-ignored diff --git a/benches/main.rs b/benches/main.rs index 6445e86..ba2b781 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -41,9 +41,10 @@ pub fn key_reading(c: &mut Criterion) { }); c.bench_function("problematic key reading", |b| { - b.iter(|| msg.read_key(black_box("zero")).unwrap_or_else(|_|{ - msg.read_key(black_box("zeros")).unwrap() - })) + b.iter(|| { + msg.read_key(black_box("zero")) + .unwrap_or_else(|_| msg.read_key(black_box("zeros")).unwrap()) + }) }); } diff --git a/src/codes_handle/iterator.rs b/src/codes_handle/iterator.rs index 3b0da1f..2baaebd 100644 --- a/src/codes_handle/iterator.rs +++ b/src/codes_handle/iterator.rs @@ -3,12 +3,12 @@ use std::ptr; use fallible_streaming_iterator::FallibleStreamingIterator; use crate::{ - codes_handle::{CodesHandle, KeyedMessage}, errors::CodesError, intermediate_bindings::{codes_handle_delete, codes_handle_new_from_file}, + CodesHandle, KeyedMessage, }; #[cfg(feature = "experimental_index")] -use crate::{intermediate_bindings::codes_index::codes_handle_new_from_index, CodesIndex}; +use crate::{intermediate_bindings::codes_handle_new_from_index, CodesIndex}; use super::GribFile; @@ -98,11 +98,6 @@ impl FallibleStreamingIterator for CodesHandle { self.unsafe_message = KeyedMessage { message_handle: new_eccodes_handle, - iterator_flags: None, - iterator_namespace: None, - keys_iterator: None, - keys_iterator_next_item_exists: false, - nearest_handle: None, }; Ok(()) @@ -113,7 +108,7 @@ impl FallibleStreamingIterator for CodesHandle { None } else { Some(&self.unsafe_message) - } + } } } @@ -136,11 +131,6 @@ impl FallibleStreamingIterator for CodesHandle { self.unsafe_message = KeyedMessage { message_handle: new_eccodes_handle, - iterator_flags: None, - iterator_namespace: None, - keys_iterator: None, - keys_iterator_next_item_exists: false, - nearest_handle: None, }; Ok(()) @@ -157,8 +147,11 @@ impl FallibleStreamingIterator for CodesHandle { #[cfg(test)] mod tests { - use crate::codes_handle::{CodesHandle, KeyType, ProductKind}; - use anyhow::Result; + use crate::{ + codes_handle::{CodesHandle, ProductKind}, + KeyType, + }; + use anyhow::{Context, Ok, Result}; use fallible_streaming_iterator::FallibleStreamingIterator; use std::path::Path; @@ -166,15 +159,15 @@ mod tests { fn iterator_lifetimes() -> Result<()> { let file_path = Path::new("./data/iceland-levels.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let msg1 = handle.next()?.unwrap(); + let msg1 = handle.next()?.context("Message not some")?; let key1 = msg1.read_key("typeOfLevel")?; - let msg2 = handle.next()?.unwrap(); + let msg2 = handle.next()?.context("Message not some")?; let key2 = msg2.read_key("typeOfLevel")?; - let msg3 = handle.next()?.unwrap(); + let msg3 = handle.next()?.context("Message not some")?; let key3 = msg3.read_key("typeOfLevel")?; assert_eq!(key1.value, KeyType::Str("isobaricInhPa".to_string())); @@ -189,7 +182,7 @@ mod tests { let file_path = Path::new("./data/iceland-surface.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; while let Some(msg) = handle.next()? { let key = msg.read_key("shortName")?; @@ -207,7 +200,7 @@ mod tests { fn iterator_collected() -> Result<()> { let file_path = Path::new("./data/iceland-surface.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; let mut handle_collected = vec![]; @@ -216,7 +209,7 @@ mod tests { } for msg in handle_collected { - let key = msg.read_key("name").unwrap(); + let key = msg.read_key("name")?; match key.value { KeyType::Str(_) => {} _ => panic!("Incorrect variant of string key"), @@ -227,18 +220,37 @@ mod tests { } #[test] - fn iterator_return() { + fn iterator_return() -> Result<()> { let file_path = Path::new("./data/iceland-surface.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); - let current_message = handle.next().unwrap().unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + let current_message = handle.next()?.context("Message not some")?; assert!(!current_message.message_handle.is_null()); - assert!(current_message.iterator_flags.is_none()); - assert!(current_message.iterator_namespace.is_none()); - assert!(current_message.keys_iterator.is_none()); - assert!(!current_message.keys_iterator_next_item_exists); + + Ok(()) + } + + #[test] + fn iterator_beyond_none() -> Result<()> { + let file_path = Path::new("./data/iceland-surface.grib"); + let product_kind = ProductKind::GRIB; + + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + + assert!(handle.next()?.is_some()); + assert!(handle.next()?.is_some()); + assert!(handle.next()?.is_some()); + assert!(handle.next()?.is_some()); + assert!(handle.next()?.is_some()); + + assert!(handle.next()?.is_none()); + assert!(handle.next()?.is_none()); + assert!(handle.next()?.is_none()); + assert!(handle.next()?.is_none()); + + Ok(()) } #[test] @@ -246,7 +258,7 @@ mod tests { let file_path = Path::new("./data/iceland.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; // Use iterator to get a Keyed message with shortName "msl" and typeOfLevel "surface" // First, filter and collect the messages to get those that we want @@ -262,12 +274,12 @@ mod tests { // Now unwrap and access the first and only element of resulting vector // Find nearest modifies internal KeyedMessage fields so we need mutable reference - let level = &mut level[0]; + let level = &level[0]; println!("{:?}", level.read_key("shortName")); // Get the four nearest gridpoints of Reykjavik - let nearest_gridpoints = level.find_nearest(64.13, -21.89).unwrap(); + let nearest_gridpoints = level.codes_nearest()?.find_nearest(64.13, -21.89)?; // Print value and distance of the nearest gridpoint println!( diff --git a/src/codes_handle/keyed_message/mod.rs b/src/codes_handle/keyed_message/mod.rs deleted file mode 100644 index 460143b..0000000 --- a/src/codes_handle/keyed_message/mod.rs +++ /dev/null @@ -1,234 +0,0 @@ -mod iterator; -mod read; -mod write; - -use eccodes_sys::codes_nearest; -use log::warn; -use std::ptr::null_mut; - -use crate::{ - codes_handle::KeyedMessage, - errors::CodesError, - intermediate_bindings::{ - codes_get_message_copy, codes_grib_nearest_delete, codes_grib_nearest_find, - codes_grib_nearest_new, codes_handle_delete, codes_handle_new_from_message_copy, - codes_keys_iterator_delete, - }, -}; - -use super::{KeysIteratorFlags, NearestGridpoint}; - -impl KeyedMessage { - fn nearest_handle(&mut self) -> Result<*mut codes_nearest, CodesError> { - if let Some(nrst) = self.nearest_handle { - Ok(nrst) - } else { - let nrst; - - unsafe { - nrst = codes_grib_nearest_new(self.message_handle)?; - } - - self.nearest_handle = Some(nrst); - - Ok(nrst) - } - } - - ///Function to get four [`NearestGridpoint`]s of a point represented by requested coordinates. - /// - ///The inputs are latitude and longitude of requested point in respectively degrees north and - ///degreed east. - /// - ///In the output gridpoints, the value field refers to parameter held by the `KeyedMessage` - ///for which the function is called in adequate units, - ///coordinates are in degrees north/east, - ///and distance field represents the distance between requested point and output point in kilometers. - /// - ///### Example - /// - ///``` - ///# use eccodes::codes_handle::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags}; - ///# use std::path::Path; - ///# use eccodes::codes_handle::KeyType::Str; - ///# use eccodes::FallibleIterator; - ///let file_path = Path::new("./data/iceland.grib"); - ///let product_kind = ProductKind::GRIB; - /// - ///let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); - ///let mut msg = handle.next().unwrap().unwrap(); - /// - /// - ///let out = msg.find_nearest(64.13, -21.89).unwrap(); - ///``` - /// - ///### Errors - /// - ///This function returns [`CodesInternal`](crate::errors::CodesInternal) when - ///one of ecCodes function returns the non-zero code. - pub fn find_nearest( - &mut self, - lat: f64, - lon: f64, - ) -> Result<[NearestGridpoint; 4], CodesError> { - let nrst = self.nearest_handle()?; - let output_points; - - unsafe { - output_points = codes_grib_nearest_find(self.message_handle, nrst, lat, lon)?; - } - - Ok(output_points) - } -} - -impl Clone for KeyedMessage { - ///Custom function to clone the `KeyedMessage`. This function comes with memory overhead. - ///During clone iterator flags and namespace are not copied, and the iterator is reset. - fn clone(&self) -> KeyedMessage { - let new_handle; - let new_buffer; - - unsafe { - new_buffer = codes_get_message_copy(self.message_handle).expect( - "Getting message clone failed. - Please report this panic on Github", - ); - new_handle = codes_handle_new_from_message_copy(&new_buffer); - } - - KeyedMessage { - message_handle: new_handle, - iterator_flags: None, - iterator_namespace: None, - keys_iterator: None, - keys_iterator_next_item_exists: false, - nearest_handle: None, - } - } -} - -impl Drop for KeyedMessage { - ///Executes the destructor for this type. - ///This method calls `codes_handle_delete()`, `codes_keys_iterator_delete()` - ///`codes_grib_nearest_delete()` from ecCodes for graceful cleanup. - ///However in some edge cases ecCodes can return non-zero code. - ///In such case all pointers and file descriptors are safely deleted. - ///However memory leaks can still occur. - /// - ///If any function called in the destructor returns an error warning will appear in log. - ///If bugs occurs during `CodesHandle` drop please enable log output and post issue on [Github](https://github.com/ScaleWeather/eccodes). - /// - ///Technical note: delete functions in ecCodes can only fail with [`CodesInternalError`](crate::errors::CodesInternal::CodesInternalError) - ///when other functions corrupt the inner memory of pointer, in that case memory leak is possible. - ///In case of corrupt pointer segmentation fault will occur. - ///The pointers are cleared at the end of drop as they are not functional despite the result of delete functions. - fn drop(&mut self) { - if let Some(nrst) = self.nearest_handle { - unsafe { - codes_grib_nearest_delete(nrst).unwrap_or_else(|error| { - warn!( - "codes_grib_nearest_delete() returned an error: {:?}", - &error - ); - }); - } - } - - self.nearest_handle = Some(null_mut()); - - if let Some(kiter) = self.keys_iterator { - unsafe { - codes_keys_iterator_delete(kiter).unwrap_or_else(|error| { - warn!( - "codes_keys_iterator_delete() returned an error: {:?}", - &error - ); - }); - } - } - - self.keys_iterator = Some(null_mut()); - - unsafe { - codes_handle_delete(self.message_handle).unwrap_or_else(|error| { - warn!("codes_handle_delete() returned an error: {:?}", &error); - }); - } - - self.message_handle = null_mut(); - } -} - -#[cfg(test)] -mod tests { - use crate::codes_handle::{CodesHandle, ProductKind}; - use crate::{FallibleIterator, FallibleStreamingIterator}; - use anyhow::Result; - use std::path::Path; - use testing_logger; - - #[test] - fn key_clone() -> Result<()> { - let file_path = Path::new("./data/iceland.grib"); - let product_kind = ProductKind::GRIB; - - let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap(); - let cloned_message = current_message.clone(); - - assert_ne!( - current_message.message_handle, - cloned_message.message_handle - ); - assert!(cloned_message.iterator_flags.is_none()); - assert!(cloned_message.iterator_namespace.is_none()); - assert!(cloned_message.keys_iterator.is_none()); - assert!(!cloned_message.keys_iterator_next_item_exists); - - Ok(()) - } - - #[test] - fn message_drop() -> Result<()> { - testing_logger::setup(); - let file_path = Path::new("./data/iceland.grib"); - let product_kind = ProductKind::GRIB; - - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); - let mut current_message = handle.next()?.unwrap().clone(); - - let _key = current_message.next()?.unwrap(); - - drop(current_message); - - testing_logger::validate(|captured_logs| { - assert_eq!(captured_logs.len(), 0); - }); - - Ok(()) - } - - #[test] - fn find_nearest() -> Result<()> { - let file_path1 = Path::new("./data/iceland.grib"); - let file_path2 = Path::new("./data/iceland-surface.grib"); - let product_kind = ProductKind::GRIB; - - let mut handle1 = CodesHandle::new_from_file(file_path1, product_kind).unwrap(); - let msg1 = handle1.next()?.unwrap(); - let out1 = msg1.clone().find_nearest(64.13, -21.89).unwrap(); - - let mut handle2 = CodesHandle::new_from_file(file_path2, product_kind).unwrap(); - let msg2 = handle2.next()?.unwrap(); - let out2 = msg2.clone().find_nearest(64.13, -21.89).unwrap(); - - assert!(out1[0].value > 10000.0); - assert!(out2[3].index == 551); - assert!(out1[1].lat == 64.0); - assert!(out2[2].lon == -21.75); - assert!(out1[0].distance > 15.0); - - Ok(()) - } -} diff --git a/src/codes_handle/mod.rs b/src/codes_handle/mod.rs index df33089..ed7094a 100644 --- a/src/codes_handle/mod.rs +++ b/src/codes_handle/mod.rs @@ -2,10 +2,10 @@ //!and all associated functions and data structures #[cfg(feature = "experimental_index")] -use crate::{codes_index::CodesIndex, intermediate_bindings::codes_index::codes_index_delete}; -use crate::CodesError; +use crate::{codes_index::CodesIndex, intermediate_bindings::codes_index_delete}; +use crate::{pointer_guard, CodesError, KeyedMessage}; use bytes::Bytes; -use eccodes_sys::{codes_handle, codes_keys_iterator, codes_nearest, ProductKind_PRODUCT_GRIB}; +use eccodes_sys::ProductKind_PRODUCT_GRIB; use errno::errno; use libc::{c_char, c_void, size_t, FILE}; use log::warn; @@ -17,15 +17,7 @@ use std::{ ptr::null_mut, }; -use eccodes_sys::{ - CODES_KEYS_ITERATOR_ALL_KEYS, CODES_KEYS_ITERATOR_DUMP_ONLY, CODES_KEYS_ITERATOR_SKIP_CODED, - CODES_KEYS_ITERATOR_SKIP_COMPUTED, CODES_KEYS_ITERATOR_SKIP_DUPLICATES, - CODES_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC, CODES_KEYS_ITERATOR_SKIP_FUNCTION, - CODES_KEYS_ITERATOR_SKIP_OPTIONAL, CODES_KEYS_ITERATOR_SKIP_READ_ONLY, -}; - mod iterator; -mod keyed_message; #[derive(Debug)] #[doc(hidden)] @@ -44,76 +36,6 @@ pub struct CodesHandle { unsafe_message: KeyedMessage, } -///Structure used to access keys inside the GRIB file message. -///All data (including data values) contained by the file can only be accessed -///through the message and keys. -/// -///The structure implements `Clone` trait which comes with a memory overhead. -///You should take care that your system has enough memory before cloning `KeyedMessage`. -/// -///Keys inside the message can be accessed directly with [`read_key()`](KeyedMessage::read_key()) -///function or using [`FallibleIterator`](KeyedMessage#impl-FallibleIterator). -///The function [`find_nearest()`](KeyedMessage::find_nearest()) allows to get the values of four nearest gridpoints -///to requested coordinates. -///`FallibleIterator` parameters can be set with [`set_iterator_parameters()`](KeyedMessage::set_iterator_parameters()) -///to specify the subset of keys to iterate over. -#[derive(Hash, Debug)] -pub struct KeyedMessage { - message_handle: *mut codes_handle, - iterator_flags: Option, - iterator_namespace: Option, - keys_iterator: Option<*mut codes_keys_iterator>, - keys_iterator_next_item_exists: bool, - nearest_handle: Option<*mut codes_nearest>, -} - -///Structure representing a single key from the `KeyedMessage`. -#[derive(Clone, Debug, PartialEq)] -pub struct Key { - pub name: String, - pub value: KeyType, -} - -///Enum to represent and contain all possible types of keys inside `KeyedMessage`. -/// -///Messages inside GRIB files can contain arbitrary keys set by the file author. -///The type of a given key is only known at runtime (after being checked). -///There are several possible types of keys, which are represented by this enum -///and each variant contains the respective data type. -#[derive(Clone, Debug, PartialEq)] -pub enum KeyType { - Float(f64), - Int(i64), - FloatArray(Vec), - IntArray(Vec), - Str(String), - Bytes(Vec), -} - -///Flags to specify the subset of keys to iterate over -///by `FallibleIterator` in `KeyedMessage`. The flags can be used together. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub enum KeysIteratorFlags { - ///Iterate over all keys - AllKeys = CODES_KEYS_ITERATOR_ALL_KEYS as isize, - ///Iterate only dump keys - DumpOnly = CODES_KEYS_ITERATOR_DUMP_ONLY as isize, - ///Exclude coded keys from iteration - SkipCoded = CODES_KEYS_ITERATOR_SKIP_CODED as isize, - ///Exclude computed keys from iteration - SkipComputed = CODES_KEYS_ITERATOR_SKIP_COMPUTED as isize, - ///Exclude function keys from iteration - SkipFunction = CODES_KEYS_ITERATOR_SKIP_FUNCTION as isize, - ///Exclude optional keys from iteration - SkipOptional = CODES_KEYS_ITERATOR_SKIP_OPTIONAL as isize, - ///Exclude read-only keys from iteration - SkipReadOnly = CODES_KEYS_ITERATOR_SKIP_READ_ONLY as isize, - ///Exclude duplicate keys from iteration - SkipDuplicates = CODES_KEYS_ITERATOR_SKIP_DUPLICATES as isize, - ///Exclude file edition specific keys from iteration - SkipEditionSpecific = CODES_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC as isize, -} - #[derive(Debug)] enum DataContainer { FileBytes(Bytes), @@ -129,22 +51,6 @@ pub enum ProductKind { GRIB = ProductKind_PRODUCT_GRIB as isize, } -///The structure returned by [`KeyedMessage::find_nearest()`]. -///Should always be analysed in relation to the coordinates request in `find_nearest()`. -#[derive(Copy, Clone, PartialEq, Debug, Default)] -pub struct NearestGridpoint { - ///Index of gridpoint - pub index: i32, - ///Latitude in degrees north - pub lat: f64, - ///Longitude in degrees east - pub lon: f64, - ///Distance from coordinates requested in `find_nearest()` - pub distance: f64, - ///Value of the filed at given coordinate - pub value: f64, -} - impl CodesHandle { ///The constructor that takes a [`path`](Path) to an existing file and ///a requested [`ProductKind`] and returns the [`CodesHandle`] object. @@ -195,11 +101,6 @@ impl CodesHandle { product_kind, unsafe_message: KeyedMessage { message_handle: null_mut(), - iterator_flags: None, - iterator_namespace: None, - keys_iterator: None, - keys_iterator_next_item_exists: false, - nearest_handle: None, }, }) } @@ -247,7 +148,7 @@ impl CodesHandle { product_kind: ProductKind, ) -> Result { let file_pointer = open_with_fmemopen(&file_data)?; - + Ok(CodesHandle { _data: (DataContainer::FileBytes(file_data)), source: GribFile { @@ -256,11 +157,6 @@ impl CodesHandle { product_kind, unsafe_message: KeyedMessage { message_handle: null_mut(), - iterator_flags: None, - iterator_namespace: None, - keys_iterator: None, - keys_iterator_next_item_exists: false, - nearest_handle: None, }, }) } @@ -273,18 +169,12 @@ impl CodesHandle { index: CodesIndex, product_kind: ProductKind, ) -> Result { - let new_handle = CodesHandle { _data: DataContainer::Empty(), //unused, index owns data source: index, product_kind, unsafe_message: KeyedMessage { message_handle: null_mut(), - iterator_flags: None, - iterator_namespace: None, - keys_iterator: None, - keys_iterator_next_item_exists: false, - nearest_handle: None, }, }; @@ -293,10 +183,7 @@ impl CodesHandle { } fn open_with_fdopen(file: &File) -> Result<*mut FILE, CodesError> { - let file_ptr; - unsafe { - file_ptr = libc::fdopen(file.as_raw_fd(), "r".as_ptr().cast::()); - } + let file_ptr = unsafe { libc::fdopen(file.as_raw_fd(), "r".as_ptr().cast::()) }; if file_ptr.is_null() { let error_val = errno(); @@ -308,10 +195,13 @@ fn open_with_fdopen(file: &File) -> Result<*mut FILE, CodesError> { } fn open_with_fmemopen(file_data: &Bytes) -> Result<*mut FILE, CodesError> { + let file_data_ptr = file_data.as_ptr() as *mut c_void; + pointer_guard::non_null!(file_data_ptr); + let file_ptr; unsafe { file_ptr = libc::fmemopen( - file_data.as_ptr() as *mut c_void, + file_data_ptr, file_data.len() as size_t, "r".as_ptr().cast::(), ); @@ -388,6 +278,7 @@ impl Drop for CodesHandle { #[cfg(test)] mod tests { + use anyhow::Result; use eccodes_sys::ProductKind_PRODUCT_GRIB; use crate::codes_handle::{CodesHandle, DataContainer, ProductKind}; @@ -397,37 +288,35 @@ mod tests { use std::path::Path; #[test] - fn file_constructor() { + fn file_constructor() -> Result<()> { let file_path = Path::new("./data/iceland.grib"); let product_kind = ProductKind::GRIB; - let handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let handle = CodesHandle::new_from_file(file_path, product_kind)?; assert!(!handle.source.pointer.is_null()); assert!(handle.unsafe_message.message_handle.is_null()); assert_eq!(handle.product_kind as u32, { ProductKind_PRODUCT_GRIB }); - let metadata = match &handle._data { - DataContainer::FileBuffer(file) => file.metadata().unwrap(), + match &handle._data { + DataContainer::FileBuffer(file) => file.metadata()?, _ => panic!(), }; - println!("{:?}", metadata); + Ok(()) } #[tokio::test] - async fn memory_constructor() { + async fn memory_constructor() -> Result<()> { let product_kind = ProductKind::GRIB; let file_data = reqwest::get( "https://github.com/ScaleWeather/eccodes/blob/main/data/iceland.grib?raw=true", ) - .await - .unwrap() + .await? .bytes() - .await - .unwrap(); + .await?; - let handle = CodesHandle::new_from_memory(file_data, product_kind).unwrap(); + let handle = CodesHandle::new_from_memory(file_data, product_kind)?; assert!(!handle.source.pointer.is_null()); assert!(handle.unsafe_message.message_handle.is_null()); assert_eq!(handle.product_kind as u32, { ProductKind_PRODUCT_GRIB }); @@ -436,40 +325,41 @@ mod tests { DataContainer::FileBytes(file) => assert!(!file.is_empty()), _ => panic!(), }; + + Ok(()) } #[test] #[cfg(feature = "experimental_index")] - fn index_constructor_and_destructor() { + fn index_constructor_and_destructor() -> Result<()> { + use anyhow::Ok; + let file_path = Path::new("./data/iceland-surface.idx"); - let index = CodesIndex::read_from_file(file_path) - .unwrap() - .select("shortName", "2t") - .unwrap() - .select("typeOfLevel", "surface") - .unwrap() - .select("level", 0) - .unwrap() - .select("stepType", "instant") - .unwrap(); + let index = CodesIndex::read_from_file(file_path)? + .select("shortName", "2t")? + .select("typeOfLevel", "surface")? + .select("level", 0)? + .select("stepType", "instant")?; let i_ptr = index.pointer.clone(); - let handle = CodesHandle::new_from_index(index, ProductKind::GRIB).unwrap(); + let handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; assert_eq!(handle.source.pointer, i_ptr); assert!(handle.unsafe_message.message_handle.is_null()); + + Ok(()) } #[tokio::test] - async fn codes_handle_drop() { + async fn codes_handle_drop() -> Result<()> { testing_logger::setup(); { let file_path = Path::new("./data/iceland-surface.grib"); let product_kind = ProductKind::GRIB; - let handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let handle = CodesHandle::new_from_file(file_path, product_kind)?; drop(handle); testing_logger::validate(|captured_logs| { @@ -482,13 +372,11 @@ mod tests { let file_data = reqwest::get( "https://github.com/ScaleWeather/eccodes/blob/main/data/iceland.grib?raw=true", ) - .await - .unwrap() + .await? .bytes() - .await - .unwrap(); + .await?; - let handle = CodesHandle::new_from_memory(file_data, product_kind).unwrap(); + let handle = CodesHandle::new_from_memory(file_data, product_kind)?; drop(handle); //logs from Reqwest are expected @@ -499,5 +387,7 @@ mod tests { } }); } + + Ok(()) } } diff --git a/src/codes_index/mod.rs b/src/codes_index.rs similarity index 82% rename from src/codes_index/mod.rs rename to src/codes_index.rs index 1b79cb1..3e1fa97 100644 --- a/src/codes_index/mod.rs +++ b/src/codes_index.rs @@ -4,7 +4,7 @@ use crate::{ codes_handle::SpecialDrop, errors::CodesError, - intermediate_bindings::codes_index::{ + intermediate_bindings::{ codes_index_add_file, codes_index_new, codes_index_read, codes_index_select_double, codes_index_select_long, codes_index_select_string, }, @@ -107,54 +107,56 @@ impl Drop for CodesIndex { #[cfg(test)] mod tests { + use anyhow::Result; + use crate::codes_index::{CodesIndex, Select}; use std::path::Path; #[test] - fn index_constructors() { + fn index_constructors() -> Result<()> { { let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; - let index = CodesIndex::new_from_keys(&keys).unwrap(); + let index = CodesIndex::new_from_keys(&keys)?; assert!(!index.pointer.is_null()); } { let file_path = Path::new("./data/iceland-surface.idx"); - let index = CodesIndex::read_from_file(file_path).unwrap(); + let index = CodesIndex::read_from_file(file_path)?; assert!(!index.pointer.is_null()); } + + Ok(()) } #[test] - fn index_destructor() { + fn index_destructor() -> Result<()> { let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; - let index = CodesIndex::new_from_keys(&keys).unwrap(); + let index = CodesIndex::new_from_keys(&keys)?; - drop(index) + drop(index); + Ok(()) } #[test] - fn add_file() { + fn add_file() -> Result<()> { let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; - let index = CodesIndex::new_from_keys(&keys).unwrap(); + let index = CodesIndex::new_from_keys(&keys)?; let grib_path = Path::new("./data/iceland.grib"); - let index = index.add_grib_file(grib_path).unwrap(); + let index = index.add_grib_file(grib_path)?; assert!(!index.pointer.is_null()); + Ok(()) } #[test] - fn index_selection() { + fn index_selection() -> Result<()> { let file_path = Path::new("./data/iceland-surface.idx"); - let index = CodesIndex::read_from_file(file_path) - .unwrap() - .select("shortName", "2t") - .unwrap() - .select("typeOfLevel", "surface") - .unwrap() - .select("level", 0) - .unwrap() - .select("stepType", "instant") - .unwrap(); + let index = CodesIndex::read_from_file(file_path)? + .select("shortName", "2t")? + .select("typeOfLevel", "surface")? + .select("level", 0)? + .select("stepType", "instant")?; assert!(!index.pointer.is_null()); + Ok(()) } } diff --git a/src/codes_nearest.rs b/src/codes_nearest.rs new file mode 100644 index 0000000..5f9c4c2 --- /dev/null +++ b/src/codes_nearest.rs @@ -0,0 +1,142 @@ +use std::ptr::null_mut; + +use eccodes_sys::codes_nearest; +use log::warn; + +use crate::{ + intermediate_bindings::{ + codes_grib_nearest_delete, codes_grib_nearest_find, codes_grib_nearest_new, + }, + CodesError, KeyedMessage, +}; + +#[derive(Debug)] +pub struct CodesNearest<'a> { + nearest_handle: *mut codes_nearest, + parent_message: &'a KeyedMessage, +} + +///The structure returned by [`KeyedMessage::find_nearest()`]. +///Should always be analysed in relation to the coordinates request in `find_nearest()`. +#[derive(Copy, Clone, PartialEq, Debug, Default)] +pub struct NearestGridpoint { + ///Index of gridpoint + pub index: i32, + ///Latitude in degrees north + pub lat: f64, + ///Longitude in degrees east + pub lon: f64, + ///Distance from coordinates requested in `find_nearest()` + pub distance: f64, + ///Value of the filed at given coordinate + pub value: f64, +} + +impl KeyedMessage { + pub fn codes_nearest(&self) -> Result { + let nearest_handle = unsafe { codes_grib_nearest_new(self.message_handle)? }; + + Ok(CodesNearest { + nearest_handle, + parent_message: self, + }) + } +} + +impl CodesNearest<'_> { + ///Function to get four [`NearestGridpoint`]s of a point represented by requested coordinates. + /// + ///The inputs are latitude and longitude of requested point in respectively degrees north and + ///degreed east. + /// + ///In the output gridpoints, the value field refers to parameter held by the `KeyedMessage` + ///for which the function is called in adequate units, + ///coordinates are in degrees north/east, + ///and distance field represents the distance between requested point and output point in kilometers. + /// + ///### Example + /// + ///``` + ///# use eccodes::codes_handle::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags}; + ///# use std::path::Path; + ///# use eccodes::codes_handle::KeyType::Str; + ///# use eccodes::FallibleIterator; + ///let file_path = Path::new("./data/iceland.grib"); + ///let product_kind = ProductKind::GRIB; + /// + ///let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + ///let mut msg = handle.next().unwrap().unwrap(); + /// + /// + ///let out = msg.find_nearest(64.13, -21.89).unwrap(); + ///``` + /// + ///### Errors + /// + ///This function returns [`CodesInternal`](crate::errors::CodesInternal) when + ///one of ecCodes function returns the non-zero code. + pub fn find_nearest(&self, lat: f64, lon: f64) -> Result<[NearestGridpoint; 4], CodesError> { + let output_points; + + unsafe { + output_points = codes_grib_nearest_find( + self.parent_message.message_handle, + self.nearest_handle, + lat, + lon, + )?; + } + + Ok(output_points) + } +} + +impl Drop for CodesNearest<'_> { + fn drop(&mut self) { + unsafe { + codes_grib_nearest_delete(self.nearest_handle).unwrap_or_else(|error| { + warn!( + "codes_grib_nearest_delete() returned an error: {:?}", + &error + ); + }); + } + + self.nearest_handle = null_mut(); + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use anyhow::{Context, Result}; + use fallible_streaming_iterator::FallibleStreamingIterator; + + use crate::{CodesHandle, ProductKind}; + + #[test] + fn find_nearest() -> Result<()> { + let file_path1 = Path::new("./data/iceland.grib"); + let file_path2 = Path::new("./data/iceland-surface.grib"); + let product_kind = ProductKind::GRIB; + + let mut handle1 = CodesHandle::new_from_file(file_path1, product_kind)?; + let msg1 = handle1.next()?.context("Message not some")?; + let nrst1 = msg1.codes_nearest()?; + let out1 = nrst1.find_nearest(64.13, -21.89)?; + + let mut handle2 = CodesHandle::new_from_file(file_path2, product_kind)?; + let msg2 = handle2.next()?.context("Message not some")?; + let nrst2 = msg2.codes_nearest()?; + let out2 = nrst2.find_nearest(64.13, -21.89)?; + + assert!(out1[0].value > 10000.0); + assert!(out2[3].index == 551); + assert!(out1[1].lat == 64.0); + assert!(out2[2].lon == -21.75); + assert!(out1[0].distance > 15.0); + + Ok(()) + } +} diff --git a/src/errors.rs b/src/errors.rs index 27b39e9..157cf30 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -47,6 +47,24 @@ pub enum CodesError { ///Similar to [`CodesInternal::CodesNotFound`] and [`CodesInternal::CodesMissingKey`]. #[error("The key is missing in present message")] MissingKey, + + /// Returned when codes_handle_clone returns null pointer + /// indicating issues with cloning the message. + #[error("Cannot clone the message")] + CloneFailed, + + /// Returned when codes_keys_iterator_new returns null pointer + #[error("Cannot create or manipulate keys iterator")] + KeysIteratorFailed, + + /// This error can be returned by almost any function in the crate. + /// It is returned when null pointer was passed to ecCodes function + /// which cannot handle null pointers. This error may indicate both + /// bug in the implementation or incorrect usage of the crate. + /// This error could be a panic, but as the crate is not comprehensively tested + /// it cannot be guaranteed that the null pointer is not caused by the user's mistake. + #[error("Null pointer encountered where it should not be")] + NullPtr, } #[derive(Copy, Eq, PartialEq, Clone, Ord, PartialOrd, Hash, Error, Debug, FromPrimitive)] diff --git a/src/intermediate_bindings/codes_get.rs b/src/intermediate_bindings/codes_get.rs new file mode 100644 index 0000000..cebe1a9 --- /dev/null +++ b/src/intermediate_bindings/codes_get.rs @@ -0,0 +1,248 @@ +use std::ffi::{CStr, CString}; + +use eccodes_sys::codes_handle; +use libc::c_void; +use num_traits::FromPrimitive; + +use crate::{ + errors::{CodesError, CodesInternal}, + pointer_guard, +}; + +use super::NativeKeyType; + +pub unsafe fn codes_get_native_type( + handle: *mut codes_handle, + key: &str, +) -> Result { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut key_type: i32 = 0; + + let error_code = eccodes_sys::codes_get_native_type(handle, key.as_ptr(), &mut key_type); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(FromPrimitive::from_i32(key_type).unwrap()) +} + +pub unsafe fn codes_get_size(handle: *mut codes_handle, key: &str) -> Result { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut key_size: usize = 0; + + let error_code = eccodes_sys::codes_get_size(handle, key.as_ptr(), &mut key_size); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(key_size) +} + +pub unsafe fn codes_get_long(handle: *mut codes_handle, key: &str) -> Result { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut key_value: i64 = 0; + + let error_code = eccodes_sys::codes_get_long(handle, key.as_ptr(), &mut key_value); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(key_value) +} + +pub unsafe fn codes_get_double(handle: *mut codes_handle, key: &str) -> Result { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut key_value: f64 = 0.0; + + let error_code = eccodes_sys::codes_get_double(handle, key.as_ptr(), &mut key_value); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(key_value) +} + +pub unsafe fn codes_get_double_array( + handle: *mut codes_handle, + key: &str, +) -> Result, CodesError> { + pointer_guard::non_null!(handle); + + let mut key_size = codes_get_size(handle, key)?; + let key = CString::new(key).unwrap(); + + let mut key_values: Vec = vec![0.0; key_size]; + + let error_code = eccodes_sys::codes_get_double_array( + handle, + key.as_ptr(), + key_values.as_mut_ptr().cast::(), + &mut key_size, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(key_values) +} + +pub unsafe fn codes_get_long_array( + handle: *mut codes_handle, + key: &str, +) -> Result, CodesError> { + pointer_guard::non_null!(handle); + + let mut key_size = codes_get_size(handle, key)?; + let key = CString::new(key).unwrap(); + + let mut key_values: Vec = vec![0; key_size]; + + let error_code = eccodes_sys::codes_get_long_array( + handle, + key.as_ptr(), + key_values.as_mut_ptr().cast::(), + &mut key_size, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(key_values) +} + +pub unsafe fn codes_get_length(handle: *mut codes_handle, key: &str) -> Result { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut key_length: usize = 0; + + let error_code = eccodes_sys::codes_get_length(handle, key.as_ptr(), &mut key_length); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(key_length) +} + +pub unsafe fn codes_get_string(handle: *mut codes_handle, key: &str) -> Result { + pointer_guard::non_null!(handle); + + let mut key_length = codes_get_length(handle, key)?; + let key = CString::new(key).unwrap(); + + let mut key_message: Vec = vec![0; key_length]; + + let error_code = eccodes_sys::codes_get_string( + handle, + key.as_ptr(), + key_message.as_mut_ptr().cast::(), + &mut key_length, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + key_message.truncate(key_length); + let key_message_result = CStr::from_bytes_with_nul(key_message.as_ref()); + + let key_message_cstr = if let Ok(msg) = key_message_result { + msg + } else { + key_message.push(0); + CStr::from_bytes_with_nul(key_message.as_ref())? + }; + + let key_message_string = key_message_cstr.to_str()?.to_string(); + + Ok(key_message_string) +} + +pub unsafe fn codes_get_bytes(handle: *mut codes_handle, key: &str) -> Result, CodesError> { + pointer_guard::non_null!(handle); + + let mut key_size = codes_get_length(handle, key)?; + let key = CString::new(key).unwrap(); + + let mut buffer: Vec = vec![0; key_size]; + + let error_code = eccodes_sys::codes_get_bytes( + handle, + key.as_ptr(), + buffer.as_mut_ptr().cast::(), + &mut key_size, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(buffer) +} + +pub unsafe fn codes_get_message_size(handle: *mut codes_handle) -> Result { + pointer_guard::non_null!(handle); + + let mut size: usize = 0; + + let error_code = eccodes_sys::codes_get_message_size(handle, &mut size); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(size) +} + +pub unsafe fn codes_get_message( + handle: *mut codes_handle, +) -> Result<(*const c_void, usize), CodesError> { + pointer_guard::non_null!(handle); + + let buffer_size = codes_get_message_size(handle)?; + + let buffer: Vec = vec![0; buffer_size]; + let mut buffer_ptr = buffer.as_ptr().cast::(); + + let mut message_size: usize = 0; + + let error_code = eccodes_sys::codes_get_message(handle, &mut buffer_ptr, &mut message_size); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + assert!( + buffer_size == message_size, + "Buffer and message sizes ar not equal in codes_get_message! + Please report this panic on Github." + ); + + Ok((buffer_ptr, message_size)) +} diff --git a/src/intermediate_bindings/codes_handle.rs b/src/intermediate_bindings/codes_handle.rs new file mode 100644 index 0000000..f8c206f --- /dev/null +++ b/src/intermediate_bindings/codes_handle.rs @@ -0,0 +1,94 @@ +use std::ptr::{self}; + +use eccodes_sys::{codes_context, codes_handle, codes_index, CODES_LOCK}; +use libc::FILE; +use num_traits::FromPrimitive; + +use crate::{ + codes_handle::ProductKind, + errors::{CodesError, CodesInternal}, + pointer_guard, +}; + +#[cfg(target_os = "macos")] +type _SYS_IO_FILE = eccodes_sys::__sFILE; + +#[cfg(not(target_os = "macos"))] +type _SYS_IO_FILE = eccodes_sys::_IO_FILE; + +pub unsafe fn codes_handle_new_from_file( + file_pointer: *mut FILE, + product_kind: ProductKind, +) -> Result<*mut codes_handle, CodesError> { + pointer_guard::non_null!(file_pointer); + + let context: *mut codes_context = ptr::null_mut(); //default context + + let mut error_code: i32 = 0; + + let file_handle = eccodes_sys::codes_handle_new_from_file( + context, + file_pointer.cast::<_SYS_IO_FILE>(), + product_kind as u32, + &mut error_code, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(file_handle) +} + +pub unsafe fn codes_handle_delete(handle: *mut codes_handle) -> Result<(), CodesError> { + if handle.is_null() { + return Ok(()); + } + + let error_code = eccodes_sys::codes_handle_delete(handle); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_handle_new_from_index( + index: *mut codes_index, +) -> Result<*mut codes_handle, CodesError> { + pointer_guard::non_null!(index); + + let mut error_code: i32 = 0; + + let _g = CODES_LOCK.lock().unwrap(); + let codes_handle = eccodes_sys::codes_handle_new_from_index(index, &mut error_code); + + // special case! codes_handle_new_from_index returns -43 when there are no messages left in the index + // this is also indicated by a null pointer, which is handled upstream + if error_code == -43 { + return Ok(codes_handle); + } + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + Ok(codes_handle) +} + +pub unsafe fn codes_handle_clone( + source_handle: *mut codes_handle, +) -> Result<*mut codes_handle, CodesError> { + pointer_guard::non_null!(source_handle); + + let clone_handle = unsafe { eccodes_sys::codes_handle_clone(source_handle) }; + + if clone_handle.is_null() { + return Err(CodesError::CloneFailed); + } + + Ok(clone_handle) +} diff --git a/src/intermediate_bindings/codes_index.rs b/src/intermediate_bindings/codes_index.rs index c285810..bc899bd 100644 --- a/src/intermediate_bindings/codes_index.rs +++ b/src/intermediate_bindings/codes_index.rs @@ -1,19 +1,14 @@ #![allow(non_camel_case_types)] #![allow(clippy::module_name_repetitions)] -use eccodes_sys::{codes_index, CODES_LOCK}; -use std::{ffi::CString, ptr}; - -#[cfg(target_os = "macos")] -type _SYS_IO_FILE = eccodes_sys::__sFILE; - -#[cfg(not(target_os = "macos"))] -type _SYS_IO_FILE = eccodes_sys::_IO_FILE; - -use eccodes_sys::{codes_context, codes_handle}; +use eccodes_sys::{codes_context, codes_index, CODES_LOCK}; use num_traits::FromPrimitive; +use std::{ffi::CString, ptr}; -use crate::errors::{CodesError, CodesInternal}; +use crate::{ + errors::{CodesError, CodesInternal}, + pointer_guard, +}; // all index functions are safeguarded by a lock // because there are random errors appearing when using the index functions concurrently @@ -61,6 +56,8 @@ pub unsafe fn codes_index_add_file( index: *mut codes_index, filename: &str, ) -> Result<(), CodesError> { + pointer_guard::non_null!(index); + let filename = CString::new(filename).unwrap(); let _g = CODES_LOCK.lock().unwrap(); @@ -78,6 +75,8 @@ pub unsafe fn codes_index_select_long( key: &str, value: i64, ) -> Result<(), CodesError> { + pointer_guard::non_null!(index); + let key = CString::new(key).unwrap(); let _g = CODES_LOCK.lock().unwrap(); @@ -95,6 +94,8 @@ pub unsafe fn codes_index_select_double( key: &str, value: f64, ) -> Result<(), CodesError> { + pointer_guard::non_null!(index); + let key = CString::new(key).unwrap(); let _g = CODES_LOCK.lock().unwrap(); @@ -112,6 +113,8 @@ pub unsafe fn codes_index_select_string( key: &str, value: &str, ) -> Result<(), CodesError> { + pointer_guard::non_null!(index); + let key = CString::new(key).unwrap(); let value = CString::new(value).unwrap(); @@ -124,24 +127,3 @@ pub unsafe fn codes_index_select_string( } Ok(()) } - -pub unsafe fn codes_handle_new_from_index( - index: *mut codes_index, -) -> Result<*mut codes_handle, CodesError> { - let mut error_code: i32 = 0; - - let _g = CODES_LOCK.lock().unwrap(); - let codes_handle = eccodes_sys::codes_handle_new_from_index(index, &mut error_code); - - // special case! codes_handle_new_from_index returns -43 when there are no messages left in the index - // this is also indicated by a null pointer, which is handled upstream - if error_code == -43 { - return Ok(codes_handle); - } - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - Ok(codes_handle) -} diff --git a/src/intermediate_bindings/codes_keys.rs b/src/intermediate_bindings/codes_keys.rs new file mode 100644 index 0000000..c7c622f --- /dev/null +++ b/src/intermediate_bindings/codes_keys.rs @@ -0,0 +1,69 @@ +use std::ffi::{CStr, CString}; + +use eccodes_sys::{codes_handle, codes_keys_iterator}; + +use num_traits::FromPrimitive; + +use crate::{ + errors::{CodesError, CodesInternal}, + pointer_guard, +}; + +pub unsafe fn codes_keys_iterator_new( + handle: *mut codes_handle, + flags: u32, + namespace: &str, +) -> Result<*mut codes_keys_iterator, CodesError> { + pointer_guard::non_null!(handle); + + let namespace = CString::new(namespace).unwrap(); + + let kiter = eccodes_sys::codes_keys_iterator_new(handle, u64::from(flags), namespace.as_ptr()); + + if kiter.is_null() { + return Err(CodesError::KeysIteratorFailed); + } + + Ok(kiter) +} + +pub unsafe fn codes_keys_iterator_delete( + keys_iterator: *mut codes_keys_iterator, +) -> Result<(), CodesError> { + if keys_iterator.is_null() { + return Ok(()); + } + + let error_code = eccodes_sys::codes_keys_iterator_delete(keys_iterator); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_keys_iterator_next( + keys_iterator: *mut codes_keys_iterator, +) -> Result { + pointer_guard::non_null!(keys_iterator); + + let next_item_exists = eccodes_sys::codes_keys_iterator_next(keys_iterator); + + Ok(next_item_exists == 1) +} + +pub unsafe fn codes_keys_iterator_get_name( + keys_iterator: *mut codes_keys_iterator, +) -> Result { + pointer_guard::non_null!(keys_iterator); + + let name_pointer = eccodes_sys::codes_keys_iterator_get_name(keys_iterator); + + let name_c_str = CStr::from_ptr(name_pointer); + let name_str = name_c_str.to_str()?; + let name_string = name_str.to_owned(); + + Ok(name_string) +} diff --git a/src/intermediate_bindings/codes_set.rs b/src/intermediate_bindings/codes_set.rs new file mode 100644 index 0000000..4a023f8 --- /dev/null +++ b/src/intermediate_bindings/codes_set.rs @@ -0,0 +1,148 @@ +use std::ffi::CString; + +use eccodes_sys::codes_handle; + +use num_traits::FromPrimitive; + +use crate::{ + errors::{CodesError, CodesInternal}, + pointer_guard, +}; + +pub unsafe fn codes_set_long( + handle: *mut codes_handle, + key: &str, + value: i64, +) -> Result<(), CodesError> { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + + let error_code = eccodes_sys::codes_set_long(handle, key.as_ptr(), value); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_set_double( + handle: *mut codes_handle, + key: &str, + value: f64, +) -> Result<(), CodesError> { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + + let error_code = eccodes_sys::codes_set_double(handle, key.as_ptr(), value); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_set_long_array( + handle: *mut codes_handle, + key: &str, + values: &[i64], +) -> Result<(), CodesError> { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + + let length = values.len(); + + let error_code = eccodes_sys::codes_set_long_array( + handle, + key.as_ptr(), + values.as_ptr().cast::(), + length, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_set_double_array( + handle: *mut codes_handle, + key: &str, + values: &[f64], +) -> Result<(), CodesError> { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + + let length = values.len(); + + let error_code = eccodes_sys::codes_set_double_array( + handle, + key.as_ptr(), + values.as_ptr().cast::(), + length, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_set_string( + handle: *mut codes_handle, + key: &str, + value: &str, +) -> Result<(), CodesError> { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut length = value.len(); + let value = CString::new(value).unwrap(); + + let error_code = + eccodes_sys::codes_set_string(handle, key.as_ptr(), value.as_ptr(), &mut length); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_set_bytes( + handle: *mut codes_handle, + key: &str, + values: &[u8], +) -> Result<(), CodesError> { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + + let mut length = values.len(); + + let error_code = eccodes_sys::codes_set_bytes( + handle, + key.as_ptr(), + values.as_ptr().cast::(), + &mut length, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} diff --git a/src/intermediate_bindings/grib_nearest.rs b/src/intermediate_bindings/grib_nearest.rs new file mode 100644 index 0000000..1b2f32e --- /dev/null +++ b/src/intermediate_bindings/grib_nearest.rs @@ -0,0 +1,95 @@ +use std::ptr::addr_of_mut; + +use eccodes_sys::{codes_handle, codes_nearest, CODES_NEAREST_SAME_DATA, CODES_NEAREST_SAME_GRID}; + +use num_traits::FromPrimitive; + +use crate::{ + errors::{CodesError, CodesInternal}, + pointer_guard, NearestGridpoint, +}; + +pub unsafe fn codes_grib_nearest_new( + handle: *mut codes_handle, +) -> Result<*mut codes_nearest, CodesError> { + pointer_guard::non_null!(handle); + + let mut error_code: i32 = 0; + + let nearest = eccodes_sys::codes_grib_nearest_new(handle, &mut error_code); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(nearest) +} + +pub unsafe fn codes_grib_nearest_delete(nearest: *mut codes_nearest) -> Result<(), CodesError> { + if nearest.is_null() { + return Ok(()); + } + + let error_code = eccodes_sys::codes_grib_nearest_delete(nearest); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + Ok(()) +} + +pub unsafe fn codes_grib_nearest_find( + handle: *mut codes_handle, + nearest: *mut codes_nearest, + lat: f64, + lon: f64, +) -> Result<[NearestGridpoint; 4], CodesError> { + pointer_guard::non_null!(handle); + pointer_guard::non_null!(nearest); + + // such flags are set because find nearest for given nearest is always + // called on the same grib message + let flags = CODES_NEAREST_SAME_GRID + CODES_NEAREST_SAME_DATA; + + let mut output_lats = [0_f64; 4]; + let mut output_lons = [0_f64; 4]; + let mut output_values = [0_f64; 4]; + let mut output_distances = [0_f64; 4]; + let mut output_indexes = [0_i32; 4]; + + let mut length: usize = 4; + + let error_code = eccodes_sys::codes_grib_nearest_find( + nearest, + handle, + lat, + lon, + u64::from(flags), + addr_of_mut!(output_lats[0]), + addr_of_mut!(output_lons[0]), + addr_of_mut!(output_values[0]), + addr_of_mut!(output_distances[0]), + addr_of_mut!(output_indexes[0]), + &mut length, + ); + + if error_code != 0 { + let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); + return Err(err.into()); + } + + let mut output = [NearestGridpoint::default(); 4]; + + for i in 0..4 { + output[i].lat = output_lats[i]; + output[i].lon = output_lons[i]; + output[i].distance = output_distances[i]; + output[i].index = output_indexes[i]; + output[i].value = output_values[i]; + } + + Ok(output) +} diff --git a/src/intermediate_bindings/mod.rs b/src/intermediate_bindings/mod.rs index cf7f521..23f49e2 100644 --- a/src/intermediate_bindings/mod.rs +++ b/src/intermediate_bindings/mod.rs @@ -7,574 +7,47 @@ //!to make ecCodes usage safer and easier, //!but they are unsafe as they operate on raw `codes_handle`. +mod codes_get; +mod codes_handle; #[cfg(feature = "experimental_index")] -pub mod codes_index; +mod codes_index; +mod codes_keys; +mod codes_set; +mod grib_nearest; -use std::{ - ffi::{CStr, CString}, - ptr::{self, addr_of_mut}, +#[derive(Copy, Eq, PartialEq, Clone, Ord, PartialOrd, Hash, Debug, num_derive::FromPrimitive)] +pub enum NativeKeyType { + Undefined = eccodes_sys::CODES_TYPE_UNDEFINED as isize, + Long = eccodes_sys::CODES_TYPE_LONG as isize, + Double = eccodes_sys::CODES_TYPE_DOUBLE as isize, + Str = eccodes_sys::CODES_TYPE_STRING as isize, + Bytes = eccodes_sys::CODES_TYPE_BYTES as isize, + Section = eccodes_sys::CODES_TYPE_SECTION as isize, + Label = eccodes_sys::CODES_TYPE_LABEL as isize, + Missing = eccodes_sys::CODES_TYPE_MISSING as isize, +} + +pub use codes_get::{ + codes_get_bytes, codes_get_double, codes_get_double_array, codes_get_long, + codes_get_long_array, codes_get_message, codes_get_native_type, codes_get_size, + codes_get_string, }; - -#[cfg(target_os = "macos")] -type _SYS_IO_FILE = eccodes_sys::__sFILE; - -#[cfg(not(target_os = "macos"))] -type _SYS_IO_FILE = eccodes_sys::_IO_FILE; - -use eccodes_sys::{ - codes_context, codes_handle, codes_keys_iterator, codes_nearest, CODES_NEAREST_SAME_DATA, - CODES_NEAREST_SAME_GRID, CODES_TYPE_BYTES, CODES_TYPE_DOUBLE, CODES_TYPE_LABEL, - CODES_TYPE_LONG, CODES_TYPE_MISSING, CODES_TYPE_SECTION, CODES_TYPE_STRING, - CODES_TYPE_UNDEFINED, +pub use codes_handle::{ + codes_handle_clone, codes_handle_delete, codes_handle_new_from_file, + codes_handle_new_from_index, }; -use libc::{c_void, FILE}; -use num_traits::FromPrimitive; - -use crate::{ - codes_handle::{NearestGridpoint, ProductKind}, - errors::{CodesError, CodesInternal}, +pub use codes_index::{ + codes_index_add_file, codes_index_delete, codes_index_new, codes_index_read, + codes_index_select_double, codes_index_select_long, codes_index_select_string, +}; +pub use codes_keys::{ + codes_keys_iterator_delete, codes_keys_iterator_get_name, codes_keys_iterator_new, + codes_keys_iterator_next, +}; +pub use codes_set::{ + codes_set_bytes, codes_set_double, codes_set_double_array, codes_set_long, + codes_set_long_array, codes_set_string, +}; +pub use grib_nearest::{ + codes_grib_nearest_delete, codes_grib_nearest_find, codes_grib_nearest_new, }; - -#[derive(Copy, Eq, PartialEq, Clone, Ord, PartialOrd, Hash, Debug, num_derive::FromPrimitive)] -pub enum NativeKeyType { - Undefined = CODES_TYPE_UNDEFINED as isize, - Long = CODES_TYPE_LONG as isize, - Double = CODES_TYPE_DOUBLE as isize, - Str = CODES_TYPE_STRING as isize, - Bytes = CODES_TYPE_BYTES as isize, - Section = CODES_TYPE_SECTION as isize, - Label = CODES_TYPE_LABEL as isize, - Missing = CODES_TYPE_MISSING as isize, -} - -pub unsafe fn codes_handle_new_from_file( - file_pointer: *mut FILE, - product_kind: ProductKind, -) -> Result<*mut codes_handle, CodesError> { - let context: *mut codes_context = ptr::null_mut(); //default context - - let mut error_code: i32 = 0; - - let file_handle = eccodes_sys::codes_handle_new_from_file( - context, - file_pointer.cast::<_SYS_IO_FILE>(), - product_kind as u32, - &mut error_code, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(file_handle) -} - -pub unsafe fn codes_handle_delete(handle: *mut codes_handle) -> Result<(), CodesError> { - if handle.is_null() { - return Ok(()); - } - - let error_code = eccodes_sys::codes_handle_delete(handle); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_get_native_type( - handle: *mut codes_handle, - key: &str, -) -> Result { - let key = CString::new(key).unwrap(); - let mut key_type: i32 = 0; - - let error_code = eccodes_sys::codes_get_native_type(handle, key.as_ptr(), &mut key_type); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(FromPrimitive::from_i32(key_type).unwrap()) -} - -pub unsafe fn codes_get_size(handle: *mut codes_handle, key: &str) -> Result { - let key = CString::new(key).unwrap(); - let mut key_size: usize = 0; - - let error_code = eccodes_sys::codes_get_size(handle, key.as_ptr(), &mut key_size); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(key_size) -} - -pub unsafe fn codes_get_long(handle: *mut codes_handle, key: &str) -> Result { - let key = CString::new(key).unwrap(); - let mut key_value: i64 = 0; - - let error_code = eccodes_sys::codes_get_long(handle, key.as_ptr(), &mut key_value); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(key_value) -} - -pub unsafe fn codes_get_double(handle: *mut codes_handle, key: &str) -> Result { - let key = CString::new(key).unwrap(); - let mut key_value: f64 = 0.0; - - let error_code = eccodes_sys::codes_get_double(handle, key.as_ptr(), &mut key_value); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(key_value) -} - -pub unsafe fn codes_get_double_array( - handle: *mut codes_handle, - key: &str, -) -> Result, CodesError> { - let mut key_size = codes_get_size(handle, key)?; - let key = CString::new(key).unwrap(); - - let mut key_values: Vec = vec![0.0; key_size]; - - let error_code = eccodes_sys::codes_get_double_array( - handle, - key.as_ptr(), - key_values.as_mut_ptr().cast::(), - &mut key_size, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(key_values) -} - -pub unsafe fn codes_get_long_array( - handle: *mut codes_handle, - key: &str, -) -> Result, CodesError> { - let mut key_size = codes_get_size(handle, key)?; - let key = CString::new(key).unwrap(); - - let mut key_values: Vec = vec![0; key_size]; - - let error_code = eccodes_sys::codes_get_long_array( - handle, - key.as_ptr(), - key_values.as_mut_ptr().cast::(), - &mut key_size, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(key_values) -} - -pub unsafe fn codes_get_length(handle: *mut codes_handle, key: &str) -> Result { - let key = CString::new(key).unwrap(); - let mut key_length: usize = 0; - - let error_code = eccodes_sys::codes_get_length(handle, key.as_ptr(), &mut key_length); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(key_length) -} - -pub unsafe fn codes_get_string(handle: *mut codes_handle, key: &str) -> Result { - let mut key_length = codes_get_length(handle, key)?; - let key = CString::new(key).unwrap(); - - let mut key_message: Vec = vec![0; key_length]; - - let error_code = eccodes_sys::codes_get_string( - handle, - key.as_ptr(), - key_message.as_mut_ptr().cast::(), - &mut key_length, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - key_message.truncate(key_length); - let key_message_result = CStr::from_bytes_with_nul(key_message.as_ref()); - - let key_message_cstr = if let Ok(msg) = key_message_result { - msg - } else { - key_message.push(0); - CStr::from_bytes_with_nul(key_message.as_ref())? - }; - - let key_message_string = key_message_cstr.to_str()?.to_string(); - - Ok(key_message_string) -} - -pub unsafe fn codes_get_bytes(handle: *mut codes_handle, key: &str) -> Result, CodesError> { - let mut key_size = codes_get_length(handle, key)?; - let key = CString::new(key).unwrap(); - - let mut buffer: Vec = vec![0; key_size]; - - let error_code = eccodes_sys::codes_get_bytes( - handle, - key.as_ptr(), - buffer.as_mut_ptr().cast::(), - &mut key_size, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(buffer) -} - -pub unsafe fn codes_get_message_size(handle: *mut codes_handle) -> Result { - let mut size: usize = 0; - - let error_code = eccodes_sys::codes_get_message_size(handle, &mut size); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(size) -} - -pub unsafe fn codes_get_message( - handle: *mut codes_handle, -) -> Result<(*const c_void, usize), CodesError> { - let buffer_size = codes_get_message_size(handle)?; - - let buffer: Vec = vec![0; buffer_size]; - let mut buffer_ptr = buffer.as_ptr().cast::(); - - let mut message_size: usize = 0; - - let error_code = eccodes_sys::codes_get_message(handle, &mut buffer_ptr, &mut message_size); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - assert!( - buffer_size == message_size, - "Buffer and message sizes ar not equal in codes_get_message! - Please report this panic on Github." - ); - - Ok((buffer_ptr, message_size)) -} - -pub unsafe fn codes_get_message_copy(handle: *mut codes_handle) -> Result, CodesError> { - let buffer_size = codes_get_message_size(handle)?; - - let mut buffer: Vec = vec![0; buffer_size]; - - let mut message_size = buffer_size; - - let error_code = eccodes_sys::codes_get_message_copy( - handle, - buffer.as_mut_ptr().cast::(), - &mut message_size, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - assert!( - (buffer_size == message_size && message_size == buffer.len()), - "Buffer, vector and message sizes ar not equal in codes_get_message! - Please report this panic on Github." - ); - - Ok(buffer) -} - -pub unsafe fn codes_handle_new_from_message_copy(message_buffer: &[u8]) -> *mut codes_handle { - let default_context: *mut codes_context = ptr::null_mut(); - - eccodes_sys::codes_handle_new_from_message_copy( - default_context, - message_buffer.as_ptr().cast::(), - message_buffer.len(), - ) -} - -pub unsafe fn codes_keys_iterator_new( - handle: *mut codes_handle, - flags: u32, - namespace: &str, -) -> *mut codes_keys_iterator { - let namespace = CString::new(namespace).unwrap(); - - eccodes_sys::codes_keys_iterator_new(handle, u64::from(flags), namespace.as_ptr()) -} - -pub unsafe fn codes_keys_iterator_delete( - keys_iterator: *mut codes_keys_iterator, -) -> Result<(), CodesError> { - let error_code = eccodes_sys::codes_keys_iterator_delete(keys_iterator); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_keys_iterator_next(keys_iterator: *mut codes_keys_iterator) -> bool { - let next_item_exists = eccodes_sys::codes_keys_iterator_next(keys_iterator); - - next_item_exists == 1 -} - -pub unsafe fn codes_keys_iterator_get_name( - keys_iterator: *mut codes_keys_iterator, -) -> Result { - let name_pointer = eccodes_sys::codes_keys_iterator_get_name(keys_iterator); - - let name_c_str = CStr::from_ptr(name_pointer); - let name_str = name_c_str.to_str()?; - let name_string = name_str.to_owned(); - - Ok(name_string) -} - -pub unsafe fn codes_grib_nearest_new( - handle: *mut codes_handle, -) -> Result<*mut codes_nearest, CodesError> { - let mut error_code: i32 = 0; - - let nearest = eccodes_sys::codes_grib_nearest_new(handle, &mut error_code); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(nearest) -} - -pub unsafe fn codes_grib_nearest_delete(nearest: *mut codes_nearest) -> Result<(), CodesError> { - let error_code = eccodes_sys::codes_grib_nearest_delete(nearest); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_grib_nearest_find( - handle: *mut codes_handle, - nearest: *mut codes_nearest, - lat: f64, - lon: f64, -) -> Result<[NearestGridpoint; 4], CodesError> { - // such flags are set because find nearest for given nearest is always - // called on the same grib message - let flags = CODES_NEAREST_SAME_GRID + CODES_NEAREST_SAME_DATA; - - let mut output_lats = [0_f64; 4]; - let mut output_lons = [0_f64; 4]; - let mut output_values = [0_f64; 4]; - let mut output_distances = [0_f64; 4]; - let mut output_indexes = [0_i32; 4]; - - let mut length: usize = 4; - - let error_code = eccodes_sys::codes_grib_nearest_find( - nearest, - handle, - lat, - lon, - u64::from(flags), - addr_of_mut!(output_lats[0]), - addr_of_mut!(output_lons[0]), - addr_of_mut!(output_values[0]), - addr_of_mut!(output_distances[0]), - addr_of_mut!(output_indexes[0]), - &mut length, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - let mut output = [NearestGridpoint::default(); 4]; - - for i in 0..4 { - output[i].lat = output_lats[i]; - output[i].lon = output_lons[i]; - output[i].distance = output_distances[i]; - output[i].index = output_indexes[i]; - output[i].value = output_values[i]; - } - - Ok(output) -} - -pub unsafe fn codes_set_long( - handle: *mut codes_handle, - key: &str, - value: i64, -) -> Result<(), CodesError> { - let key = CString::new(key).unwrap(); - - let error_code = eccodes_sys::codes_set_long(handle, key.as_ptr(), value); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_set_double( - handle: *mut codes_handle, - key: &str, - value: f64, -) -> Result<(), CodesError> { - let key = CString::new(key).unwrap(); - - let error_code = eccodes_sys::codes_set_double(handle, key.as_ptr(), value); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_set_long_array( - handle: *mut codes_handle, - key: &str, - values: &[i64], -) -> Result<(), CodesError> { - let key = CString::new(key).unwrap(); - - let length = values.len(); - - let error_code = eccodes_sys::codes_set_long_array( - handle, - key.as_ptr(), - values.as_ptr().cast::(), - length, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_set_double_array( - handle: *mut codes_handle, - key: &str, - values: &[f64], -) -> Result<(), CodesError> { - let key = CString::new(key).unwrap(); - - let length = values.len(); - - let error_code = eccodes_sys::codes_set_double_array( - handle, - key.as_ptr(), - values.as_ptr().cast::(), - length, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_set_string( - handle: *mut codes_handle, - key: &str, - value: &str, -) -> Result<(), CodesError> { - let key = CString::new(key).unwrap(); - let mut length = value.len(); - let value = CString::new(value).unwrap(); - - let error_code = - eccodes_sys::codes_set_string(handle, key.as_ptr(), value.as_ptr(), &mut length); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} - -pub unsafe fn codes_set_bytes( - handle: *mut codes_handle, - key: &str, - values: &[u8], -) -> Result<(), CodesError> { - let key = CString::new(key).unwrap(); - - let mut length = values.len(); - - let error_code = eccodes_sys::codes_set_bytes( - handle, - key.as_ptr(), - values.as_ptr().cast::(), - &mut length, - ); - - if error_code != 0 { - let err: CodesInternal = FromPrimitive::from_i32(error_code).unwrap(); - return Err(err.into()); - } - - Ok(()) -} diff --git a/src/keyed_message/mod.rs b/src/keyed_message/mod.rs new file mode 100644 index 0000000..44e5a9e --- /dev/null +++ b/src/keyed_message/mod.rs @@ -0,0 +1,158 @@ +mod read; +mod write; + +use eccodes_sys::codes_handle; +use log::warn; +use std::ptr::null_mut; + +use crate::intermediate_bindings::{codes_handle_clone, codes_handle_delete}; + +///Structure used to access keys inside the GRIB file message. +///All data (including data values) contained by the file can only be accessed +///through the message and keys. +/// +///The structure implements `Clone` trait which comes with a memory overhead. +///You should take care that your system has enough memory before cloning `KeyedMessage`. +/// +///Keys inside the message can be accessed directly with [`read_key()`](KeyedMessage::read_key()) +///function or using [`FallibleIterator`](KeyedMessage#impl-FallibleIterator). +///The function [`find_nearest()`](KeyedMessage::find_nearest()) allows to get the values of four nearest gridpoints +///to requested coordinates. +///`FallibleIterator` parameters can be set with [`set_iterator_parameters()`](KeyedMessage::set_iterator_parameters()) +///to specify the subset of keys to iterate over. +#[derive(Hash, Debug)] +pub struct KeyedMessage { + pub(crate) message_handle: *mut codes_handle, +} + +///Structure representing a single key from the `KeyedMessage`. +#[derive(Clone, Debug, PartialEq)] +pub struct Key { + pub name: String, + pub value: KeyType, +} + +///Enum to represent and contain all possible types of keys inside `KeyedMessage`. +/// +///Messages inside GRIB files can contain arbitrary keys set by the file author. +///The type of a given key is only known at runtime (after being checked). +///There are several possible types of keys, which are represented by this enum +///and each variant contains the respective data type. +#[derive(Clone, Debug, PartialEq)] +pub enum KeyType { + Float(f64), + Int(i64), + FloatArray(Vec), + IntArray(Vec), + Str(String), + Bytes(Vec), +} + +impl Clone for KeyedMessage { + ///Custom function to clone the `KeyedMessage`. This function comes with memory overhead. + ///During clone iterator flags and namespace are not copied, and the iterator is reset. + fn clone(&self) -> KeyedMessage { + let new_handle = + unsafe { codes_handle_clone(self.message_handle).expect("Cannot clone the message") }; + + KeyedMessage { + message_handle: new_handle, + } + } +} + +impl Drop for KeyedMessage { + ///Executes the destructor for this type. + ///This method calls destructor functions from ecCodes library. + ///In some edge cases these functions can return non-zero code. + ///In such case all pointers and file descriptors are safely deleted. + ///However memory leaks can still occur. + /// + ///If any function called in the destructor returns an error warning will appear in log. + ///If bugs occur during `CodesHandle` drop please enable log output and post issue on [Github](https://github.com/ScaleWeather/eccodes). + /// + ///Technical note: delete functions in ecCodes can only fail with [`CodesInternalError`](crate::errors::CodesInternal::CodesInternalError) + ///when other functions corrupt the inner memory of pointer, in that case memory leak is possible. + ///In case of corrupt pointer segmentation fault will occur. + ///The pointers are cleared at the end of drop as they are not functional regardless of result of delete functions. + fn drop(&mut self) { + unsafe { + codes_handle_delete(self.message_handle).unwrap_or_else(|error| { + warn!("codes_handle_delete() returned an error: {:?}", &error); + }); + } + + self.message_handle = null_mut(); + } +} + +#[cfg(test)] +mod tests { + use crate::codes_handle::{CodesHandle, ProductKind}; + use crate::FallibleStreamingIterator; + use anyhow::{Context, Result}; + use std::path::Path; + use testing_logger; + + #[test] + fn message_clone_1() -> Result<()> { + let file_path = Path::new("./data/iceland.grib"); + let product_kind = ProductKind::GRIB; + + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + let current_message = handle.next()?.context("Message not some")?; + let cloned_message = current_message.clone(); + + assert_ne!( + current_message.message_handle, + cloned_message.message_handle + ); + + Ok(()) + } + + #[test] + fn message_clone_2() -> Result<()> { + let file_path = Path::new("./data/iceland.grib"); + let product_kind = ProductKind::GRIB; + + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + let msg = handle.next()?.context("Message not some")?.clone(); + let _ = handle.next()?; + + drop(handle); + + let _ = msg.read_key("dataDate")?; + let _ = msg.read_key("jDirectionIncrementInDegrees")?; + let _ = msg.read_key("values")?; + let _ = msg.read_key("name")?; + let _ = msg.read_key("section1Padding")?; + let _ = msg.read_key("experimentVersionNumber")?; + + Ok(()) + } + + #[test] + fn message_drop() -> Result<()> { + testing_logger::setup(); + let file_path = Path::new("./data/iceland.grib"); + let product_kind = ProductKind::GRIB; + + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + let current_message = handle.next()?.context("Message not some")?.clone(); + + let _kiter = current_message.default_keys_iterator()?; + let _niter = current_message.codes_nearest()?; + + drop(handle); + drop(_kiter); + drop(_niter); + drop(current_message); + + testing_logger::validate(|captured_logs| { + assert_eq!(captured_logs.len(), 0); + }); + + Ok(()) + } +} diff --git a/src/codes_handle/keyed_message/read.rs b/src/keyed_message/read.rs similarity index 87% rename from src/codes_handle/keyed_message/read.rs rename to src/keyed_message/read.rs index 1fcf994..93633fb 100644 --- a/src/codes_handle/keyed_message/read.rs +++ b/src/keyed_message/read.rs @@ -1,11 +1,11 @@ use crate::{ - codes_handle::{Key, KeyType, KeyedMessage}, errors::CodesError, intermediate_bindings::{ codes_get_bytes, codes_get_double, codes_get_double_array, codes_get_long, codes_get_long_array, codes_get_native_type, codes_get_size, codes_get_string, NativeKeyType, }, + Key, KeyType, KeyedMessage, }; impl KeyedMessage { @@ -165,10 +165,10 @@ impl KeyedMessage { #[cfg(test)] mod tests { - use anyhow::Result; + use anyhow::{Context, Result}; - use crate::codes_handle::{CodesHandle, KeyType, ProductKind}; - use crate::{FallibleIterator, FallibleStreamingIterator}; + use crate::codes_handle::{CodesHandle, ProductKind}; + use crate::{FallibleIterator, FallibleStreamingIterator, KeyType}; use std::path::Path; #[test] @@ -176,11 +176,11 @@ mod tests { let file_path = Path::new("./data/iceland.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next().unwrap().unwrap(); + let current_message = handle.next()?.context("Message not some")?; - let str_key = current_message.read_key("name").unwrap(); + let str_key = current_message.read_key("name")?; match str_key.value { KeyType::Str(_) => {} @@ -189,10 +189,7 @@ mod tests { assert_eq!(str_key.name, "name"); - let double_key = current_message - .read_key("jDirectionIncrementInDegrees") - .unwrap(); - + let double_key = current_message.read_key("jDirectionIncrementInDegrees")?; match double_key.value { KeyType::Float(_) => {} _ => panic!("Incorrect variant of double key"), @@ -200,9 +197,7 @@ mod tests { assert_eq!(double_key.name, "jDirectionIncrementInDegrees"); - let long_key = current_message - .read_key("numberOfPointsAlongAParallel") - .unwrap(); + let long_key = current_message.read_key("numberOfPointsAlongAParallel")?; match long_key.value { KeyType::Int(_) => {} @@ -211,7 +206,7 @@ mod tests { assert_eq!(long_key.name, "numberOfPointsAlongAParallel"); - let double_arr_key = current_message.read_key("values").unwrap(); + let double_arr_key = current_message.read_key("values")?; match double_arr_key.value { KeyType::FloatArray(_) => {} @@ -229,11 +224,11 @@ mod tests { let product_kind = ProductKind::GRIB; let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let mut current_message = handle.next()?.unwrap().clone(); + let current_message = handle.next()?.context("Message not some")?; + let mut kiter = current_message.default_keys_iterator()?; - for i in 0..=300 { - let key = current_message.next(); - println!("{}: {:?}", i, key); + while let Some(key) = kiter.next()? { + assert!(!key.name.is_empty()); } Ok(()) @@ -245,11 +240,11 @@ mod tests { let product_kind = ProductKind::GRIB; let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let mut current_message = handle.next()?.unwrap().clone(); + let current_message = handle.next()?.context("Message not some")?; + let mut kiter = current_message.default_keys_iterator()?; - for i in 0..=300 { - let key = current_message.next(); - println!("{}: {:?}", i, key); + while let Some(key) = kiter.next()? { + assert!(!key.name.is_empty()); } Ok(()) @@ -261,7 +256,7 @@ mod tests { let product_kind = ProductKind::GRIB; let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap(); + let current_message = handle.next()?.context("Message not some")?; let missing_key = current_message.read_key("doesNotExist"); @@ -277,7 +272,7 @@ mod tests { let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let msg = handle.next()?.unwrap(); + let msg = handle.next()?.context("Message not some")?; let _ = msg.read_key("dataDate")?; let _ = msg.read_key("jDirectionIncrementInDegrees")?; diff --git a/src/codes_handle/keyed_message/write.rs b/src/keyed_message/write.rs similarity index 91% rename from src/codes_handle/keyed_message/write.rs rename to src/keyed_message/write.rs index b33ee4e..dfb4fe6 100644 --- a/src/codes_handle/keyed_message/write.rs +++ b/src/keyed_message/write.rs @@ -1,12 +1,12 @@ use std::{fs::OpenOptions, io::Write, path::Path, slice}; use crate::{ - codes_handle::{Key, KeyType, KeyedMessage}, errors::CodesError, intermediate_bindings::{ codes_get_message, codes_set_bytes, codes_set_double, codes_set_double_array, codes_set_long, codes_set_long_array, codes_set_string, }, + Key, KeyType, KeyedMessage, }; impl KeyedMessage { @@ -134,15 +134,11 @@ impl KeyedMessage { #[cfg(test)] mod tests { - use anyhow::{Ok, Result}; + use anyhow::{Context, Ok, Result}; use crate::{ - codes_handle::{ - CodesHandle, Key, - KeyType::{self}, - ProductKind, - }, - FallibleStreamingIterator, + codes_handle::{CodesHandle, ProductKind}, + FallibleStreamingIterator, Key, KeyType, }; use std::{fs::remove_file, path::Path}; @@ -153,7 +149,7 @@ mod tests { let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap(); + let current_message = handle.next()?.context("Message not some")?; let out_path = Path::new("./data/iceland_write.grib"); current_message.write_to_file(out_path, false)?; @@ -168,7 +164,7 @@ mod tests { let product_kind = ProductKind::GRIB; let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap().clone(); + let current_message = handle.next()?.context("Message not some")?.clone(); drop(handle); @@ -187,12 +183,12 @@ mod tests { let file_path = Path::new("./data/iceland-surface.grib"); let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap(); + let current_message = handle.next()?.context("Message not some")?; current_message.write_to_file(out_path, false)?; let file_path = Path::new("./data/iceland-levels.grib"); let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap(); + let current_message = handle.next()?.context("Message not some")?; current_message.write_to_file(out_path, true)?; remove_file(out_path)?; @@ -206,7 +202,7 @@ mod tests { let file_path = Path::new("./data/iceland.grib"); let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let mut current_message = handle.next()?.unwrap().clone(); + let mut current_message = handle.next()?.context("Message not some")?.clone(); let old_key = current_message.read_key("centre")?; @@ -231,7 +227,7 @@ mod tests { let file_path = Path::new("./data/iceland.grib"); let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let mut current_message = handle.next()?.unwrap().clone(); + let mut current_message = handle.next()?.context("Message not some")?.clone(); let old_key = current_message.read_key("centre")?; @@ -247,7 +243,7 @@ mod tests { let file_path = Path::new("./data/iceland_edit.grib"); let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - let current_message = handle.next()?.unwrap(); + let current_message = handle.next()?.context("Message not some")?; let read_key = current_message.read_key("centre")?; diff --git a/src/codes_handle/keyed_message/iterator.rs b/src/keys_iterator.rs similarity index 54% rename from src/codes_handle/keyed_message/iterator.rs rename to src/keys_iterator.rs index 18ff274..532cc38 100644 --- a/src/codes_handle/keyed_message/iterator.rs +++ b/src/keys_iterator.rs @@ -1,17 +1,123 @@ use eccodes_sys::codes_keys_iterator; use fallible_iterator::FallibleIterator; +use log::warn; +use std::ptr::null_mut; use crate::{ - codes_handle::{Key, KeyedMessage}, errors::CodesError, intermediate_bindings::{ - codes_keys_iterator_get_name, codes_keys_iterator_new, codes_keys_iterator_next, + codes_keys_iterator_delete, codes_keys_iterator_get_name, codes_keys_iterator_new, + codes_keys_iterator_next, }, + Key, KeyedMessage, }; -use super::KeysIteratorFlags; +#[derive(Debug)] +pub struct KeysIterator<'a> { + parent_message: &'a KeyedMessage, + iterator_handle: *mut codes_keys_iterator, + next_item_exists: bool, +} + +///Flags to specify the subset of keys to iterate over +///by `FallibleIterator` in `KeyedMessage`. The flags can be used together. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum KeysIteratorFlags { + ///Iterate over all keys + AllKeys = eccodes_sys::CODES_KEYS_ITERATOR_ALL_KEYS as isize, + ///Iterate only dump keys + DumpOnly = eccodes_sys::CODES_KEYS_ITERATOR_DUMP_ONLY as isize, + ///Exclude coded keys from iteration + SkipCoded = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_CODED as isize, + ///Exclude computed keys from iteration + SkipComputed = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_COMPUTED as isize, + ///Exclude function keys from iteration + SkipFunction = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_FUNCTION as isize, + ///Exclude optional keys from iteration + SkipOptional = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_OPTIONAL as isize, + ///Exclude read-only keys from iteration + SkipReadOnly = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_READ_ONLY as isize, + ///Exclude duplicate keys from iteration + SkipDuplicates = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_DUPLICATES as isize, + ///Exclude file edition specific keys from iteration + SkipEditionSpecific = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC as isize, +} + +impl KeyedMessage { + ///Function that allows to set the flags and namespace for `FallibleIterator`. + ///**Must be called before calling the iterator.** Changing the parameters + ///after first call of `next()` will have no effect on the iterator. + /// + ///The flags are set by providing any combination of [`KeysIteratorFlags`] + ///inside a vector. Check the documentation for the details of each flag meaning. + /// + ///Namespace is set simply as string, eg. `"ls"`, `"time"`, `"parameter"`, `"geography"`, `"statistics"`. + ///Invalid namespace will result in empty iterator. + /// + ///Default parameters are [`AllKeys`](KeysIteratorFlags::AllKeys) flag and `""` namespace, + ///which implies iteration over all keys available in the message. + /// + ///### Example + /// + ///``` + ///# use eccodes::codes_handle::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags}; + ///# use std::path::Path; + ///# use eccodes::codes_handle::KeyType::Str; + ///# use eccodes::FallibleIterator; + ///let file_path = Path::new("./data/iceland.grib"); + ///let product_kind = ProductKind::GRIB; + /// + ///let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); + ///let mut current_message = handle.next().unwrap().unwrap(); + /// + /// + ///let flags = vec![ + /// KeysIteratorFlags::AllKeys, + /// KeysIteratorFlags::SkipOptional, + /// KeysIteratorFlags::SkipReadOnly, + /// KeysIteratorFlags::SkipDuplicates, + ///]; + /// + ///let namespace = "geography".to_owned(); + /// + ///current_message.set_iterator_parameters(flags, namespace); + /// + /// + ///while let Some(key) = current_message.next().unwrap() { + /// println!("{:?}", key); + ///} + ///``` + pub fn new_keys_iterator( + &self, + flags: Vec, + namespace: String, + ) -> Result { + 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: self, + iterator_handle, + next_item_exists, + }) + } + + pub fn default_keys_iterator(&self) -> Result { + 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: self, + iterator_handle, + next_item_exists, + }) + } +} -///`FallibleIterator` implementation for `KeyedMessage` to access keyes inside message. +///`FallibleIterator` implementation for `KeysIterator` to iterate through keys inside `KeyedMessage`. ///Mainly useful to discover what keys are present inside the message. /// ///This function internally calls [`read_key()`](KeyedMessage::read_key()) function @@ -44,25 +150,23 @@ use super::KeysIteratorFlags; ///## Errors ///The `next()` method will return [`CodesInternal`](crate::errors::CodesInternal) ///when internal ecCodes function returns non-zero code. -impl FallibleIterator for KeyedMessage { +impl FallibleIterator for KeysIterator<'_> { type Item = Key; type Error = CodesError; fn next(&mut self) -> Result, Self::Error> { - let itr = self.keys_iterator()?; - - if self.keys_iterator_next_item_exists { + if self.next_item_exists { let key_name; let next_item_exists; unsafe { - key_name = codes_keys_iterator_get_name(itr)?; - next_item_exists = codes_keys_iterator_next(itr); + key_name = codes_keys_iterator_get_name(self.iterator_handle)?; + next_item_exists = codes_keys_iterator_next(self.iterator_handle)?; } - let key = KeyedMessage::read_key(self, &key_name)?; + let key = KeyedMessage::read_key(self.parent_message, &key_name)?; - self.keys_iterator_next_item_exists = next_item_exists; + self.next_item_exists = next_item_exists; Ok(Some(key)) } else { @@ -71,109 +175,38 @@ impl FallibleIterator for KeyedMessage { } } -impl KeyedMessage { - ///Function that allows to set the flags and namespace for `FallibleIterator`. - ///**Must be called before calling the iterator.** Changing the parameters - ///after first call of `next()` will have no effect on the iterator. - /// - ///The flags are set by providing any combination of [`KeysIteratorFlags`] - ///inside a vector. Check the documentation for the details of each flag meaning. - /// - ///Namespace is set simply as string, eg. `"ls"`, `"time"`, `"parameter"`, `"geography"`, `"statistics"`. - ///Invalid namespace will result in empty iterator. - /// - ///Default parameters are [`AllKeys`](KeysIteratorFlags::AllKeys) flag and `""` namespace, - ///which implies iteration over all keys available in the message. - /// - ///### Example - /// - ///``` - ///# use eccodes::codes_handle::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags}; - ///# use std::path::Path; - ///# use eccodes::codes_handle::KeyType::Str; - ///# use eccodes::FallibleIterator; - ///let file_path = Path::new("./data/iceland.grib"); - ///let product_kind = ProductKind::GRIB; - /// - ///let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); - ///let mut current_message = handle.next().unwrap().unwrap(); - /// - /// - ///let flags = vec![ - /// KeysIteratorFlags::AllKeys, - /// KeysIteratorFlags::SkipOptional, - /// KeysIteratorFlags::SkipReadOnly, - /// KeysIteratorFlags::SkipDuplicates, - ///]; - /// - ///let namespace = "geography".to_owned(); - /// - ///current_message.set_iterator_parameters(flags, namespace); - /// - /// - ///while let Some(key) = current_message.next().unwrap() { - /// println!("{:?}", key); - ///} - ///``` - pub fn set_iterator_parameters(&mut self, flags: Vec, namespace: String) { - self.iterator_namespace = Some(namespace); - - let mut flags_sum = 0; - - for flag in flags { - flags_sum += flag as u32; +impl Drop for KeysIterator<'_> { + fn drop(&mut self) { + unsafe { + codes_keys_iterator_delete(self.iterator_handle).unwrap_or_else(|error| { + warn!( + "codes_keys_iterator_delete() returned an error: {:?}", + &error + ); + }); } - self.iterator_flags = Some(flags_sum); - } - - fn keys_iterator(&mut self) -> Result<*mut codes_keys_iterator, CodesError> { - self.keys_iterator.map_or_else( - || { - let flags = self.iterator_flags.unwrap_or(0); - - let namespace = match self.iterator_namespace.clone() { - Some(n) => n, - None => String::new(), - }; - - let itr; - let next_item; - unsafe { - itr = codes_keys_iterator_new(self.message_handle, flags, &namespace); - next_item = codes_keys_iterator_next(itr); - } - - self.keys_iterator_next_item_exists = next_item; - self.keys_iterator = Some(itr); - - Ok(itr) - }, - Ok, - ) + self.iterator_handle = null_mut(); } } #[cfg(test)] mod tests { - use anyhow::Result; + use anyhow::{Context, Result}; - use crate::codes_handle::{CodesHandle, KeysIteratorFlags, ProductKind}; + use crate::codes_handle::{CodesHandle, ProductKind}; use crate::{FallibleIterator, FallibleStreamingIterator}; 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 = CodesHandle::new_from_file(file_path, product_kind).unwrap(); - let mut current_message = handle.next()?.unwrap().clone(); - - assert!(current_message.iterator_flags.is_none()); - assert!(current_message.iterator_namespace.is_none()); - assert!(current_message.keys_iterator.is_none()); - assert!(!current_message.keys_iterator_next_item_exists); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + let current_message = handle.next()?.context("Message not some")?; let flags = vec![ KeysIteratorFlags::AllKeys, //0 @@ -184,15 +217,9 @@ mod tests { let namespace = "geography".to_owned(); - current_message.set_iterator_parameters(flags, namespace); - - assert_eq!(current_message.iterator_flags, Some(35)); - assert_eq!( - current_message.iterator_namespace, - Some("geography".to_owned()) - ); + let mut kiter = current_message.new_keys_iterator(flags, namespace)?; - while let Some(key) = current_message.next().unwrap() { + while let Some(key) = kiter.next()? { assert!(!key.name.is_empty()); } @@ -204,8 +231,8 @@ mod tests { let file_path = Path::new("./data/iceland.grib"); let product_kind = ProductKind::GRIB; - let mut handle = CodesHandle::new_from_file(file_path, product_kind).unwrap(); - let mut current_message = handle.next()?.unwrap().clone(); + let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; + let current_message = handle.next()?.context("Message not some")?; let flags = vec![ KeysIteratorFlags::AllKeys, //0 @@ -213,9 +240,9 @@ mod tests { let namespace = "blabla".to_owned(); - current_message.set_iterator_parameters(flags, namespace); + let mut kiter = current_message.new_keys_iterator(flags, namespace)?; - while let Some(key) = current_message.next().unwrap() { + while let Some(key) = kiter.next()? { assert!(!key.name.is_empty()); } diff --git a/src/lib.rs b/src/lib.rs index 08348f6..a659d0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,15 +220,20 @@ pub mod codes_handle; #[cfg(feature = "experimental_index")] #[cfg_attr(docsrs, doc(cfg(feature = "experimental_index")))] pub mod codes_index; +pub mod codes_nearest; pub mod errors; mod intermediate_bindings; +pub mod keyed_message; +pub mod keys_iterator; +mod pointer_guard; -pub use codes_handle::{ - CodesHandle, Key, KeyType, KeyedMessage, KeysIteratorFlags, NearestGridpoint, ProductKind, -}; +pub use codes_handle::{CodesHandle, ProductKind}; #[cfg(feature = "experimental_index")] #[cfg_attr(docsrs, doc(cfg(feature = "experimental_index")))] pub use codes_index::CodesIndex; +pub use codes_nearest::{CodesNearest, NearestGridpoint}; pub use errors::CodesError; pub use fallible_iterator::{FallibleIterator, IntoFallibleIterator}; pub use fallible_streaming_iterator::FallibleStreamingIterator; +pub use keyed_message::{Key, KeyType, KeyedMessage}; +pub use keys_iterator::{KeysIterator, KeysIteratorFlags}; diff --git a/src/pointer_guard.rs b/src/pointer_guard.rs new file mode 100644 index 0000000..ba8c4a0 --- /dev/null +++ b/src/pointer_guard.rs @@ -0,0 +1,45 @@ +macro_rules! non_null { + ($ptr:expr) => { + if $ptr.is_null() { + return Err(CodesError::NullPtr); + } + }; +} +pub(crate) use non_null; + +#[cfg(test)] +mod tests { + use crate::errors::CodesError; + use crate::pointer_guard::non_null; + use std::ptr; + + #[test] + fn test_non_null() { + let ptr: *mut i32 = ptr::null_mut(); + let result = simulated_function(ptr); + + assert!(result.is_err()); + + let result = result.unwrap_err(); + + match result { + CodesError::NullPtr => (), + _ => panic!("Incorrect error type: {:?}", result), + } + } + + #[test] + fn test_non_null_ok() { + let mut x = 42_i32; + let ptr = &mut x as *mut i32; + + let result = simulated_function(ptr); + + assert!(result.is_ok()); + } + + fn simulated_function(ptr: *mut i32) -> Result<(), CodesError> { + non_null!(ptr); + Ok(()) + } +} diff --git a/tests/handle.rs b/tests/handle.rs index 299a10b..c753afb 100644 --- a/tests/handle.rs +++ b/tests/handle.rs @@ -1,40 +1,41 @@ use std::{path::Path, thread}; -use eccodes::{CodesHandle, KeyType, ProductKind, FallibleStreamingIterator}; +use anyhow::{Context, Result}; +use eccodes::{CodesHandle, FallibleStreamingIterator, KeyType, ProductKind}; #[test] -fn thread_safety() { - thread::spawn(|| loop { - let file_path = Path::new("./data/iceland.grib"); +fn thread_safety() -> Result<()> { + thread::spawn(|| -> anyhow::Result<()> { + loop { + let file_path = Path::new("./data/iceland.grib"); + + let mut handle = CodesHandle::new_from_file(file_path, ProductKind::GRIB)?; + let current_message = handle.next()?.context("Message not some")?; - let mut handle = CodesHandle::new_from_file(file_path, ProductKind::GRIB).unwrap(); - let current_message = handle.next().unwrap().unwrap(); + for _ in 0..100 { + let _ = current_message.read_key("name")?; - for _ in 0..100 { - let _ = current_message.read_key("name").unwrap(); + let str_key = current_message.read_key("name")?; - let str_key = current_message.read_key("name").unwrap(); + match str_key.value { + KeyType::Str(_) => {} + _ => panic!("Incorrect variant of string key"), + } - match str_key.value { - KeyType::Str(_) => {} - _ => panic!("Incorrect variant of string key"), + assert_eq!(str_key.name, "name"); } - assert_eq!(str_key.name, "name"); + drop(handle); } - - drop(handle); }); for _ in 0..1000 { let file_path = Path::new("./data/iceland.grib"); - let mut handle = CodesHandle::new_from_file(file_path, ProductKind::GRIB).unwrap(); - let current_message = handle.next().unwrap().unwrap(); + let mut handle = CodesHandle::new_from_file(file_path, ProductKind::GRIB)?; + let current_message = handle.next()?.context("Message not some")?; - let long_key = current_message - .read_key("numberOfPointsAlongAParallel") - .unwrap(); + let long_key = current_message.read_key("numberOfPointsAlongAParallel")?; match long_key.value { KeyType::Int(_) => {} @@ -45,4 +46,6 @@ fn thread_safety() { drop(handle); } + + Ok(()) } diff --git a/tests/index.rs b/tests/index.rs index a3910a9..2249b33 100644 --- a/tests/index.rs +++ b/tests/index.rs @@ -2,7 +2,7 @@ use std::{path::Path, thread}; -use anyhow::Result; +use anyhow::{Context, Result}; use eccodes::{ codes_index::Select, CodesError, CodesHandle, CodesIndex, FallibleStreamingIterator, KeyType, ProductKind, @@ -10,57 +10,51 @@ use eccodes::{ use rand::Rng; #[test] -fn iterate_handle_from_index() { +fn iterate_handle_from_index() -> Result<()> { let file_path = Path::new("./data/iceland-surface.idx"); - let index = CodesIndex::read_from_file(file_path) - .unwrap() - .select("shortName", "2t") - .unwrap() - .select("typeOfLevel", "surface") - .unwrap() - .select("level", 0) - .unwrap() - .select("stepType", "instant") - .unwrap(); + let index = CodesIndex::read_from_file(file_path)? + .select("shortName", "2t")? + .select("typeOfLevel", "surface")? + .select("level", 0)? + .select("stepType", "instant")?; - let handle = CodesHandle::new_from_index(index, ProductKind::GRIB).unwrap(); + let handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; - let counter = handle.count().unwrap(); + let counter = handle.count()?; assert_eq!(counter, 1); + + Ok(()) } #[test] -fn read_index_messages() { +fn read_index_messages() -> Result<()> { let file_path = Path::new("./data/iceland-surface.idx"); - let index = CodesIndex::read_from_file(file_path) - .unwrap() - .select("shortName", "2t") - .unwrap() - .select("typeOfLevel", "surface") - .unwrap() - .select("level", 0) - .unwrap() - .select("stepType", "instant") - .unwrap(); - - let mut handle = CodesHandle::new_from_index(index, ProductKind::GRIB).unwrap(); - let current_message = handle.next().unwrap().unwrap(); + let index = CodesIndex::read_from_file(file_path)? + .select("shortName", "2t")? + .select("typeOfLevel", "surface")? + .select("level", 0)? + .select("stepType", "instant")?; + + let mut handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; + let current_message = handle.next()?.context("Message not some")?; { - let short_name = current_message.read_key("shortName").unwrap(); + let short_name = current_message.read_key("shortName")?; match short_name.value { KeyType::Str(val) => assert!(val == "2t"), _ => panic!("Unexpected key type"), }; } { - let level = current_message.read_key("level").unwrap(); + let level = current_message.read_key("level")?; match level.value { KeyType::Int(val) => assert!(val == 0), _ => panic!("Unexpected key type"), }; } + + Ok(()) } #[test] @@ -88,14 +82,14 @@ fn collect_index_iterator() -> Result<()> { } #[test] -fn add_file_error() { - thread::spawn(|| { +fn add_file_error() -> Result<()> { + thread::spawn(|| -> Result<()> { let grib_path = Path::new("./data/iceland-levels.grib"); let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; - let mut index_op = CodesIndex::new_from_keys(&keys).unwrap(); + let mut index_op = CodesIndex::new_from_keys(&keys)?; loop { - index_op = index_op.add_grib_file(grib_path).unwrap(); + index_op = index_op.add_grib_file(grib_path)?; } }); @@ -103,22 +97,22 @@ fn add_file_error() { let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; let wrong_path = Path::new("./data/xxx.grib"); - let index = CodesIndex::new_from_keys(&keys) - .unwrap() - .add_grib_file(wrong_path); + let index = CodesIndex::new_from_keys(&keys)?.add_grib_file(wrong_path); assert!(index.is_err()); + + Ok(()) } #[test] -fn index_panic() { - thread::spawn(|| { +fn index_panic() -> Result<()> { + thread::spawn(|| -> Result<()> { let grib_path = Path::new("./data/iceland-levels.grib"); let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; - let mut index_op = CodesIndex::new_from_keys(&keys).unwrap(); + let mut index_op = CodesIndex::new_from_keys(&keys)?; loop { - index_op = index_op.add_grib_file(grib_path).unwrap(); + index_op = index_op.add_grib_file(grib_path)?; } }); @@ -126,61 +120,57 @@ fn index_panic() { let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; let wrong_path = Path::new("./data/xxx.grib"); - let index = CodesIndex::new_from_keys(&keys).unwrap(); + let index = CodesIndex::new_from_keys(&keys)?; let result = std::panic::catch_unwind(|| index.add_grib_file(wrong_path).unwrap()); assert!(result.is_err()); + + Ok(()) } #[test] -fn add_file_while_index_open() { - thread::spawn(|| { +#[ignore = "for releases, indexing is experimental"] +fn add_file_while_index_open() -> Result<()> { + thread::spawn(|| -> Result<()> { let file_path = Path::new("./data/iceland-surface.idx"); - let mut index_op = CodesIndex::read_from_file(file_path).unwrap(); + let mut index_op = CodesIndex::read_from_file(file_path)?; loop { index_op = index_op - .select("shortName", "2t") - .unwrap() - .select("typeOfLevel", "surface") - .unwrap() - .select("level", 0) - .unwrap() - .select("stepType", "instant") - .unwrap(); + .select("shortName", "2t")? + .select("typeOfLevel", "surface")? + .select("level", 0)? + .select("stepType", "instant")?; } }); let keys = vec!["shortName", "typeOfLevel", "level", "stepType"]; let grib_path = Path::new("./data/iceland-surface.grib"); - let index = CodesIndex::new_from_keys(&keys) - .unwrap() - .add_grib_file(grib_path); + let index = CodesIndex::new_from_keys(&keys)?.add_grib_file(grib_path); assert!(index.is_ok()); + + Ok(()) } #[test] -fn add_file_to_read_index() { +fn add_file_to_read_index() -> Result<()> { let file_path = Path::new("./data/iceland-surface.idx"); let grib_path = Path::new("./data/iceland-surface.grib"); - let _index = CodesIndex::read_from_file(file_path) - .unwrap() - .add_grib_file(grib_path) - .unwrap() - .select("shortName", "2t") - .unwrap() - .select("typeOfLevel", "surface") - .unwrap() - .select("level", 0) - .unwrap() - .select("stepType", "instant") - .unwrap(); + let _index = CodesIndex::read_from_file(file_path)? + .add_grib_file(grib_path)? + .select("shortName", "2t")? + .select("typeOfLevel", "surface")? + .select("level", 0)? + .select("stepType", "instant")?; + + Ok(()) } #[test] +#[ignore = "for releases, indexing is experimental"] fn simulatenous_index_destructors() -> Result<()> { let h1 = thread::spawn(|| -> anyhow::Result<(), CodesError> { let mut rng = rand::thread_rng(); @@ -220,7 +210,7 @@ fn simulatenous_index_destructors() -> Result<()> { thread::sleep(std::time::Duration::from_millis(sleep_time)); drop(index); } - + Ok(()) }); @@ -231,8 +221,9 @@ fn simulatenous_index_destructors() -> Result<()> { } #[test] -fn index_handle_interference() { - thread::spawn(|| { +#[ignore = "for releases, indexing is experimental"] +fn index_handle_interference() -> Result<()> { + thread::spawn(|| -> Result<()> { let file_path = Path::new("./data/iceland.grib"); loop { @@ -249,14 +240,13 @@ fn index_handle_interference() { for _ in 0..10 { let sleep_time = rng.gen_range(1..42); // randomizing sleep time to hopefully catch segfaults - let index = CodesIndex::new_from_keys(&keys) - .unwrap() - .add_grib_file(grib_path) - .unwrap(); + let index = CodesIndex::new_from_keys(&keys)?.add_grib_file(grib_path)?; let i_handle = CodesHandle::new_from_index(index, ProductKind::GRIB); assert!(i_handle.is_ok()); thread::sleep(std::time::Duration::from_millis(sleep_time)); } + + Ok(()) }