From 96207d2529901db9f4048d4966c0eac135738a4c Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Sat, 10 Feb 2024 22:13:42 +0100 Subject: [PATCH] preliminary finish docs --- src/codes_handle/mod.rs | 3 +- src/codes_nearest.rs | 8 +- src/errors.rs | 5 + src/keyed_message/mod.rs | 114 ++++++++++++++------- src/keyed_message/read.rs | 99 +++++++++---------- src/keyed_message/write.rs | 158 +++++++++++++++--------------- src/keys_iterator.rs | 196 +++++++++++++++++++------------------ src/lib.rs | 1 + src/message_ndarray.rs | 6 +- 9 files changed, 331 insertions(+), 259 deletions(-) diff --git a/src/codes_handle/mod.rs b/src/codes_handle/mod.rs index 56662de..139e5d7 100644 --- a/src/codes_handle/mod.rs +++ b/src/codes_handle/mod.rs @@ -36,7 +36,7 @@ pub struct GribFile { /// - From GRIB index using [`new_from_index()`](CodesHandle::new_from_index) (with `experimental_index` feature enabled) /// /// 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`]. +/// leading to bugs. Errors encountered in the destructor are logged with [`log`]. /// /// # `FallibleStreamingIterator` /// @@ -125,6 +125,7 @@ enum DataContainer { ///Used to indicate to ecCodes how it should decode/encode messages. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum ProductKind { + #[allow(missing_docs)] GRIB = ProductKind_PRODUCT_GRIB as isize, } diff --git a/src/codes_nearest.rs b/src/codes_nearest.rs index 7198f5d..ace2a78 100644 --- a/src/codes_nearest.rs +++ b/src/codes_nearest.rs @@ -38,7 +38,13 @@ pub struct NearestGridpoint { 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`. + /// [`CodesNearest`] can be used to find nearest gridpoints for given coordinates in the `KeyedMessage` + /// by calling [`find_nearest()`](crate::CodesNearest::find_nearest). + /// + /// # Errors + /// + /// This function returns [`CodesInternal`](crate::errors::CodesInternal) when + /// internal nearest handle cannot be created. pub fn codes_nearest(&self) -> Result { let nearest_handle = unsafe { codes_grib_nearest_new(self.message_handle)? }; diff --git a/src/errors.rs b/src/errors.rs index 9d389fe..f43c67d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -43,6 +43,11 @@ pub enum CodesError { #[error("The key is missing in present message")] MissingKey, + /// Returned when the size of requested key is lower than 1. + /// This indicates corrupted data file, bug in the crate or bug in the ecCodes library. + #[error("Incorrect key size")] + IncorrectKeySize, + /// Returned when codes_handle_clone returns null pointer /// indicating issues with cloning the message. #[error("Cannot clone the message")] diff --git a/src/keyed_message/mod.rs b/src/keyed_message/mod.rs index 2154975..b0e1b2f 100644 --- a/src/keyed_message/mod.rs +++ b/src/keyed_message/mod.rs @@ -8,55 +8,84 @@ use eccodes_sys::codes_handle; use log::warn; 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. +use crate::{ + intermediate_bindings::{codes_handle_clone, codes_handle_delete}, + CodesError, +}; + +/// Structure that provides access to the data contained in the GRIB file, which directly corresponds to the message in the GRIB file +/// +/// **Usage examples are provided in documentation of each method.** +/// +/// You can think about the message as a container of data corresponding to a single variable +/// at given date, time and level. In ecCodes the message is represented as a collection of unique +/// key-value pairs. Each [`Key`] in the message has a unique name and a value of type [`KeyType`]. +/// +/// You can read a `Key` directly using [`read_key()`](KeyedMessage::read_key()) or iterate over +/// all keys using [`KeysIterator`](crate::KeysIterator). You can also modify the message using +/// [`write_key()`](KeyedMessage::write_key()). As of `0.10`, this crate can successfully read all keys +/// from ERA5 and GFS files. +/// +/// If you are interested only in getting data values from the message you can use +/// [`to_ndarray()`](KeyedMessage::to_ndarray) from the [`message_ndarray`](crate::message_ndarray) module. +/// +/// Some of the useful keys are: `validityDate`, `validityTime`, `level`, `typeOfLevel`, `shortName`, `units` and `values`. +/// +/// Note that names, types and availability of some keys can vary between platforms and ecCodes versions. You should test +/// your code whenever changing the environment. +/// +/// [`CodesNearest`](crate::CodesNearest) can be used to find nearest gridpoints for given coordinates in the `KeyedMessage`. +/// +/// Most of `KeyedMessage` methods (except for writing) can be used directly with `&KeyedMessage` +/// returned by `CodesHandle` iterator, which provides the best performance. +/// When mutable access or longer liftime is needed the message can be cloned with [`try_clone`](KeyedMessage::try_clone) +/// Note that cloning comes with a performance and memory overhead. +/// You should take care that your system has enough memory before cloning. +/// +/// Destructor for this structure does not panic, but some internal functions may rarely fail +/// leading to bugs. Errors encountered in desctructor the are logged with [`log`]. #[derive(Hash, Debug)] pub struct KeyedMessage { pub(crate) message_handle: *mut codes_handle, } -///Structure representing a single key from the `KeyedMessage`. +/// Structure representing a single key in the `KeyedMessage` #[derive(Clone, Debug, PartialEq)] pub struct Key { + #[allow(missing_docs)] pub name: String, + #[allow(missing_docs)] pub value: KeyType, } -///Enum to represent and contain all possible types of keys inside `KeyedMessage`. +/// Enum representing the value of [`Key`] from the `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. +/// Messages inside GRIB files can contain keys of arbitrary types, which are known only at runtime (after being checked). +/// ecCodes can return several different types of key, which are represented by this enum +/// and each variant contains the respective data type. #[derive(Clone, Debug, PartialEq)] pub enum KeyType { + #[allow(missing_docs)] Float(f64), + #[allow(missing_docs)] Int(i64), + #[allow(missing_docs)] FloatArray(Vec), + #[allow(missing_docs)] IntArray(Vec), + #[allow(missing_docs)] Str(String), + #[allow(missing_docs)] Bytes(Vec), } impl KeyedMessage { /// 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. pub fn try_clone(&self) -> Result { - let new_handle = - unsafe { codes_handle_clone(self.message_handle)? }; + let new_handle = unsafe { codes_handle_clone(self.message_handle)? }; Ok(KeyedMessage { message_handle: new_handle, @@ -66,19 +95,19 @@ impl KeyedMessage { #[doc(hidden)] 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. + /// 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). + /// 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. + /// 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| { @@ -98,6 +127,25 @@ mod tests { use std::path::Path; use testing_logger; + #[test] + fn check_docs_keys() -> 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 _ = current_message.read_key("validityDate")?; + let _ = current_message.read_key("validityTime")?; + let _ = current_message.read_key("level")?; + let _ = current_message.read_key("shortName")?; + let _ = current_message.read_key("units")?; + let _ = current_message.read_key("values")?; + let _ = current_message.read_key("typeOfLevel")?; + + Ok(()) + } + #[test] fn message_clone_1() -> Result<()> { let file_path = Path::new("./data/iceland.grib"); diff --git a/src/keyed_message/read.rs b/src/keyed_message/read.rs index 7d700b4..926e27a 100644 --- a/src/keyed_message/read.rs +++ b/src/keyed_message/read.rs @@ -9,57 +9,54 @@ use crate::{ }; impl KeyedMessage { - ///Method to get a [`Key`] with provided name from the `KeyedMessage`. - /// - ///This function takes a key name and returns the key value as [`Key`] - ///if requested key exists. Check the [`Key`] documentation for details - ///of possible key types. - /// - ///## Example - /// - ///``` - /// use eccodes::{ProductKind, CodesHandle, KeyType}; - /// # use std::path::Path; - /// # use anyhow::Context; - /// use eccodes::FallibleStreamingIterator; - /// # - /// # fn main() -> anyhow::Result<()> { - /// let file_path = Path::new("./data/iceland.grib"); - /// let product_kind = ProductKind::GRIB; + /// Method to get a [`Key`] with provided name from the `KeyedMessage`, if it exists. /// - /// let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - /// let message = handle.next()?.context("no message")?; - /// let message_short_name = message.read_key("shortName")?; - /// let expected_short_name = KeyType::Str("msl".to_string()); + /// This function check the type of requested key and tries to read it as the native type. + /// That flow adds performance overhead, but makes the function highly unlikely to fail. /// - /// assert_eq!(message_short_name.value, expected_short_name); - /// # Ok(()) - /// # } - ///``` - /// - ///This function will try to retrieve the key of native string type as string even - ///when the nul byte is not positioned at the end of key value. - /// - ///If retrieving the key value in native type fails this function will try to read - ///the requested key as bytes. - /// - ///## Errors - /// - ///Returns [`CodesInternal::CodesNotFound`](crate::errors::CodesInternal::CodesNotFound) - ///wrapped in [`CodesError::Internal`] when a key of given name has not been found in the message. - /// - ///Returns [`CodesError::MissingKey`] when a given key has a missing type. - /// - ///Returns [`CodesError::Internal`] when one of internal ecCodes functions to read the key fails. - /// - ///Returns [`CodesError::CstrUTF8`] and [`CodesError::NulChar`] when the string returned by ecCodes - ///library cannot be parsed as valid UTF8 Rust string. - /// - ///## Panics - /// - ///Panics when the size of given key is lower than 1. This indicates corrupted data file, - ///bug in the crate or bug in the ecCodes library. If you encounter this panic please check - ///if your file is correct and report it on Github. + /// This function will try to retrieve the key of native string type as string even + /// when the nul byte is not positioned at the end of key value. + /// + /// If retrieving the key value in native type fails this function will try to read + /// the requested key as bytes. + /// + /// # Example + /// + /// ``` + /// use eccodes::{ProductKind, CodesHandle, KeyType}; + /// # use std::path::Path; + /// # use anyhow::Context; + /// use eccodes::FallibleStreamingIterator; + /// # + /// # 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 message = handle.next()?.context("no message")?; + /// let message_short_name = message.read_key("shortName")?; + /// let expected_short_name = KeyType::Str("msl".to_string()); + /// + /// assert_eq!(message_short_name.value, expected_short_name); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns [`CodesNotFound`](crate::errors::CodesInternal::CodesNotFound) + /// when a key of given name has not been found in the message. + /// + /// Returns [`CodesError::MissingKey`] when a given key does not have a specified type. + /// + /// Returns [`CodesError::Internal`] when one of internal ecCodes functions to read the key fails. + /// + /// Returns [`CodesError::CstrUTF8`] and [`CodesError::NulChar`] when the string returned by ecCodes + /// library cannot be parsed as valid UTF8 Rust string. + /// + /// Returns [`CodesError::IncorrectKeySize`] when the size of given key is lower than 1. This indicates corrupted data file, + /// bug in the crate or bug in the ecCodes library. If you encounter this error please check + /// if your file is correct and report it on Github. pub fn read_key(&self, key_name: &str) -> Result { let key_type; @@ -93,7 +90,7 @@ impl KeyedMessage { Err(err) => Err(err), } } else { - panic!("Incorrect key size!"); + return Err(CodesError::IncorrectKeySize) } } NativeKeyType::Double => { @@ -121,7 +118,7 @@ impl KeyedMessage { Err(err) => Err(err), } } else { - panic!("Incorrect key size!"); + return Err(CodesError::IncorrectKeySize) } } NativeKeyType::Bytes => { diff --git a/src/keyed_message/write.rs b/src/keyed_message/write.rs index 62259e4..5fcbe50 100644 --- a/src/keyed_message/write.rs +++ b/src/keyed_message/write.rs @@ -10,43 +10,43 @@ use crate::{ }; impl KeyedMessage { - ///Function to write given `KeyedMessage` to a file at provided path. - ///If file does not exists it will be created. - ///If `append` is set to `true` file will be opened in append mode - ///and no data will be overwritten (useful when writing mutiple messages to one file). - /// - ///## Example - /// - ///``` - /// use eccodes::{CodesHandle, Key, KeyType, ProductKind}; - /// # use eccodes::errors::CodesError; - /// use eccodes::FallibleStreamingIterator; - /// # use std::path::Path; - /// # use std::fs::remove_file; - /// # - /// # fn main() -> anyhow::Result<(), CodesError> { - /// let in_path = Path::new("./data/iceland-levels.grib"); - /// let out_path = Path::new("./data/iceland-800hPa.grib"); - /// - /// let mut handle = CodesHandle::new_from_file(in_path, ProductKind::GRIB)?; - /// - /// while let Some(msg) = handle.next()? { - /// if msg.read_key("level")?.value == KeyType::Int(800) { - /// msg.write_to_file(out_path, true)?; - /// } - /// } - /// # remove_file(out_path)?; - /// # Ok(()) - /// # } - ///``` - /// - ///## Errors - /// - ///Returns [`CodesError::FileHandlingInterrupted`] when the file cannot be opened, - ///created or correctly written. - /// - ///Returns [`CodesInternal`](crate::errors::CodesInternal) - ///when internal ecCodes function returns non-zero code. + /// Function to write given `KeyedMessage` to a file at provided path. + /// If file does not exists it will be created. + /// If `append` is set to `true` file will be opened in append mode + /// and no data will be overwritten (useful when writing mutiple messages to one file). + /// + /// # Example + /// + /// ``` + /// use eccodes::{CodesHandle, Key, KeyType, ProductKind}; + /// # use eccodes::errors::CodesError; + /// use eccodes::FallibleStreamingIterator; + /// # use std::path::Path; + /// # use std::fs::remove_file; + /// # + /// # fn main() -> anyhow::Result<(), CodesError> { + /// let in_path = Path::new("./data/iceland-levels.grib"); + /// let out_path = Path::new("./data/iceland-800hPa.grib"); + /// + /// let mut handle = CodesHandle::new_from_file(in_path, ProductKind::GRIB)?; + /// + /// while let Some(msg) = handle.next()? { + /// if msg.read_key("level")?.value == KeyType::Int(800) { + /// msg.write_to_file(out_path, true)?; + /// } + /// } + /// # remove_file(out_path)?; + /// # Ok(()) + /// # } + /// ``` + /// + /// # Errors + /// + /// Returns [`CodesError::FileHandlingInterrupted`] when the file cannot be opened, + /// created or correctly written. + /// + /// Returns [`CodesInternal`](crate::errors::CodesInternal) + /// when internal ecCodes function returns non-zero code. pub fn write_to_file(&self, file_path: &Path, append: bool) -> Result<(), CodesError> { let msg = unsafe { codes_get_message(self.message_handle)? }; let buf = unsafe { slice::from_raw_parts(msg.0.cast::(), msg.1) }; @@ -61,51 +61,51 @@ impl KeyedMessage { Ok(()) } - ///Function to set specified `Key` inside the `KeyedMessage`. - ///This function automatically matches the `KeyType` and uses adequate - ///internal ecCodes function to set the key. - ///The message must be mutable to use this function. - /// - ///**User must provide the `Key` with correct type**, otherwise - ///error will occur. - ///Note that not all keys can be set, for example - ///`"name"` and `shortName` are read-only. Trying to set such keys - ///will result in error. Some keys can also be set using a non-native - ///type (eg. `centre`), but [`read_key()`](KeyedMessage::read_key()) function will only read then - ///in native type. - /// - ///Refer to [ecCodes library documentation](https://confluence.ecmwf.int/display/ECC/ecCodes+Home) - ///for more details. - /// - ///## Example - /// - ///``` - /// use eccodes::{CodesHandle, Key, KeyType, ProductKind}; - /// # use eccodes::errors::CodesError; - /// use eccodes::FallibleStreamingIterator; - /// # use anyhow::Context; - /// # use std::path::Path; - /// # - /// # fn main() -> anyhow::Result<()> { - /// let file_path = Path::new("./data/iceland.grib"); + /// Function to set specified `Key` inside the `KeyedMessage`. + /// This function automatically matches the `KeyType` and uses adequate + /// internal ecCodes function to set the key. + /// The message must be mutable to use this function. + /// + /// **User must provide the `Key` with correct type**, otherwise + /// error will occur. + /// Note that not all keys can be set, for example + /// `"name"` and `shortName` are read-only. Trying to set such keys + /// will result in error. Some keys can also be set using a non-native + /// type (eg. `centre`), but [`read_key()`](KeyedMessage::read_key()) function will only read them + /// in native type. + /// + /// Refer to [ecCodes library documentation](https://confluence.ecmwf.int/display/ECC/ecCodes+Home) + /// for more details. + /// + /// # Example /// - /// let mut handle = CodesHandle::new_from_file(file_path, ProductKind::GRIB)?; - /// let mut current_message = handle.next()?.context("no message")?.try_clone()?; + /// ``` + /// use eccodes::{CodesHandle, Key, KeyType, ProductKind}; + /// # use eccodes::errors::CodesError; + /// use eccodes::FallibleStreamingIterator; + /// # use anyhow::Context; + /// # use std::path::Path; + /// # + /// # fn main() -> anyhow::Result<()> { + /// let file_path = Path::new("./data/iceland.grib"); + /// + /// let mut handle = CodesHandle::new_from_file(file_path, ProductKind::GRIB)?; + /// let mut current_message = handle.next()?.context("no message")?.try_clone()?; + /// + /// let new_key = Key { + /// name: "centre".to_string(), + /// value: KeyType::Str("cnmc".to_string()), + /// }; + /// + /// current_message.write_key(new_key)?; + /// # Ok(()) + /// # } + /// ``` /// - /// let new_key = Key { - /// name: "centre".to_string(), - /// value: KeyType::Str("cnmc".to_string()), - /// }; + /// # Errors /// - /// current_message.write_key(new_key)?; - /// # Ok(()) - /// # } - ///``` - /// - ///## Errors - /// - ///This method will return [`CodesInternal`](crate::errors::CodesInternal) - ///when internal ecCodes function returns non-zero code. + /// This method will return [`CodesInternal`](crate::errors::CodesInternal) + /// when internal ecCodes function returns non-zero code. pub fn write_key(&mut self, key: Key) -> Result<(), CodesError> { match key.value { KeyType::Float(val) => unsafe { diff --git a/src/keys_iterator.rs b/src/keys_iterator.rs index 6d67f61..61c0e2d 100644 --- a/src/keys_iterator.rs +++ b/src/keys_iterator.rs @@ -14,6 +14,48 @@ use crate::{ Key, KeyedMessage, }; +/// Structure to iterate through keys in [`KeyedMessage`]. +/// +/// Mainly useful to discover what keys are present inside the message. +/// +/// Implements [`FallibleIterator`] providing similar functionality to classic `Iterator`. +/// `FallibleIterator` is used because internal ecCodes functions can return internal error in some edge-cases. +/// The usage of `FallibleIterator` is sligthly different than usage of `Iterator`, +/// check the documentation for more details. +/// +/// The `next()` function internally calls [`read_key()`](KeyedMessage::read_key()) function +/// so it is usually more efficient to call that function directly only for keys you +/// are interested in. +/// +/// ## Example +/// +/// ``` +/// use eccodes::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags, KeyType}; +/// # use std::path::Path; +/// # use anyhow::Context; +/// use eccodes::{FallibleIterator, FallibleStreamingIterator}; +/// # +/// # 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 current_message = handle.next()?.context("no message")?; +/// +/// let mut keys_iter = current_message.default_keys_iterator()?; +/// +/// while let Some(key) = keys_iter.next()? { +/// println!("{:?}", key); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Errors +/// +/// The `next()` method will return [`CodesInternal`](crate::errors::CodesInternal) +/// when internal ecCodes function returns non-zero code. #[allow(clippy::module_name_repetitions)] #[derive(Debug)] pub struct KeysIterator<'a> { @@ -22,78 +64,78 @@ pub struct KeysIterator<'a> { next_item_exists: bool, } -///Flags to specify the subset of keys to iterate over -///by `FallibleIterator` in `KeyedMessage`. The flags can be used together. +/// Flags to specify the subset of keys to iterate over in +/// `KeysIterator`. Flags can be combined as needed. #[allow(clippy::module_name_repetitions)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub enum KeysIteratorFlags { - ///Iterate over all keys + /// Iterate over all keys AllKeys = eccodes_sys::CODES_KEYS_ITERATOR_ALL_KEYS as isize, - ///Iterate only dump keys + /// Iterate only over dump keys DumpOnly = eccodes_sys::CODES_KEYS_ITERATOR_DUMP_ONLY as isize, - ///Exclude coded keys from iteration + /// Exclude coded keys from iteration SkipCoded = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_CODED as isize, - ///Exclude computed keys from iteration + /// Exclude computed keys from iteration SkipComputed = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_COMPUTED as isize, - ///Exclude function keys from iteration + /// Exclude function keys from iteration SkipFunction = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_FUNCTION as isize, - ///Exclude optional keys from iteration + /// Exclude optional keys from iteration SkipOptional = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_OPTIONAL as isize, - ///Exclude read-only keys from iteration + /// Exclude read-only keys from iteration SkipReadOnly = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_READ_ONLY as isize, - ///Exclude duplicate keys from iteration + /// Exclude duplicate keys from iteration SkipDuplicates = eccodes_sys::CODES_KEYS_ITERATOR_SKIP_DUPLICATES as isize, - ///Exclude file edition specific keys from iteration + /// 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::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags, KeyType}; - /// # use std::path::Path; - /// # use anyhow::Context; - /// use eccodes::{FallibleIterator, FallibleStreamingIterator}; - /// # - /// # fn main() -> anyhow::Result<()> { - /// # - /// let file_path = Path::new("./data/iceland.grib"); - /// let product_kind = ProductKind::GRIB; + /// Creates new [`KeysIterator`] for the message with specified flags and namespace. /// - /// let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; - /// let current_message = handle.next()?.context("no message")?; + /// The flags are set by providing any combination of [`KeysIteratorFlags`] + /// inside a slice. Check the documentation for the details of each flag meaning. /// - /// let flags = vec![ - /// KeysIteratorFlags::AllKeys, - /// KeysIteratorFlags::SkipOptional, - /// KeysIteratorFlags::SkipReadOnly, - /// KeysIteratorFlags::SkipDuplicates, - /// ]; + /// Namespace is set simply as string, eg. `"ls"`, `"time"`, `"parameter"`, `"geography"`, `"statistics"`. + /// Invalid namespace will result in empty iterator. /// - /// let namespace = "geography"; + /// # Example /// - /// let mut keys_iter = current_message.new_keys_iterator(&flags, namespace)?; + /// ``` + /// use eccodes::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags, KeyType}; + /// # use std::path::Path; + /// # use anyhow::Context; + /// use eccodes::{FallibleIterator, FallibleStreamingIterator}; + /// # + /// # 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 current_message = handle.next()?.context("no message")?; + /// + /// let flags = [ + /// KeysIteratorFlags::AllKeys, + /// KeysIteratorFlags::SkipOptional, + /// KeysIteratorFlags::SkipReadOnly, + /// KeysIteratorFlags::SkipDuplicates, + /// ]; + /// + /// let namespace = "geography"; + /// + /// let mut keys_iter = current_message.new_keys_iterator(&flags, namespace)?; + /// + /// while let Some(key) = keys_iter.next()? { + /// println!("{:?}", key); + /// } + /// # Ok(()) + /// # } + /// ``` /// - /// while let Some(key) = keys_iter.next()? { - /// println!("{:?}", key); - /// } - /// # Ok(()) - /// # } - ///``` + /// # Errors + /// + /// This function returns [`CodesInternal`](crate::errors::CodesInternal) when + /// internal ecCodes function returns non-zero code. pub fn new_keys_iterator( &self, flags: &[KeysIteratorFlags], @@ -112,6 +154,14 @@ impl KeyedMessage { }) } + /// Same as [`new_keys_iterator()`](KeyedMessage::new_keys_iterator) but with default + /// parameters: [`AllKeys`](KeysIteratorFlags::AllKeys) flag and `""` namespace, + /// yeilding iterator over all keys in the message. + /// + /// # Errors + /// + /// This function returns [`CodesInternal`](crate::errors::CodesInternal) when + /// internal ecCodes function returns non-zero code. 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)? }; @@ -124,46 +174,6 @@ impl KeyedMessage { } } -///`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 -///so it is probably more efficient to call that function directly only for keys you -///are interested in. -/// -///[`FallibleIterator`] is used instead of classic `Iterator` -///because internal ecCodes functions can return internal error in some edge-cases. -///The usage of `FallibleIterator` is sligthly different than usage of `Iterator`, -///check its documentation for more details. -/// -///## Example -/// -///``` -/// use eccodes::{ProductKind, CodesHandle, KeyedMessage, KeysIteratorFlags, KeyType}; -/// # use std::path::Path; -/// # use anyhow::Context; -/// use eccodes::{FallibleIterator, FallibleStreamingIterator}; -/// # -/// # 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 current_message = handle.next()?.context("no message")?; -/// -/// let mut keys_iter = current_message.default_keys_iterator()?; -/// -/// while let Some(key) = keys_iter.next()? { -/// println!("{:?}", key); -/// } -/// # Ok(()) -/// # } -///``` -/// -///## Errors -///The `next()` method will return [`CodesInternal`](crate::errors::CodesInternal) -///when internal ecCodes function returns non-zero code. impl FallibleIterator for KeysIterator<'_> { type Item = Key; type Error = CodesError; @@ -223,7 +233,7 @@ mod tests { let mut handle = CodesHandle::new_from_file(file_path, product_kind)?; let current_message = handle.next()?.context("Message not some")?; - let flags = vec![ + let flags = [ KeysIteratorFlags::AllKeys, //0 KeysIteratorFlags::SkipOptional, //2 KeysIteratorFlags::SkipReadOnly, //1 diff --git a/src/lib.rs b/src/lib.rs index 9fa5734..7f6d740 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![warn(clippy::pedantic)] #![allow(clippy::cast_possible_wrap)] +#![warn(missing_docs)] //! # Unofficial high-level safe Rust bindings to ecCodes library //! diff --git a/src/message_ndarray.rs b/src/message_ndarray.rs index 09a8be9..27d6e74 100644 --- a/src/message_ndarray.rs +++ b/src/message_ndarray.rs @@ -5,12 +5,16 @@ use ndarray::{s, Array2, Array3}; use crate::{errors::MessageNdarrayError, CodesError, KeyType, KeyedMessage}; -/// Struct returned by [`KeyedMessage::to_lons_lats_values()`] method +/// Struct returned by [`KeyedMessage::to_lons_lats_values()`] method. +/// The arrays are collocated, meaning that `longitudes[i, j]` and `latitudes[i, j]` are the coordinates of `values[i, j]`. #[derive(Clone, PartialEq, Debug, Default)] #[cfg_attr(docsrs, doc(cfg(feature = "message_ndarray")))] pub struct RustyCodesMessage { + /// Longitudes in degrees pub longitudes: Array2, + /// Latitudes in degrees pub latitudes: Array2, + /// Values in native GRIB units pub values: Array2, }