From d125717b0916ec6717d4fd009a6ba41acb7a66a0 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 01/15] upgrade the GH actions ubuntu runner due to too old eccodes --- .github/workflows/rust.yml | 112 ++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dbd4f5a..574078f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -3,10 +3,10 @@ name: cargo on: push: branches: - - main + - main pull_request: branches: - - main + - main env: CARGO_TERM_COLOR: always @@ -14,18 +14,18 @@ env: jobs: pre_job: continue-on-error: true - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: should_skip: ${{ steps.skip_check.outputs.should_skip }} steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@v5 - with: - # All of these options are optional, so you can remove them if you are happy with the defaults - concurrent_skipping: "same_content_newer" - skip_after_successful_duplicate: "true" - paths_ignore: '["**/README.md"]' - do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5 + with: + # All of these options are optional, so you can remove them if you are happy with the defaults + concurrent_skipping: "same_content_newer" + skip_after_successful_duplicate: "true" + paths_ignore: '["**/README.md"]' + do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' build: name: Build on Ubuntu @@ -34,30 +34,30 @@ jobs: runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v4 - - name: Prepare environment - run: | - sudo apt-get update - sudo apt-get install clang - sudo apt-get install libclang1 - sudo apt-get install libeccodes-dev - rustup update stable - cargo install cargo-criterion - cargo clean - - name: Check release build - run: | - cargo build --release --no-default-features - cargo build --release --features "ndarray" - - name: Check with clippy - run: | - cargo clippy --features "ndarray" -- -D warnings - - name: Check tests - run: | - cargo test --no-default-features - cargo test --features "ndarray" - - name: Benchmark with criterion - run: | - cargo criterion + - uses: actions/checkout@v4 + - name: Prepare environment + run: | + sudo apt-get update + sudo apt-get install clang + sudo apt-get install libclang1 + sudo apt-get install libeccodes-dev + rustup update stable + cargo install cargo-criterion + cargo clean + - name: Check release build + run: | + cargo build --release --no-default-features + cargo build --release --features "ndarray" + - name: Check with clippy + run: | + cargo clippy --features "ndarray" -- -D warnings + - name: Check tests + run: | + cargo test --no-default-features + cargo test --features "ndarray" + - name: Benchmark with criterion + run: | + cargo criterion build-macos: name: Build on MacOS @@ -66,24 +66,24 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v4 - - name: Prepare environment - run: | - brew install eccodes - rustup update stable - cargo install cargo-criterion - cargo clean - - name: Check release build - run: | - cargo build --release --no-default-features - cargo build --release --features "ndarray" - - name: Check with clippy - run: | - cargo clippy --features "ndarray" -- -D warnings - - name: Check tests - run: | - cargo test --no-default-features - cargo test --features "ndarray" - - name: Benchmark with criterion - run: | - cargo criterion + - uses: actions/checkout@v4 + - name: Prepare environment + run: | + brew install eccodes + rustup update stable + cargo install cargo-criterion + cargo clean + - name: Check release build + run: | + cargo build --release --no-default-features + cargo build --release --features "ndarray" + - name: Check with clippy + run: | + cargo clippy --features "ndarray" -- -D warnings + - name: Check tests + run: | + cargo test --no-default-features + cargo test --features "ndarray" + - name: Benchmark with criterion + run: | + cargo criterion From 60411d0932b07a31383d300f5c72099abafef1f2 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 02/15] measure static reading in benchmarks instead of dynamic as static read is the primary supported function --- benches/main.rs | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/benches/main.rs b/benches/main.rs index 73f62c1..9285990 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -1,6 +1,6 @@ use criterion::{Criterion, criterion_group, criterion_main}; -use eccodes::{FallibleIterator, KeyRead}; use eccodes::codes_file::{CodesFile, ProductKind}; +use eccodes::{FallibleIterator, KeyRead}; use std::hint::black_box; use std::path::Path; @@ -14,48 +14,30 @@ pub fn key_reading(c: &mut Criterion) { let msg = handle.ref_message_iter().next().unwrap().unwrap(); c.bench_function("long reading", |b| { - b.iter(|| msg.read_key_dynamic(black_box("dataDate")).unwrap()) + b.iter(|| -> i64 { msg.read_key(black_box("dataDate")).unwrap() }) }); c.bench_function("double reading", |b| { - b.iter(|| { - msg.read_key_dynamic(black_box("jDirectionIncrementInDegrees")) + b.iter(|| -> f64 { + msg.read_key(black_box("jDirectionIncrementInDegrees")) .unwrap() }) }); - c.bench_function("double array reading", |b| { - b.iter(|| msg.read_key_dynamic(black_box("values")).unwrap()) - }); - - c.bench_function("static double array reading", |b| { - b.iter(|| -> Vec { msg.read_key(black_box("values")).unwrap() }) - }); - - c.bench_function("static float array reading", |b| { - b.iter(|| -> Vec { msg.read_key(black_box("values")).unwrap() }) - }); - c.bench_function("string reading", |b| { - b.iter(|| msg.read_key_dynamic(black_box("name")).unwrap()) + b.iter(|| -> String { msg.read_key(black_box("name")).unwrap() }) }); - c.bench_function("bytes reading", |b| { - b.iter(|| msg.read_key_dynamic(black_box("section1Padding")).unwrap()) + c.bench_function("long array reading", |b| { + b.iter(|| -> Vec { msg.read_key(black_box("values")).unwrap() }) }); - c.bench_function("missing nul-byte termination reading", |b| { - b.iter(|| { - msg.read_key_dynamic(black_box("experimentVersionNumber")) - .unwrap() - }) + c.bench_function("float array reading", |b| { + b.iter(|| -> Vec { msg.read_key(black_box("values")).unwrap() }) }); - c.bench_function("problematic key reading", |b| { - b.iter(|| { - msg.read_key_dynamic(black_box("zero")) - .unwrap_or_else(|_| msg.read_key_dynamic(black_box("zeros")).unwrap()) - }) + c.bench_function("double array reading", |b| { + b.iter(|| -> Vec { msg.read_key(black_box("values")).unwrap() }) }); } From 609b6aa35aeb4b15447dcac86c66e9d9b6d66cd3 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 03/15] reorder float array functions --- src/codes_message/read.rs | 7 +++---- src/intermediate_bindings/codes_get.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/codes_message/read.rs b/src/codes_message/read.rs index 1254d07..8423a8d 100644 --- a/src/codes_message/read.rs +++ b/src/codes_message/read.rs @@ -114,16 +114,15 @@ macro_rules! key_size_check { impl_key_read!(scalar, codes_get_long, NativeKeyType::Long, i64); impl_key_read!(scalar, codes_get_double, NativeKeyType::Double, f64); -// Double-typed ecCodes arrays can be decoded directly into f32. +impl_key_read!(array, codes_get_string, NativeKeyType::Str, String); +impl_key_read!(array, codes_get_bytes, NativeKeyType::Bytes, Vec); +impl_key_read!(array, codes_get_long_array, NativeKeyType::Long, Vec); impl_key_read!( array, codes_get_float_array, NativeKeyType::Double, Vec ); -impl_key_read!(array, codes_get_string, NativeKeyType::Str, String); -impl_key_read!(array, codes_get_bytes, NativeKeyType::Bytes, Vec); -impl_key_read!(array, codes_get_long_array, NativeKeyType::Long, Vec); impl_key_read!( array, codes_get_double_array, diff --git a/src/intermediate_bindings/codes_get.rs b/src/intermediate_bindings/codes_get.rs index 7380254..c6a796a 100644 --- a/src/intermediate_bindings/codes_get.rs +++ b/src/intermediate_bindings/codes_get.rs @@ -71,19 +71,19 @@ pub unsafe fn codes_get_double(handle: *const codes_handle, key: &str) -> Result } } -pub unsafe fn codes_get_double_array( +pub unsafe fn codes_get_float_array( handle: *const codes_handle, key: &str, -) -> Result, CodesError> { +) -> Result, CodesError> { unsafe { 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 mut key_values: Vec = vec![0.0; key_size]; - let error_code = eccodes_sys::codes_get_double_array( + let error_code = eccodes_sys::codes_get_float_array( handle, key.as_ptr(), key_values.as_mut_ptr().cast(), @@ -95,19 +95,19 @@ pub unsafe fn codes_get_double_array( } } -pub unsafe fn codes_get_float_array( +pub unsafe fn codes_get_double_array( handle: *const codes_handle, key: &str, -) -> Result, CodesError> { +) -> Result, CodesError> { unsafe { 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 mut key_values: Vec = vec![0.0; key_size]; - let error_code = eccodes_sys::codes_get_float_array( + let error_code = eccodes_sys::codes_get_double_array( handle, key.as_ptr(), key_values.as_mut_ptr().cast(), From 3a29a768114ddd6c92f88631db5d51da062f7cb3 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 04/15] add reading of keys as f32 --- benches/main.rs | 7 +++++++ src/codes_message/read.rs | 3 ++- src/intermediate_bindings/codes_get.rs | 14 ++++++++++++++ src/intermediate_bindings/mod.rs | 6 +++--- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/benches/main.rs b/benches/main.rs index 9285990..34b0efe 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -17,6 +17,13 @@ pub fn key_reading(c: &mut Criterion) { b.iter(|| -> i64 { msg.read_key(black_box("dataDate")).unwrap() }) }); + c.bench_function("float reading", |b| { + b.iter(|| -> f32 { + msg.read_key(black_box("jDirectionIncrementInDegrees")) + .unwrap() + }) + }); + c.bench_function("double reading", |b| { b.iter(|| -> f64 { msg.read_key(black_box("jDirectionIncrementInDegrees")) diff --git a/src/codes_message/read.rs b/src/codes_message/read.rs index 8423a8d..570b5e8 100644 --- a/src/codes_message/read.rs +++ b/src/codes_message/read.rs @@ -4,7 +4,7 @@ use crate::{ codes_message::CodesMessage, errors::CodesError, intermediate_bindings::{ - NativeKeyType, codes_get_bytes, codes_get_double, codes_get_double_array, + NativeKeyType, codes_get_bytes, codes_get_double, codes_get_double_array, codes_get_float, codes_get_float_array, codes_get_long, codes_get_long_array, codes_get_native_type, codes_get_size, codes_get_string, }, @@ -113,6 +113,7 @@ macro_rules! key_size_check { } impl_key_read!(scalar, codes_get_long, NativeKeyType::Long, i64); +impl_key_read!(scalar, codes_get_float, NativeKeyType::Double, f32); impl_key_read!(scalar, codes_get_double, NativeKeyType::Double, f64); impl_key_read!(array, codes_get_string, NativeKeyType::Str, String); impl_key_read!(array, codes_get_bytes, NativeKeyType::Bytes, Vec); diff --git a/src/intermediate_bindings/codes_get.rs b/src/intermediate_bindings/codes_get.rs index c6a796a..e9598af 100644 --- a/src/intermediate_bindings/codes_get.rs +++ b/src/intermediate_bindings/codes_get.rs @@ -57,6 +57,20 @@ pub unsafe fn codes_get_long(handle: *const codes_handle, key: &str) -> Result Result { + unsafe { + pointer_guard::non_null!(handle); + + let key = CString::new(key).unwrap(); + let mut key_value: f32 = 0.0; + + let error_code = eccodes_sys::codes_get_float(handle, key.as_ptr(), &raw mut key_value); + error_code_to_result(error_code)?; + + Ok(key_value) + } +} + pub unsafe fn codes_get_double(handle: *const codes_handle, key: &str) -> Result { unsafe { pointer_guard::non_null!(handle); diff --git a/src/intermediate_bindings/mod.rs b/src/intermediate_bindings/mod.rs index 23da50b..b1a8f43 100644 --- a/src/intermediate_bindings/mod.rs +++ b/src/intermediate_bindings/mod.rs @@ -26,9 +26,9 @@ pub enum NativeKeyType { } pub use codes_get::{ - codes_get_bytes, codes_get_double, codes_get_double_array, codes_get_float_array, - codes_get_long, codes_get_long_array, codes_get_message, codes_get_native_type, codes_get_size, - codes_get_string, + codes_get_bytes, codes_get_double, codes_get_double_array, codes_get_float, + codes_get_float_array, codes_get_long, codes_get_long_array, codes_get_message, + codes_get_native_type, codes_get_size, codes_get_string, }; pub use codes_handle::{codes_handle_clone, codes_handle_delete, codes_handle_new_from_file}; pub use codes_keys::{ From c641c63366765ad4172b815f06c0a58eaedf0ddd Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 05/15] cleanup and improve tests --- src/codes_message/read.rs | 54 ++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/codes_message/read.rs b/src/codes_message/read.rs index 570b5e8..e52a126 100644 --- a/src/codes_message/read.rs +++ b/src/codes_message/read.rs @@ -244,6 +244,49 @@ mod tests { use crate::{FallibleIterator, codes_message::DynamicKeyType}; use std::path::Path; + #[test] + fn static_reading() -> Result<()> { + let file_path = Path::new("./data/iceland.grib"); + let product_kind = ProductKind::GRIB; + + let mut handle = CodesFile::new_from_file(file_path, product_kind)?; + let current_message = handle + .ref_message_iter() + .next()? + .context("Message not some")?; + + assert_eq!( + KeyRead::::read_key(¤t_message, "dataDate")?, + 20210601 + ); + assert_eq!( + KeyRead::::read_key(¤t_message, "jDirectionIncrementInDegrees")?, + 0.25 + ); + assert_eq!( + KeyRead::::read_key(¤t_message, "jDirectionIncrementInDegrees")?, + 0.25 + ); + assert_eq!( + KeyRead::::read_key(¤t_message, "name")?, + "Mean sea level pressure" + ); + assert_eq!( + KeyRead::>::read_key(¤t_message, "numberOfPointsAlongAParallel")?, + vec![49] + ); + assert_eq!( + KeyRead::>::read_key(¤t_message, "jDirectionIncrementInDegrees")?, + vec![0.25] + ); + assert_eq!( + KeyRead::>::read_key(¤t_message, "jDirectionIncrementInDegrees")?, + vec![0.25] + ); + + Ok(()) + } + #[test] fn key_reader() -> Result<()> { let file_path = Path::new("./data/iceland.grib"); @@ -369,8 +412,8 @@ mod tests { } #[test] - // checks if we can read keys that are used in benchmarks - fn benchmark_keys() -> Result<()> { + // Test keys that have caused problems in the past + fn challenging_keys() -> Result<()> { let file_path = Path::new("./data/iceland.grib"); let product_kind = ProductKind::GRIB; @@ -381,12 +424,11 @@ mod tests { .next()? .context("Message not some")?; - let _ = msg.read_key_dynamic("dataDate")?; - let _ = msg.read_key_dynamic("jDirectionIncrementInDegrees")?; - let _ = msg.read_key_dynamic("values")?; - let _ = msg.read_key_dynamic("name")?; + // key of bytes type let _ = msg.read_key_dynamic("section1Padding")?; + // missing nul-byte termination let _ = msg.read_key_dynamic("experimentVersionNumber")?; + // differing name on different platforms let _ = msg .read_key_dynamic("zero") .unwrap_or_else(|_| msg.read_key_dynamic("zeros").unwrap()); From b80e2a926c5dea66bd0c94643bdabd7dac4f5530 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 06/15] fix: incorrect key type in benchmark --- benches/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benches/main.rs b/benches/main.rs index 34b0efe..695c3f8 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -36,7 +36,10 @@ pub fn key_reading(c: &mut Criterion) { }); c.bench_function("long array reading", |b| { - b.iter(|| -> Vec { msg.read_key(black_box("values")).unwrap() }) + b.iter(|| -> Vec { + msg.read_key(black_box("numberOfPointsAlongAParallel")) + .unwrap() + }) }); c.bench_function("float array reading", |b| { From e56a9c6a3fe8503d4e1c4b0f0710aaca80bfb062 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 07/15] upgrade dependecies --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb1f02d..3df8067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ eccodes-sys = { version = "0.7.0", default-features = false } libc = { version = "0.2", default-features = false } thiserror = { version = "2.0", default-features = false } errno = { version = "0.3", default-features = false } -num-derive = { version = "0.4", default-features = false } +num-derive = { version = "0.5", default-features = false } num-traits = { version = "0.2", default-features = false } fallible-iterator = { version = "0.3", default-features = false } ndarray = { version = "0.17", default-features = false, optional = true, features = [ @@ -38,7 +38,7 @@ tracing = { version = "0.1", default-features = false, features = [ [dev-dependencies] reqwest = { version = "0.13", features = ["rustls"] } criterion = "0.8" -rand = "0.9" +rand = "0.10" anyhow = { version = "1.0", features = ["backtrace"] } float-cmp = "0.10" From 26633adc02f1f88ca3c9c4a31f16783c9fda1c0e Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 08/15] move lints definition to toml file (it's a better practice as it doesn't mess with lints downstream) --- Cargo.toml | 31 +++++++++++++++++++++++++++++++ benches/main.rs | 1 + clippy.toml | 4 ++++ src/lib.rs | 6 ------ tests/example.rs | 1 + tests/handle.rs | 1 + 6 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 clippy.toml diff --git a/Cargo.toml b/Cargo.toml index 3df8067..b80f21b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,34 @@ features = ["docs", "ndarray"] [[bench]] name = "main" harness = false + +[lints.rust] +warnings = "deny" +missing_docs = "deny" + +[lints.clippy] +pedantic = { level = "deny", priority = -1 } +nursery = { level = "deny", priority = -1 } +perf = { level = "deny", priority = -1 } +cargo = { level = "deny", priority = -1 } + +unwrap_used = "deny" +expect_used = "deny" +todo = "deny" +indexing_slicing = "deny" +unreachable = "deny" +unimplemented = "deny" +panic = "deny" +exit = "deny" +as_conversions = "deny" + +arithmetic_side_effects = "deny" +unchecked_time_subtraction = "deny" +string_slice = "deny" +panic_in_result_fn = "deny" +dbg_macro = "deny" +unnecessary_self_imports = "deny" +absolute_paths = "deny" +doc_lazy_continuation = "deny" + +multiple_crate_versions = "allow" diff --git a/benches/main.rs b/benches/main.rs index 695c3f8..cd5aa44 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use criterion::{Criterion, criterion_group, criterion_main}; use eccodes::codes_file::{CodesFile, ProductKind}; use eccodes::{FallibleIterator, KeyRead}; diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..4b6c79f --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true +allow-panic-in-tests = true +allow-indexing-slicing-in-tests = true diff --git a/src/lib.rs b/src/lib.rs index 34df109..d635543 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,3 @@ -#![warn(clippy::pedantic)] -#![allow(clippy::cast_possible_wrap)] -#![warn(missing_docs)] -#![warn(clippy::cargo)] -#![warn(clippy::perf)] -#![warn(clippy::doc_lazy_continuation)] #![cfg_attr(docsrs, feature(doc_cfg))] //! # Unofficial high-level safe Rust bindings to ecCodes library diff --git a/tests/example.rs b/tests/example.rs index 8d443c3..f3320c4 100644 --- a/tests/example.rs +++ b/tests/example.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use anyhow::Context; use eccodes::{CodesFile, FallibleIterator, ProductKind}; fn main() -> anyhow::Result<()> { diff --git a/tests/handle.rs b/tests/handle.rs index 191fca5..302f6f8 100644 --- a/tests/handle.rs +++ b/tests/handle.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] use std::{path::Path, thread}; use anyhow::{Context, Result}; From b043424fc2d6e2758d4b5f55a43b85320800bb2f Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 09/15] first batch of clippy fixes --- Cargo.toml | 1 + src/codes_file/iterator.rs | 5 ++++- src/errors.rs | 15 ++++++++++----- src/intermediate_bindings/codes_get.rs | 22 +++++++++++----------- src/intermediate_bindings/codes_handle.rs | 2 +- src/intermediate_bindings/codes_keys.rs | 2 +- src/intermediate_bindings/codes_set.rs | 14 +++++++------- 7 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b80f21b..8e01c62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,3 +84,4 @@ absolute_paths = "deny" doc_lazy_continuation = "deny" multiple_crate_versions = "allow" +cast_possible_wrap = "allow" diff --git a/src/codes_file/iterator.rs b/src/codes_file/iterator.rs index e04c39c..e457fee 100644 --- a/src/codes_file/iterator.rs +++ b/src/codes_file/iterator.rs @@ -112,10 +112,13 @@ impl FallibleIterator for ArcMessageIter { /// This method internally uses a Mutex to access `CodesFile`, which can panic when poisoned, /// but thers is no path in which you can get to the state of poisoned mutex, while still able to access this method. fn next(&mut self) -> Result, Self::Error> { + #[allow( + clippy::expect_used, + reason = "This mutex can be poisoned only when thread that holds ArcMessageIter panics, which would make using the mutex impossible" + )] let eccodes_handle = self .codes_file .lock() - // This mutex can be poisoned only when thread that holds ArcMessageIter panics, which would make using the mutex impossible") .expect("The mutex inside ArcMessageIter got poisoned") .generate_codes_handle()?; diff --git a/src/errors.rs b/src/errors.rs index a3d7b66..dc19092 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -7,6 +7,7 @@ use errno::Errno; use num_derive::FromPrimitive; +use std::{ffi, io, num, str}; use thiserror::Error; /// Errors returned by the all functions in the crate. @@ -27,16 +28,20 @@ pub enum CodesError { ///Returned when there is an issue while handlng the file. ///Check the [`std::fs`] documentation why and when this error can occur. #[error("Error occured while opening the file: {0}")] - FileHandlingInterrupted(#[from] std::io::Error), + FileHandlingInterrupted(#[from] io::Error), ///Returned when the string cannot be parsed as valid UTF8 string. #[error("Cannot parse string as UTF8: {0}")] - CstrUTF8(#[from] std::str::Utf8Error), + CstrUTF8(#[from] str::Utf8Error), - ///Returned when the C-string returned by ecCodes library cannot be converted + ///Returned when the string cannot be converted into a `CString` before calling the FFI. + #[error("Cannot parse string as CString: {0}")] + CStringNul(#[from] ffi::NulError), + + ///Returned when the `Cstring` returned by ecCodes library cannot be converted ///into a Rust-string. #[error("String returned by ecCodes is not nul terminated: {0}")] - NulChar(#[from] std::ffi::FromBytesWithNulError), + NulChar(#[from] ffi::FromBytesWithNulError), ///Returned when the requested key is not present in the message. ///Similar to [`CodesInternal::CodesNotFound`] and [`CodesInternal::CodesMissingKey`]. @@ -117,7 +122,7 @@ pub enum MessageNdarrayError { /// This error can occur when casting types of shape fails /// on 32-bit systems or for very large arrays. #[error(transparent)] - IntCasting(#[from] std::num::TryFromIntError), + IntCasting(#[from] num::TryFromIntError), } ///Errors returned by internal ecCodes library functions. diff --git a/src/intermediate_bindings/codes_get.rs b/src/intermediate_bindings/codes_get.rs index e9598af..7c5e620 100644 --- a/src/intermediate_bindings/codes_get.rs +++ b/src/intermediate_bindings/codes_get.rs @@ -18,7 +18,7 @@ pub unsafe fn codes_get_native_type( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_type: i32 = 0; let error_code = @@ -33,7 +33,7 @@ pub unsafe fn codes_get_size(handle: *const codes_handle, key: &str) -> Result Result Result< unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_value: f32 = 0.0; let error_code = eccodes_sys::codes_get_float(handle, key.as_ptr(), &raw mut key_value); @@ -75,7 +75,7 @@ pub unsafe fn codes_get_double(handle: *const codes_handle, key: &str) -> Result unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_value: f64 = 0.0; let error_code = eccodes_sys::codes_get_double(handle, key.as_ptr(), &raw mut key_value); @@ -93,7 +93,7 @@ pub unsafe fn codes_get_float_array( pointer_guard::non_null!(handle); let mut key_size = codes_get_size(handle, key)?; - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_values: Vec = vec![0.0; key_size]; @@ -117,7 +117,7 @@ pub unsafe fn codes_get_double_array( pointer_guard::non_null!(handle); let mut key_size = codes_get_size(handle, key)?; - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_values: Vec = vec![0.0; key_size]; @@ -141,7 +141,7 @@ pub unsafe fn codes_get_long_array( pointer_guard::non_null!(handle); let mut key_size = codes_get_size(handle, key)?; - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_values: Vec = vec![0; key_size]; @@ -164,7 +164,7 @@ pub unsafe fn codes_get_length( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_length: usize = 0; let error_code = eccodes_sys::codes_get_length(handle, key.as_ptr(), &raw mut key_length); @@ -182,7 +182,7 @@ pub unsafe fn codes_get_string( pointer_guard::non_null!(handle); let mut key_length = codes_get_length(handle, key)?; - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut key_message: Vec = vec![0; key_length]; @@ -218,7 +218,7 @@ pub unsafe fn codes_get_bytes( pointer_guard::non_null!(handle); let mut key_size = codes_get_length(handle, key)?; - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut buffer: Vec = vec![0; key_size]; diff --git a/src/intermediate_bindings/codes_handle.rs b/src/intermediate_bindings/codes_handle.rs index 9c72b04..c1902c4 100644 --- a/src/intermediate_bindings/codes_handle.rs +++ b/src/intermediate_bindings/codes_handle.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types)] #![allow(clippy::module_name_repetitions)] -use std::ptr::{self}; +use std::ptr; use eccodes_sys::{codes_context, codes_handle}; use libc::FILE; diff --git a/src/intermediate_bindings/codes_keys.rs b/src/intermediate_bindings/codes_keys.rs index 20db605..33dfbd7 100644 --- a/src/intermediate_bindings/codes_keys.rs +++ b/src/intermediate_bindings/codes_keys.rs @@ -18,7 +18,7 @@ pub unsafe fn codes_keys_iterator_new( unsafe { pointer_guard::non_null!(handle); - let namespace = CString::new(namespace).unwrap(); + let namespace = CString::new(namespace)?; let kiter = eccodes_sys::codes_keys_iterator_new(handle, u64::from(flags), namespace.as_ptr()); diff --git a/src/intermediate_bindings/codes_set.rs b/src/intermediate_bindings/codes_set.rs index fc763aa..078388b 100644 --- a/src/intermediate_bindings/codes_set.rs +++ b/src/intermediate_bindings/codes_set.rs @@ -15,7 +15,7 @@ pub unsafe fn codes_set_long( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let error_code = eccodes_sys::codes_set_long(handle, key.as_ptr(), value); error_code_to_result(error_code)?; @@ -32,7 +32,7 @@ pub unsafe fn codes_set_double( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let error_code = eccodes_sys::codes_set_double(handle, key.as_ptr(), value); error_code_to_result(error_code)?; @@ -49,7 +49,7 @@ pub unsafe fn codes_set_long_array( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let length = values.len(); @@ -69,7 +69,7 @@ pub unsafe fn codes_set_double_array( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let length = values.len(); @@ -93,9 +93,9 @@ pub unsafe fn codes_set_string( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut length = value.len(); - let value = CString::new(value).unwrap(); + let value = CString::new(value)?; let error_code = eccodes_sys::codes_set_string(handle, key.as_ptr(), value.as_ptr(), &raw mut length); @@ -113,7 +113,7 @@ pub unsafe fn codes_set_bytes( unsafe { pointer_guard::non_null!(handle); - let key = CString::new(key).unwrap(); + let key = CString::new(key)?; let mut length = values.len(); From 6224347e12cf63de9cdf9e593ff1b6fbd5af5d13 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 10/15] clippy: remove the indexing and a false-positive --- src/codes_message/mod.rs | 4 +++ src/errors.rs | 4 +++ src/intermediate_bindings/grib_nearest.rs | 30 ++++++++++++++++------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/codes_message/mod.rs b/src/codes_message/mod.rs index 368b666..8dba481 100644 --- a/src/codes_message/mod.rs +++ b/src/codes_message/mod.rs @@ -88,6 +88,10 @@ pub type RefMessage<'ch> = CodesMessage>; /// [`CodesMessage`] that can be moved and shared across threads. pub type ArcMessage = CodesMessage>; +#[allow( + clippy::non_send_fields_in_send_ty, + reason = "Safety is ensured on the C side" +)] unsafe impl Send for ArcMessage {} unsafe impl Sync for ArcMessage {} diff --git a/src/errors.rs b/src/errors.rs index dc19092..e539e57 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -66,6 +66,10 @@ pub enum CodesError { #[error("Cannot clone the message")] CloneFailed, + /// Returned when `CodesNearest::find_nearest` fails internally, it is most likely a bug + #[error("Internal error occured while trying to find nearest points")] + NearestFindFailed, + /// Returned when [`eccodes_sys::codes_keys_iterator_new`] returns null pointer #[error("Cannot create or manipulate keys iterator")] KeysIteratorFailed, diff --git a/src/intermediate_bindings/grib_nearest.rs b/src/intermediate_bindings/grib_nearest.rs index 8c51265..ca0748d 100644 --- a/src/intermediate_bindings/grib_nearest.rs +++ b/src/intermediate_bindings/grib_nearest.rs @@ -79,15 +79,27 @@ pub unsafe fn codes_grib_nearest_find( ); error_code_to_result(error_code)?; - 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]; - } + let output = output_lats + .iter() + .zip( + output_lons.iter().zip( + output_values + .iter() + .zip(output_distances.iter().zip(output_indexes)), + ), + ) + .map( + |(&lat, (&lon, (&value, (&distance, index))))| NearestGridpoint { + index, + lat, + lon, + distance, + value, + }, + ) + .collect::>() + .try_into() + .map_err(|_| CodesError::NearestFindFailed)?; Ok(output) } From 03338ac5a9ba8dc1b654134b980d20ae58d6953d Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 11/15] clippy: review and allow as conversions to not do that per-line --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8e01c62..e509b71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,6 @@ unreachable = "deny" unimplemented = "deny" panic = "deny" exit = "deny" -as_conversions = "deny" arithmetic_side_effects = "deny" unchecked_time_subtraction = "deny" @@ -83,5 +82,6 @@ unnecessary_self_imports = "deny" absolute_paths = "deny" doc_lazy_continuation = "deny" +as_conversions = "allow" multiple_crate_versions = "allow" cast_possible_wrap = "allow" From 2aa8ab94d64cde5011224e041892195d13543bca Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 12/15] clippy: fix unchecked multiply --- src/codes_message/ndarray.rs | 8 ++++++-- src/errors.rs | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/codes_message/ndarray.rs b/src/codes_message/ndarray.rs index 157cf0e..b596aaa 100644 --- a/src/codes_message/ndarray.rs +++ b/src/codes_message/ndarray.rs @@ -51,7 +51,7 @@ impl CodesMessage

