diff --git a/src/codes_handle/mod.rs b/src/codes_handle/mod.rs index b36af97..2a52cfe 100644 --- a/src/codes_handle/mod.rs +++ b/src/codes_handle/mod.rs @@ -24,18 +24,16 @@ pub struct GribFile { pointer: *mut FILE, } -/// Main structure used to operate on the GRIB file, which takes a full ownership of the accessed file. +/// Structure providing access to the GRIB file which takes a full ownership of the accessed file. /// -/// It can be constructed either using a file or a memory buffer. +/// It can be constructed from: +/// +/// - File path using [`new_from_file()`](CodesHandle::new_from_file) +/// - From memory buffer using [`new_from_memory()`](CodesHandle::new_from_memory) +/// - From GRIB index using [`new_from_index()`](CodesHandle::new_from_index) (with `experimental_index` feature enabled) /// -/// - Use [`new_from_file()`](CodesHandle::new_from_file) -/// to open a file under provided [`path`](`std::path::Path`) using filesystem, -/// when copying whole file into memory is not desired or not necessary. -/// -/// - Alternatively use [`new_from_memory()`](CodesHandle::new_from_memory) -/// to access a file that is already in memory. For example, when file is downloaded from the internet -/// and does not need to be saved on hard drive. -/// The file must be stored in [`bytes::Bytes`](https://docs.rs/bytes/1.1.0/bytes/struct.Bytes.html). +/// Destructor for this structure does not panic, but some internal functions may rarely fail +/// leading to bugs. Errors encountered during the destructor are logged with [`log`]. #[derive(Debug)] pub struct CodesHandle { _data: DataContainer, @@ -60,30 +58,31 @@ pub enum ProductKind { } impl CodesHandle { - ///The constructor that takes a [`path`](Path) to an existing file and - ///a requested [`ProductKind`] and returns the [`CodesHandle`] object. + ///Opens file at given [`Path`] as selected [`ProductKind`] and contructs `CodesHandle`. /// ///## Example /// ///``` ///# use eccodes::codes_handle::{ProductKind, CodesHandle}; ///# use std::path::Path; - ///# + ///# fn main() -> anyhow::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)?; + /// # Ok(()) + /// # } ///``` /// - ///The function opens the file as [`File`] and then utilises - ///[`fdopen()`](https://man7.org/linux/man-pages/man3/fdopen.3.html) function - ///to associate [`io::RawFd`](`std::os::unix::io::RawFd`) from [`File`] + ///The function creates [`fs::File`](std::fs::File) from provided path and utilises + ///[`fdopen()`](https://man7.org/linux/man-pages/man3/fdopen.3.html) + ///to associate [`io::RawFd`](`std::os::unix::io::RawFd`) ///with a stream represented by [`libc::FILE`](https://docs.rs/libc/0.2.101/libc/enum.FILE.html) pointer. /// - ///The constructor takes a [`path`](Path) as an argument instead of [`File`] + ///The constructor takes as argument a [`path`](Path) instead of [`File`] ///to ensure that `fdopen()` uses the same mode as [`File`]. - ///The file descriptor does not take the ownership of a file, therefore the - ///[`File`] is safely closed when it is dropped. + /// + /// The file stream and [`File`] are safely closed when `CodesHandle` is dropped. /// ///## Errors ///Returns [`CodesError::FileHandlingInterrupted`] with [`io::Error`](std::io::Error) @@ -94,9 +93,6 @@ impl CodesHandle { /// ///Returns [`CodesError::Internal`] with error code ///when internal [`codes_handle`](eccodes_sys::codes_handle) cannot be created. - /// - ///Returns [`CodesError::NoMessages`] when there is no message of requested type - ///in the provided file. pub fn new_from_file(file_path: &Path, product_kind: ProductKind) -> Result { let file = OpenOptions::new().read(true).open(file_path)?; let file_pointer = open_with_fdopen(&file)?; @@ -113,33 +109,31 @@ impl CodesHandle { }) } - ///The constructor that takes data of file present in memory in [`Bytes`] format and - ///a requested [`ProductKind`] and returns the [`CodesHandle`] object. + ///Opens data in provided [`Bytes`] buffer as selected [`ProductKind`] and contructs `CodesHandle`. /// ///## Example /// ///``` - ///# async fn run() { - ///# use eccodes::codes_handle::{ProductKind, CodesHandle}; + ///# async fn run() -> anyhow::Result<()> { + ///# use eccodes::{ProductKind, CodesHandle}; ///# ///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)?; + /// # Ok(()) ///# } ///``` /// - ///The function associates the data in memory with a stream + ///The function associates data in memory with a stream ///represented by [`libc::FILE`](https://docs.rs/libc/0.2.101/libc/enum.FILE.html) pointer - ///using [`fmemopen()`](https://man7.org/linux/man-pages/man3/fmemopen.3.html) function. + ///using [`fmemopen()`](https://man7.org/linux/man-pages/man3/fmemopen.3.html). /// - ///The constructor takes a full ownership of the data inside [`Bytes`], + ///The constructor takes full ownership of the data inside [`Bytes`], ///which is safely dropped during the [`CodesHandle`] drop. /// ///## Errors @@ -148,9 +142,6 @@ impl CodesHandle { /// ///Returns [`CodesError::Internal`] with error code ///when internal [`codes_handle`](eccodes_sys::codes_handle) cannot be created. - /// - ///Returns [`CodesError::NoMessages`] when there is no message of requested type - ///in the provided file. pub fn new_from_memory( file_data: Bytes, product_kind: ProductKind, @@ -172,15 +163,43 @@ impl CodesHandle { #[cfg(feature = "experimental_index")] #[cfg_attr(docsrs, doc(cfg(feature = "experimental_index")))] + impl CodesHandle { + /// Creates [`CodesHandle`] for provided [`CodesIndex`]. + /// + /// ## Example + /// + /// ``` + /// # fn run() -> anyhow::Result<()> { + /// # use eccodes::{CodesHandle, CodesIndex}; + /// # + /// let index = CodesIndex::new_from_keys(&vec!["shortName", "typeOfLevel", "level"])?; + /// let handle = CodesHandle::new_from_index(index)?; + /// + /// Ok(()) + /// # } + /// ``` + /// + /// The function takes ownership of the provided [`CodesIndex`] which owns + /// the GRIB data. [`CodesHandle`] created from [`CodesIndex`] is of different type + /// than the one created from file or memory buffer, because it internally uses + /// different functions to access messages. But it can be used in the same way. + /// + /// ⚠️ Warning: This function may interfere with other functions in concurrent context, + /// due to ecCodes issues with thread-safety for indexes. More information can be found + /// in [`codes_index`](crate::codes_index) module documentation. + /// + /// ## Errors + /// + /// Returns [`CodesError::Internal`] with error code + /// when internal [`codes_handle`](eccodes_sys::codes_handle) cannot be created. pub fn new_from_index( index: CodesIndex, - product_kind: ProductKind, ) -> Result { let new_handle = CodesHandle { _data: DataContainer::Empty(), //unused, index owns data source: index, - product_kind, + product_kind: ProductKind::GRIB, unsafe_message: KeyedMessage { message_handle: null_mut(), }, @@ -224,14 +243,15 @@ fn open_with_fmemopen(file_data: &Bytes) -> Result<*mut FILE, CodesError> { Ok(file_ptr) } -/// This trait is neccessary because (1) drop in GribFile/IndexFile cannot -/// be called directly as source cannot be moved out of shared reference -/// and (2) Drop drops fields in arbitrary order leading to fclose() failing +// This trait is neccessary because (1) drop in GribFile/IndexFile cannot +// be called directly as source cannot be moved out of shared reference +// and (2) Drop drops fields in arbitrary order leading to fclose() failing #[doc(hidden)] pub trait SpecialDrop { fn spec_drop(&mut self); } +#[doc(hidden)] impl SpecialDrop for GribFile { fn spec_drop(&mut self) { //fclose() can fail in several different cases, however there is not much @@ -257,6 +277,7 @@ impl SpecialDrop for GribFile { } } +#[doc(hidden)] #[cfg(feature = "experimental_index")] impl SpecialDrop for CodesIndex { fn spec_drop(&mut self) { @@ -268,17 +289,17 @@ impl SpecialDrop for CodesIndex { } } +#[doc(hidden)] impl Drop for CodesHandle { - ///Executes the destructor for this type. - ///This method calls `fclose()` from libc for graceful cleanup. - /// - ///Currently it is assumed that under normal circumstances this destructor never fails. - ///However in some edge cases fclose 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). + /// Executes the destructor for this type. + /// + /// Currently it is assumed that under normal circumstances this destructor never fails. + /// However in some edge cases fclose 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). fn drop(&mut self) { self.source.spec_drop(); } @@ -351,7 +372,7 @@ mod tests { let i_ptr = index.pointer.clone(); - let handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; + let handle = CodesHandle::new_from_index(index)?; assert_eq!(handle.source.pointer, i_ptr); assert!(handle.unsafe_message.message_handle.is_null()); @@ -398,4 +419,24 @@ mod tests { Ok(()) } + + #[test] + #[cfg(feature = "experimental_index")] + fn empty_index_constructor() -> Result<()> { + use fallible_streaming_iterator::FallibleStreamingIterator; + + let index = + CodesIndex::new_from_keys(&vec!["shortName", "typeOfLevel", "level", "stepType"])?; + + let mut handle = CodesHandle::new_from_index(index)?; + + assert!(!handle.source.pointer.is_null()); + assert!(handle.unsafe_message.message_handle.is_null()); + + let msg = handle.next()?; + + assert!(!msg.is_some()); + + Ok(()) + } } diff --git a/src/codes_index.rs b/src/codes_index.rs index 949b7bd..ab71648 100644 --- a/src/codes_index.rs +++ b/src/codes_index.rs @@ -100,6 +100,7 @@ impl Select<&str> for CodesIndex { } } +#[doc(hidden)] impl Drop for CodesIndex { fn drop(&mut self) { self.spec_drop(); diff --git a/src/codes_nearest.rs b/src/codes_nearest.rs index 294cf3e..7198f5d 100644 --- a/src/codes_nearest.rs +++ b/src/codes_nearest.rs @@ -13,29 +13,32 @@ use crate::{ CodesError, KeyedMessage, }; +/// The structure used to find nearest gridpoints in `KeyedMessage`. #[derive(Debug)] pub struct CodesNearest<'a> { nearest_handle: *mut codes_nearest, parent_message: &'a KeyedMessage, } -///The structure returned by [`CodesNearest::find_nearest()`]. -///Should always be analysed in relation to the coordinates requested in `find_nearest()`. +/// The structure returned by [`CodesNearest::find_nearest()`]. +/// Should always be analysed in relation to the coordinates requested in `find_nearest()`. #[derive(Copy, Clone, PartialEq, Debug, Default)] pub struct NearestGridpoint { - ///Index of gridpoint + ///Index of this gridpoint pub index: i32, - ///Latitude in degrees north + ///Latitude of this gridpoint in degrees north pub lat: f64, - ///Longitude in degrees east + ///Longitude of this gridpoint in degrees east pub lon: f64, - ///Distance from coordinates requested in `find_nearest()` + /// Distance between requested point and this gridpoint in kilometers pub distance: f64, - ///Value of the filed at given coordinate + ///Value of parameter at this gridpoint contained by `KeyedMessage` in corresponding units pub value: f64, } impl KeyedMessage { + /// Creates a new instance of [`CodesNearest`] for the `KeyedMessage`. + /// [`CodesNearest`] can be used to find nearest gridpoints for given coordinates in the `KeyedMessage`. pub fn codes_nearest(&self) -> Result { let nearest_handle = unsafe { codes_grib_nearest_new(self.message_handle)? }; @@ -52,11 +55,6 @@ impl CodesNearest<'_> { ///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 /// ///``` @@ -67,10 +65,10 @@ impl CodesNearest<'_> { /// # fn main() -> anyhow::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("no message")?; - /// + /// /// let c_nearest = msg.codes_nearest()?; /// let out = c_nearest.find_nearest(64.13, -21.89)?; /// # Ok(()) @@ -97,6 +95,7 @@ impl CodesNearest<'_> { } } +#[doc(hidden)] impl Drop for CodesNearest<'_> { fn drop(&mut self) { unsafe { diff --git a/src/errors.rs b/src/errors.rs index e9ea81b..9d389fe 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -29,11 +29,6 @@ pub enum CodesError { #[error("Error occured while opening the file: {0}")] FileHandlingInterrupted(#[from] std::io::Error), - ///Returned when the `CodesHandle` constructor did not find - /// any messages of given kind in the file. - #[error("No message have been found in the file")] - NoMessages, - ///Returned when the string cannot be parsed as valid UTF8 string. #[error("Cannot parse string as UTF8: {0}")] CstrUTF8(#[from] std::str::Utf8Error), diff --git a/src/keyed_message/mod.rs b/src/keyed_message/mod.rs index c0198ea..2154975 100644 --- a/src/keyed_message/mod.rs +++ b/src/keyed_message/mod.rs @@ -10,17 +10,17 @@ use std::ptr::null_mut; use crate::{intermediate_bindings::{codes_handle_clone, codes_handle_delete}, CodesError}; -///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()`](crate::codes_nearest::CodesNearest::find_nearest()) allows to get the values of four nearest gridpoints -///to requested coordinates. +/// 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()`](crate::codes_nearest::CodesNearest::find_nearest()) allows to get the values of four nearest gridpoints +/// to requested coordinates. #[derive(Hash, Debug)] pub struct KeyedMessage { pub(crate) message_handle: *mut codes_handle, @@ -50,7 +50,7 @@ pub enum KeyType { } impl KeyedMessage { - ///Custom function to clone the `KeyedMessage`. This function comes with memory overhead. + /// Custom function to clone the `KeyedMessage`. This function comes with memory overhead. /// /// # Errors /// This function will return [`CodesInternal`](crate::errors::CodesInternal) if ecCodes fails to clone the message. @@ -64,6 +64,7 @@ impl KeyedMessage { } } +#[doc(hidden)] impl Drop for KeyedMessage { ///Executes the destructor for this type. ///This method calls destructor functions from ecCodes library. diff --git a/src/keys_iterator.rs b/src/keys_iterator.rs index 2329ab6..6d67f61 100644 --- a/src/keys_iterator.rs +++ b/src/keys_iterator.rs @@ -189,6 +189,7 @@ impl FallibleIterator for KeysIterator<'_> { } } +#[doc(hidden)] impl Drop for KeysIterator<'_> { fn drop(&mut self) { unsafe { diff --git a/tests/index.rs b/tests/index.rs index 2e738e3..4d5efcf 100644 --- a/tests/index.rs +++ b/tests/index.rs @@ -18,7 +18,7 @@ fn iterate_handle_from_index() -> Result<()> { .select("level", 0)? .select("stepType", "instant")?; - let handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; + let handle = CodesHandle::new_from_index(index)?; let counter = handle.count()?; @@ -36,7 +36,7 @@ fn read_index_messages() -> Result<()> { .select("level", 0)? .select("stepType", "instant")?; - let mut handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; + let mut handle = CodesHandle::new_from_index(index)?; let current_message = handle.next()?.context("Message not some")?; { @@ -68,7 +68,7 @@ fn collect_index_iterator() -> Result<()> { .select("typeOfLevel", "isobaricInhPa")? .select("level", 700)?; - let mut handle = CodesHandle::new_from_index(index, ProductKind::GRIB)?; + let mut handle = CodesHandle::new_from_index(index)?; let mut levels = vec![]; @@ -241,7 +241,7 @@ fn index_handle_interference() -> Result<()> { let sleep_time = rng.gen_range(1..42); // randomizing sleep time to hopefully catch segfaults let index = CodesIndex::new_from_keys(&keys)?.add_grib_file(grib_path)?; - let i_handle = CodesHandle::new_from_index(index, ProductKind::GRIB); + let i_handle = CodesHandle::new_from_index(index); assert!(i_handle.is_ok());