From 91b33235bc76481e6b7d2207d2fef18aa4566c67 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Sat, 6 Jun 2026 15:33:46 +0200 Subject: [PATCH] feat: add CoordinateOverflow error handling and tests for coordinate parsing Fix issue #584 --- src/error.rs | 6 +++++ src/lib.rs | 9 +++---- tests/coordinate_overflow.rs | 46 ++++++++++++++++++++++++++++++++++++ tests/real_world.rs | 8 ++++++- 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 tests/coordinate_overflow.rs diff --git a/src/error.rs b/src/error.rs index 4446b95..9eeed04 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,4 +35,10 @@ pub enum ParserError { /// The geometry section of a feature is malformed. #[error("Geometry section contains errors")] InvalidGeometry, + + /// A coordinate value does not fit in the requested numeric type. + #[error("Coordinate value {value} overflows the requested type")] + CoordinateOverflow { + value: i32, + } } diff --git a/src/lib.rs b/src/lib.rs index 4556f75..6303593 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -428,10 +428,11 @@ fn parse_geometry( Some(result) => result, None => i32::MAX, // clip value }; - coordinates.push(Coord { - x: NumCast::from(cursor[0]).unwrap_or_else(T::zero), - y: NumCast::from(cursor[1]).unwrap_or_else(T::zero), - }); + let x = NumCast::from(cursor[0]) + .ok_or(error::ParserError::CoordinateOverflow { value: cursor[0] })?; + let y = NumCast::from(cursor[1]) + .ok_or(error::ParserError::CoordinateOverflow { value: cursor[1] })?; + coordinates.push(Coord { x, y }); } parameter_count -= 1; } diff --git a/tests/coordinate_overflow.rs b/tests/coordinate_overflow.rs new file mode 100644 index 0000000..5dda1d0 --- /dev/null +++ b/tests/coordinate_overflow.rs @@ -0,0 +1,46 @@ +use std::fs::read; + +use mvt_reader::{Reader, error::ParserError}; + +#[test] +fn u8_coordinate_overflow_returns_error() { + // Fixture 053 has coordinates (0,0), (4096,0), (4096,4096), (0,4096) + // which clearly exceed u8 max (255) + let bytes = read("mvt-fixtures/fixtures/053/tile.mvt").unwrap(); + let reader = Reader::new(bytes).unwrap(); + + let result = reader.get_features_as::(0); + assert!( + matches!(result, Err(ParserError::CoordinateOverflow { .. })), + "Expected CoordinateOverflow error for coordinates exceeding u8 range, got: {:?}", + result + ); +} + +#[test] +fn u8_coordinate_within_range_succeeds() { + // Fixture 002 has a point at (25, 17) which fits in u8 + let bytes = read("mvt-fixtures/fixtures/002/tile.mvt").unwrap(); + let reader = Reader::new(bytes).unwrap(); + + let result = reader.get_features_as::(0); + assert!( + result.is_ok(), + "Expected success for coordinates within u8 range, got: {:?}", + result + ); +} + +#[test] +fn i32_coordinate_large_values_succeeds() { + // Fixture 053 has coordinates up to 4096 which fit in i32 + let bytes = read("mvt-fixtures/fixtures/053/tile.mvt").unwrap(); + let reader = Reader::new(bytes).unwrap(); + + let result = reader.get_features_as::(0); + assert!( + result.is_ok(), + "Expected success for coordinates within i32 range, got: {:?}", + result + ); +} diff --git a/tests/real_world.rs b/tests/real_world.rs index ceca980..50bb088 100644 --- a/tests/real_world.rs +++ b/tests/real_world.rs @@ -96,7 +96,13 @@ fn assert_all_fixtures_readable(label: &str) -> Result<(), io::Erro let layer_names = reader.get_layer_names().expect("Failed to get layer names"); for (i, _) in layer_names.iter().enumerate() { let features = reader.get_features_as::(i); - assert!(!features.unwrap().is_empty()); + match features { + Ok(f) => assert!(!f.is_empty()), + Err(ParserError::CoordinateOverflow { .. }) => { + println!("CoordinateOverflow for layer {} (expected for small types)", i); + } + Err(e) => panic!("Unexpected error: {:?}", e), + } } println!("found layer names: {:?}", layer_names); }