{ let vals: Vec = self.read_key("values")?; - let expected_vals_len = ni * nj; + let expected_vals_len = ni.checked_mul(nj).ok_or(CodesError::TooMuchValues)?; if vals.len() != expected_vals_len { return Err( MessageNdarrayError::UnexpectedValuesLength(vals.len(), expected_vals_len).into(), @@ -100,7 +100,11 @@ impl CodesMessage

{ let latlonvals: Vec = self.read_key("latLonValues")?; - let expected_vals_len = ni * nj * 3; + let expected_vals_len = ni + .checked_mul(nj) + .ok_or(CodesError::TooMuchValues)? + .checked_mul(3) + .ok_or(CodesError::TooMuchValues)?; if latlonvals.len() != expected_vals_len { return Err(MessageNdarrayError::UnexpectedValuesLength( latlonvals.len(), diff --git a/src/errors.rs b/src/errors.rs index e539e57..6b716fb 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -84,9 +84,18 @@ pub enum CodesError { /// Returned when function in `message_ndarray` module cannot convert /// the message to ndarray. Check [`MessageNdarrayError`] for more details. #[cfg(feature = "ndarray")] + #[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))] #[error("error occured while converting CodesMessage to ndarray {0}")] NdarrayConvert(#[from] MessageNdarrayError), + /// Returned when the message values array count of elements exceeds `usize::MAX` + #[cfg(feature = "ndarray")] + #[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))] + #[error( + "CodesMessage contains to much elements in the values array to be converted into ndarray" + )] + TooMuchValues, + /// eccodes functions return errors as error codes and it is technically possible /// that the library might return an error code that does not appear in [`CodesInternal`] enum. #[error("eccodes returned unrecognized error code: {0}")] From 33e274437a412de8374296873c329f48539ce739 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 13/15] fix unused when ndarray is disabled --- src/errors.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/errors.rs b/src/errors.rs index 6b716fb..b2260f4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -7,7 +7,9 @@ use errno::Errno; use num_derive::FromPrimitive; -use std::{ffi, io, num, str}; +#[cfg(feature = "ndarray")] +use std::num; +use std::{ffi, io, str}; use thiserror::Error; /// Errors returned by the all functions in the crate. From 2ee7b591f5bc0e187639cf62bbe4ff7c11ed49d6 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 14/15] bump the version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e509b71..5f16bcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "eccodes" description = "Unofficial high-level Rust bindings of the latest ecCodes release" repository = "https://github.com/ScaleWeather/eccodes" -version = "0.14.0" +version = "0.15.0" readme = "README.md" authors = ["Jakub Lewandowski "] keywords = ["eccodes", "grib", "bufr", "meteorology", "weather"] From 15eea993249c142c2f5c590efb84333e850cb798 Mon Sep 17 00:00:00 2001 From: Quba1 <22771850+Quba1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:55:14 +0200 Subject: [PATCH 15/15] fix: libclang1 is no more in ubuntu --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 574078f..4e45e0c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,7 +39,7 @@ jobs: run: | sudo apt-get update sudo apt-get install clang - sudo apt-get install libclang1 + sudo apt-get install libclang-dev sudo apt-get install libeccodes-dev rustup update stable cargo install cargo-criterion