eccodes/codes_message/
read.rs

1use std::{cmp::Ordering, fmt::Debug};
2
3use crate::{
4    codes_message::CodesMessage,
5    errors::CodesError,
6    intermediate_bindings::{
7        NativeKeyType, codes_get_bytes, codes_get_double, codes_get_double_array, codes_get_long,
8        codes_get_long_array, codes_get_native_type, codes_get_size, codes_get_string,
9    },
10};
11
12/// Provides GRIB key reading capabilites. Implemented by [`CodesMessage`] for all possible key types.
13pub trait KeyRead<T> {
14    /// Tries to read a key of given name from [`CodesMessage`]. This function checks if key native type
15    /// matches the requested type (ie. you cannot read integer as string, or array as a number).
16    ///
17    /// # Example
18    ///
19    /// ```
20    ///  # use eccodes::{CodesFile, KeyRead, FallibleIterator, ProductKind};
21    ///  # use anyhow::Context;
22    ///  #
23    ///  # fn main() -> anyhow::Result<()> {
24    ///  #
25    ///  let mut file = CodesFile::new_from_file("./data/iceland.grib", ProductKind::GRIB)?;
26    ///  let message = file.ref_message_iter().next()?.context("no message")?;
27    ///  let short_name: String = message.read_key("shortName")?;
28    ///  
29    ///  assert_eq!(short_name, "msl");
30    ///  # Ok(())
31    ///  # }
32    /// ```
33    ///
34    /// # Errors
35    ///
36    /// Returns [`WrongRequestedKeySize`](CodesError::WrongRequestedKeyType) when trying to read key in non-native type (use [`unchecked`](KeyRead::read_key_unchecked) instead).
37    ///
38    /// Returns [`WrongRequestedKeySize`](CodesError::WrongRequestedKeySize) when trying to read array as integer.
39    ///
40    /// Returns [`IncorrectKeySize`](CodesError::IncorrectKeySize) when key size is 0. This can indicate corrupted data.
41    ///
42    /// This function will return [`CodesInternal`](crate::errors::CodesInternal) if ecCodes fails to read the key.
43    fn read_key(&self, name: &str) -> Result<T, CodesError>;
44
45    /// Skips all the checks provided by [`read_key`](KeyRead::read_key) and directly calls ecCodes, ensuring only memory and type safety.
46    ///
47    /// This function has better perfomance than [`read_key`](KeyRead::read_key) but all error handling and (possible)
48    /// type conversions are performed directly by ecCodes.
49    ///
50    /// This function is also useful for (not usually used) keys that return incorrect native type.
51    ///
52    /// # Errors
53    ///
54    /// This function will return [`CodesInternal`](crate::errors::CodesInternal) if ecCodes fails to read the key.
55    fn read_key_unchecked(&self, name: &str) -> Result<T, CodesError>;
56}
57
58#[doc(hidden)]
59pub trait KeyPropertiesRead {
60    fn get_key_size(&self, key_name: &str) -> Result<usize, CodesError>;
61    fn get_key_native_type(&self, key_name: &str) -> Result<NativeKeyType, CodesError>;
62}
63
64impl<P: Debug> KeyPropertiesRead for CodesMessage<P> {
65    fn get_key_size(&self, key_name: &str) -> Result<usize, CodesError> {
66        unsafe { codes_get_size(self.message_handle, key_name) }
67    }
68
69    fn get_key_native_type(&self, key_name: &str) -> Result<NativeKeyType, CodesError> {
70        unsafe { codes_get_native_type(self.message_handle, key_name) }
71    }
72}
73
74macro_rules! impl_key_read {
75    ($key_sizing:ident, $ec_func:ident, $key_variant:path, $gen_type:ty) => {
76        impl<P: Debug> KeyRead<$gen_type> for CodesMessage<P> {
77            fn read_key_unchecked(&self, key_name: &str) -> Result<$gen_type, CodesError> {
78                unsafe { $ec_func(self.message_handle, key_name) }
79            }
80
81            fn read_key(&self, key_name: &str) -> Result<$gen_type, CodesError> {
82                match self.get_key_native_type(key_name)? {
83                    $key_variant => (),
84                    _ => return Err(CodesError::WrongRequestedKeyType),
85                }
86
87                let key_size = self.get_key_size(key_name)?;
88
89                key_size_check!($key_sizing, key_size);
90
91                self.read_key_unchecked(key_name)
92            }
93        }
94    };
95}
96
97macro_rules! key_size_check {
98    // size_var is needed because of macro hygiene
99    (scalar, $size_var:ident) => {
100        match $size_var.cmp(&1) {
101            Ordering::Greater => return Err(CodesError::WrongRequestedKeySize),
102            Ordering::Less => return Err(CodesError::IncorrectKeySize),
103            Ordering::Equal => (),
104        }
105    };
106
107    (array, $size_var:ident) => {
108        if $size_var < 1 {
109            return Err(CodesError::IncorrectKeySize);
110        }
111    };
112}
113
114impl_key_read!(scalar, codes_get_long, NativeKeyType::Long, i64);
115impl_key_read!(scalar, codes_get_double, NativeKeyType::Double, f64);
116impl_key_read!(array, codes_get_string, NativeKeyType::Str, String);
117impl_key_read!(array, codes_get_bytes, NativeKeyType::Bytes, Vec<u8>);
118impl_key_read!(array, codes_get_long_array, NativeKeyType::Long, Vec<i64>);
119impl_key_read!(
120    array,
121    codes_get_double_array,
122    NativeKeyType::Double,
123    Vec<f64>
124);
125
126/// Enum of types GRIB key can have.
127///
128/// Messages inside GRIB files can contain keys of arbitrary types, which are known only at runtime (after being checked).
129/// ecCodes can return several different types of key, which are represented by this enum
130/// and each variant contains the respective data type.
131#[derive(Clone, Debug, PartialEq)]
132pub enum DynamicKeyType {
133    #[allow(missing_docs)]
134    Float(f64),
135    #[allow(missing_docs)]
136    Int(i64),
137    #[allow(missing_docs)]
138    FloatArray(Vec<f64>),
139    #[allow(missing_docs)]
140    IntArray(Vec<i64>),
141    #[allow(missing_docs)]
142    Str(String),
143    #[allow(missing_docs)]
144    Bytes(Vec<u8>),
145}
146
147impl<P: Debug> CodesMessage<P> {
148    /// Method to get a value of given key with [`DynamicKeyType`] from the `CodesMessage`, if it exists.
149    ///
150    /// In most cases you should use [`read_key()`](KeyRead::read_key) due to more predictive behaviour
151    /// and simpler interface.
152    ///
153    /// This function exists for backwards compatibility and user convienience.
154    ///
155    /// This function checks the type of requested key and tries to read it as the native type.
156    /// That flow adds performance overhead, but makes the function highly unlikely to fail.
157    ///
158    /// This function will try to retrieve the key of native string type as string even
159    /// when the nul byte is not positioned at the end of key value.
160    ///
161    /// If retrieving the key value in native type fails this function will try to read
162    /// the requested key as bytes.
163    ///
164    /// # Example
165    ///
166    /// ```
167    ///  use eccodes::{ProductKind, CodesFile, DynamicKeyType, FallibleIterator};
168    ///  # use anyhow::Context;
169    ///  #
170    ///  # fn main() -> anyhow::Result<()> {
171    ///  #
172    ///  let mut file = CodesFile::new_from_file("./data/iceland.grib", ProductKind::GRIB)?;
173    ///  let message = file.ref_message_iter().next()?.context("no message")?;
174    ///  let message_short_name = message.read_key_dynamic("shortName")?;
175    ///  let expected_short_name = DynamicKeyType::Str("msl".to_string());
176    ///  
177    ///  assert_eq!(message_short_name, expected_short_name);
178    ///  # Ok(())
179    ///  # }
180    /// ```
181    ///
182    /// # Errors
183    ///
184    /// Returns [`CodesNotFound`](crate::errors::CodesInternal::CodesNotFound)
185    /// when a key of given name has not been found in the message.
186    ///
187    /// Returns [`CodesError::MissingKey`] when a given key does not have a specified type.
188    ///
189    /// Returns [`CodesError::Internal`] when one of internal ecCodes functions to read the key fails.
190    ///
191    /// Returns [`CodesError::CstrUTF8`] and [`CodesError::NulChar`] when the string returned by ecCodes
192    /// library cannot be parsed as valid UTF8 Rust string.
193    ///
194    /// Returns [`CodesError::IncorrectKeySize`] when the size of given key is lower than 1. This indicates corrupted data file,
195    /// bug in the crate or bug in the ecCodes library. If you encounter this error please check
196    /// if your file is correct and report it on Github.
197    pub fn read_key_dynamic(&self, key_name: &str) -> Result<DynamicKeyType, CodesError> {
198        let key_type = self.get_key_native_type(key_name)?;
199        let key_size = self.get_key_size(key_name)?;
200
201        match key_type {
202            NativeKeyType::Long => {
203                if key_size == 1 {
204                    self.read_key_unchecked(key_name).map(DynamicKeyType::Int)
205                } else if key_size >= 2 {
206                    self.read_key_unchecked(key_name)
207                        .map(DynamicKeyType::IntArray)
208                } else {
209                    return Err(CodesError::IncorrectKeySize);
210                }
211            }
212            NativeKeyType::Double => {
213                if key_size == 1 {
214                    self.read_key_unchecked(key_name).map(DynamicKeyType::Float)
215                } else if key_size >= 2 {
216                    self.read_key_unchecked(key_name)
217                        .map(DynamicKeyType::FloatArray)
218                } else {
219                    return Err(CodesError::IncorrectKeySize);
220                }
221            }
222            NativeKeyType::Bytes => self.read_key_unchecked(key_name).map(DynamicKeyType::Bytes),
223            NativeKeyType::Missing => return Err(CodesError::MissingKey),
224            _ => self.read_key_unchecked(key_name).map(DynamicKeyType::Str),
225        }
226        .or_else(|_| self.read_key_unchecked(key_name).map(DynamicKeyType::Bytes))
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use anyhow::{Context, Result};
233
234    use crate::codes_file::{CodesFile, ProductKind};
235    use crate::{CodesError, KeyRead};
236    use crate::{FallibleIterator, codes_message::DynamicKeyType};
237    use std::path::Path;
238
239    #[test]
240    fn key_reader() -> Result<()> {
241        let file_path = Path::new("./data/iceland.grib");
242        let product_kind = ProductKind::GRIB;
243
244        let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
245
246        let current_message = handle
247            .ref_message_iter()
248            .next()?
249            .context("Message not some")?;
250
251        let str_key = current_message.read_key_dynamic("name")?;
252        match str_key {
253            DynamicKeyType::Str(_) => {}
254            _ => panic!("Incorrect variant of string key"),
255        }
256
257        let double_key = current_message.read_key_dynamic("jDirectionIncrementInDegrees")?;
258        match double_key {
259            DynamicKeyType::Float(_) => {}
260            _ => panic!("Incorrect variant of double key"),
261        }
262
263        let long_key = current_message.read_key_dynamic("numberOfPointsAlongAParallel")?;
264        match long_key {
265            DynamicKeyType::Int(_) => {}
266            _ => panic!("Incorrect variant of long key"),
267        }
268
269        let double_arr_key = current_message.read_key_dynamic("values")?;
270        match double_arr_key {
271            DynamicKeyType::FloatArray(_) => {}
272            _ => panic!("Incorrect variant of double array key"),
273        }
274
275        Ok(())
276    }
277
278    #[test]
279    fn era5_keys_dynamic() -> Result<()> {
280        let file_path = Path::new("./data/iceland.grib");
281        let product_kind = ProductKind::GRIB;
282
283        let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
284        let mut current_message = handle
285            .ref_message_iter()
286            .next()?
287            .context("Message not some")?;
288        let key_names = current_message
289            .default_keys_iterator()?
290            // this checks if iterator isn't infinite
291            .map(|kn| {
292                assert!(!kn.is_empty());
293                Ok(kn)
294            })
295            .collect::<Vec<_>>()?;
296
297        key_names
298            .iter()
299            .for_each(|kn| assert!(current_message.read_key_dynamic(kn).is_ok()));
300
301        Ok(())
302    }
303
304    #[test]
305    fn gfs_keys_dynamic() -> Result<()> {
306        let file_path = Path::new("./data/gfs.grib");
307        let product_kind = ProductKind::GRIB;
308
309        let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
310        let mut current_message = handle
311            .ref_message_iter()
312            .next()?
313            .context("Message not some")?;
314        let key_names = current_message
315            .default_keys_iterator()?
316            // this checks if iterator isn't infinite
317            .map(|kn| {
318                assert!(!kn.is_empty());
319                Ok(kn)
320            })
321            .collect::<Vec<_>>()?;
322
323        key_names
324            .iter()
325            .for_each(|kn| assert!(current_message.read_key_dynamic(kn).is_ok()));
326
327        Ok(())
328    }
329
330    #[test]
331    fn missing_key() -> Result<()> {
332        let file_path = Path::new("./data/iceland.grib");
333        let product_kind = ProductKind::GRIB;
334
335        let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
336        let current_message = handle
337            .ref_message_iter()
338            .next()?
339            .context("Message not some")?;
340
341        let missing_key = current_message.read_key_dynamic("doesNotExist");
342
343        assert!(missing_key.is_err());
344
345        Ok(())
346    }
347
348    #[test]
349    fn incorrect_key_type() -> Result<()> {
350        let mut handle = CodesFile::new_from_file("./data/iceland.grib", ProductKind::GRIB)?;
351        let current_message = handle
352            .ref_message_iter()
353            .next()?
354            .context("Message not some")?;
355
356        let missing_key: Result<f64, CodesError> = current_message.read_key("shortName");
357
358        assert!(missing_key.is_err());
359
360        Ok(())
361    }
362
363    #[test]
364    // checks if we can read keys that are used in benchmarks
365    fn benchmark_keys() -> Result<()> {
366        let file_path = Path::new("./data/iceland.grib");
367        let product_kind = ProductKind::GRIB;
368
369        let mut handle = CodesFile::new_from_file(file_path, product_kind)?;
370
371        let msg = handle
372            .ref_message_iter()
373            .next()?
374            .context("Message not some")?;
375
376        let _ = msg.read_key_dynamic("dataDate")?;
377        let _ = msg.read_key_dynamic("jDirectionIncrementInDegrees")?;
378        let _ = msg.read_key_dynamic("values")?;
379        let _ = msg.read_key_dynamic("name")?;
380        let _ = msg.read_key_dynamic("section1Padding")?;
381        let _ = msg.read_key_dynamic("experimentVersionNumber")?;
382        let _ = msg
383            .read_key_dynamic("zero")
384            .unwrap_or_else(|_| msg.read_key_dynamic("zeros").unwrap());
385
386        Ok(())
387    }
388}