do not panic in ValueGrid

This commit is contained in:
Vinzenz Schroeter 2025-03-08 17:50:22 +01:00
parent b178b48834
commit 18db901fb5
9 changed files with 55 additions and 80 deletions

View file

@ -38,7 +38,7 @@ impl TryFrom<Packet> for BrightnessGridCommand {
payload, payload,
} = packet; } = packet;
let grid = ByteGrid::load(width as usize, height as usize, &payload); let grid = ByteGrid::load(width as usize, height as usize, &payload).unwrap();
let grid = match BrightnessGrid::try_from(grid) { let grid = match BrightnessGrid::try_from(grid) {
Ok(grid) => grid, Ok(grid) => grid,
Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)), Err(val) => return Err(TryFromPacketError::InvalidBrightness(val)),

View file

@ -52,7 +52,7 @@ impl TryFrom<Packet> for CharGridCommand {
String::from_utf8(payload.clone())?.chars().collect(); String::from_utf8(payload.clone())?.chars().collect();
Ok(Self { Ok(Self {
origin: Origin::new(a as usize, b as usize), origin: Origin::new(a as usize, b as usize),
grid: CharGrid::load(c as usize, d as usize, &payload), grid: CharGrid::load(c as usize, d as usize, &payload).unwrap(),
}) })
} }
} }

View file

@ -61,7 +61,7 @@ impl TryFrom<Packet> for Cp437GridCommand {
} = packet; } = packet;
Ok(Self { Ok(Self {
origin: Origin::new(a as usize, b as usize), origin: Origin::new(a as usize, b as usize),
grid: Cp437Grid::load(c as usize, d as usize, &payload), grid: Cp437Grid::load(c as usize, d as usize, &payload).unwrap(),
}) })
} }
} }

View file

@ -397,7 +397,7 @@ mod tests {
8, 8,
1, 1,
&[true, false, true, false, true, false, true, false], &[true, false, true, false, true, false, true, false],
); ).unwrap();
let converted = Bitmap::try_from(&original).unwrap(); let converted = Bitmap::try_from(&original).unwrap();
let reconverted = ValueGrid::from(&converted); let reconverted = ValueGrid::from(&converted);
assert_eq!(original, reconverted); assert_eq!(original, reconverted);

View file

@ -21,7 +21,7 @@ pub type BrightnessGrid = ValueGrid<Brightness>;
impl BrightnessGrid { impl BrightnessGrid {
/// Like [Self::load], but ignoring any out-of-range brightness values /// Like [Self::load], but ignoring any out-of-range brightness values
pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self { pub fn saturating_load(width: usize, height: usize, data: &[u8]) -> Self {
ValueGrid::load(width, height, data).map(Brightness::saturating_from) ValueGrid::load(width, height, data).unwrap().map(Brightness::saturating_from)
} }
} }
@ -40,7 +40,7 @@ impl From<&BrightnessGrid> for ByteGrid {
.iter() .iter()
.map(|brightness| (*brightness).into()) .map(|brightness| (*brightness).into())
.collect::<Vec<u8>>(); .collect::<Vec<u8>>();
ValueGrid::load(value.width(), value.height(), &u8s) ValueGrid::load(value.width(), value.height(), &u8s).unwrap()
} }
} }
@ -56,7 +56,7 @@ impl TryFrom<ByteGrid> for BrightnessGrid {
value.width(), value.width(),
value.height(), value.height(),
&brightnesses, &brightnesses,
)) ).unwrap())
} }
} }
@ -85,7 +85,7 @@ mod tests {
Brightness::MIN, Brightness::MIN,
Brightness::MAX Brightness::MAX
] ]
), ).unwrap(),
BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42]) BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42])
); );
} }

View file

@ -140,7 +140,11 @@ impl CharGrid {
bytes: Vec<u8>, bytes: Vec<u8>,
) -> Result<CharGrid, LoadUtf8Error> { ) -> Result<CharGrid, LoadUtf8Error> {
let s: Vec<char> = String::from_utf8(bytes)?.chars().collect(); let s: Vec<char> = String::from_utf8(bytes)?.chars().collect();
Ok(CharGrid::try_load(width, height, s)?) Ok(CharGrid::load(width, height, &s).ok_or(
LoadUtf8Error::TryLoadError(
TryLoadValueGridError::InvalidDimensions,
),
)?)
} }
} }

View file

@ -86,7 +86,7 @@ mod tests {
fn load_ascii_nowrap() { fn load_ascii_nowrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.map(move |c| c as u8); .map(move |c| c as u8);
let expected = Cp437Grid::load(5, 2, &chars); let expected = Cp437Grid::load(5, 2, &chars).unwrap();
let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap(); let actual = Cp437Grid::load_ascii("Hello,\nWorld!", 5, false).unwrap();
// comma will be removed because line is too long and wrap is off // comma will be removed because line is too long and wrap is off
@ -97,7 +97,7 @@ mod tests {
fn load_ascii_wrap() { fn load_ascii_wrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd'] let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.map(move |c| c as u8); .map(move |c| c as u8);
let expected = Cp437Grid::load(5, 2, &chars); let expected = Cp437Grid::load(5, 2, &chars).unwrap();
let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap(); let actual = Cp437Grid::load_ascii("HelloWorld", 5, true).unwrap();
// line break will be added // line break will be added

View file

@ -60,86 +60,57 @@ impl<T: Value> ValueGrid<T> {
/// Loads a [ValueGrid] with the specified dimensions from the provided data. /// Loads a [ValueGrid] with the specified dimensions from the provided data.
/// ///
/// returns: [ValueGrid] that contains a copy of the provided data /// returns: [ValueGrid] that contains a copy of the provided data,
/// /// or None if the dimensions do not match the data size.
/// # Panics
///
/// - when the dimensions and data size do not match exactly.
#[must_use] #[must_use]
pub fn load(width: usize, height: usize, data: &[T]) -> Self { pub fn load(width: usize, height: usize, data: &[T]) -> Option<Self> {
assert_eq!( if width * height != data.len() {
width * height, return None;
data.len(), }
"dimension mismatch for data {data:?}" Some(Self {
);
Self {
data: Vec::from(data), data: Vec::from(data),
width, width,
height, height,
} })
} }
/// Creates a [ValueGrid] with the specified width from the provided data without copying it. /// Creates a [ValueGrid] with the specified width from the provided data,
/// wrapping to as many rows as needed,
/// without copying the vec.
/// ///
/// returns: [ValueGrid] that contains the provided data. /// returns: [ValueGrid] that contains the provided data,
/// or None if the data size is not divisible by the width.
/// ///
/// # Panics /// # Examples
/// ///
/// - when the data size is not dividable by the width. /// ```
/// # use servicepoint::ValueGrid;
/// let grid = ValueGrid::from_vec(2, vec![0, 1, 2, 3, 4, 5]).unwrap();
/// ```
#[must_use] #[must_use]
pub fn from_vec(width: usize, data: Vec<T>) -> Self { pub fn from_vec(width: usize, data: Vec<T>) -> Option<Self> {
let len = data.len(); let len = data.len();
let height = len / width; let height = len / width;
assert_eq!( if len % width != 0 {
0, return None;
len % width, }
"dimension mismatch - len {len} is not dividable by {width}" Some(Self {
);
Self {
data, data,
width, width,
height, height,
} })
} }
/// Loads a [ValueGrid] with the specified width from the provided data, wrapping to as many rows as needed. /// Loads a [ValueGrid] with the specified width from the provided data, wrapping to as many rows as needed.
/// ///
/// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError]. /// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError].
/// ///
/// # Examples #[must_use]
///
/// ```
/// # use servicepoint::ValueGrid;
/// let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap();
/// ```
pub fn wrap( pub fn wrap(
width: usize, width: usize,
data: &[T], data: &[T],
) -> Result<Self, TryLoadValueGridError> { ) -> Option<Self> {
let len = data.len(); Self::from_vec(width, data.to_vec())
if len % width != 0 {
return Err(TryLoadValueGridError::InvalidDimensions);
}
Ok(Self::load(width, len / width, data))
}
/// Loads a [ValueGrid] with the specified dimensions from the provided data.
///
/// returns: [ValueGrid] that contains a copy of the provided data or [TryLoadValueGridError].
pub fn try_load(
width: usize,
height: usize,
data: Vec<T>,
) -> Result<Self, TryLoadValueGridError> {
if width * height != data.len() {
return Err(TryLoadValueGridError::InvalidDimensions);
}
Ok(Self {
data,
width,
height,
})
} }
/// Iterate over all cells in [ValueGrid]. /// Iterate over all cells in [ValueGrid].
@ -230,7 +201,7 @@ impl<T: Value> ValueGrid<T> {
.iter() .iter()
.map(|elem| f(*elem)) .map(|elem| f(*elem))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
ValueGrid::load(self.width(), self.height(), &data) ValueGrid::load(self.width(), self.height(), &data).unwrap()
} }
/// Copies a row from the grid. /// Copies a row from the grid.
@ -486,7 +457,7 @@ mod tests {
let data: Vec<u8> = grid.into(); let data: Vec<u8> = grid.into();
let grid = ValueGrid::load(2, 3, &data); let grid = ValueGrid::load(2, 3, &data).unwrap();
assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]); assert_eq!(grid.data, [0, 1, 1, 2, 2, 3]);
} }
@ -525,7 +496,7 @@ mod tests {
#[test] #[test]
fn iter_rows() { fn iter_rows() {
let vec = ValueGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]); let vec = ValueGrid::load(2, 3, &[0, 1, 1, 2, 2, 3]).unwrap();
for (y, row) in vec.iter_rows().enumerate() { for (y, row) in vec.iter_rows().enumerate() {
for (x, val) in row.enumerate() { for (x, val) in row.enumerate() {
assert_eq!(*val, (x + y) as u8); assert_eq!(*val, (x + y) as u8);
@ -536,20 +507,20 @@ mod tests {
#[test] #[test]
#[should_panic] #[should_panic]
fn out_of_bounds_x() { fn out_of_bounds_x() {
let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]); let mut vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]).unwrap();
vec.set(2, 1, 5); vec.set(2, 1, 5);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn out_of_bounds_y() { fn out_of_bounds_y() {
let vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]); let vec = ValueGrid::load(2, 2, &[0, 1, 2, 3]).unwrap();
vec.get(1, 2); vec.get(1, 2);
} }
#[test] #[test]
fn ref_mut() { fn ref_mut() {
let mut vec = ValueGrid::from_vec(3, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); let mut vec = ValueGrid::from_vec(3, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]).unwrap();
let top_left = vec.get_ref_mut(0, 0); let top_left = vec.get_ref_mut(0, 0);
*top_left += 5; *top_left += 5;
@ -565,7 +536,7 @@ mod tests {
#[test] #[test]
fn optional() { fn optional() {
let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]); let mut grid = ValueGrid::load(2, 2, &[0, 1, 2, 3]).unwrap();
grid.set_optional(0, 0, 5); grid.set_optional(0, 0, 5);
grid.set_optional(-1, 0, 8); grid.set_optional(-1, 0, 8);
grid.set_optional(0, 8, 42); grid.set_optional(0, 8, 42);
@ -577,7 +548,7 @@ mod tests {
#[test] #[test]
fn col() { fn col() {
let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]); let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]).unwrap();
assert_eq!(grid.get_col(0), Some(vec![0, 2, 4])); assert_eq!(grid.get_col(0), Some(vec![0, 2, 4]));
assert_eq!(grid.get_col(1), Some(vec![1, 3, 5])); assert_eq!(grid.get_col(1), Some(vec![1, 3, 5]));
assert_eq!(grid.get_col(2), None); assert_eq!(grid.get_col(2), None);
@ -598,7 +569,7 @@ mod tests {
#[test] #[test]
fn row() { fn row() {
let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]); let mut grid = ValueGrid::load(2, 3, &[0, 1, 2, 3, 4, 5]).unwrap();
assert_eq!(grid.get_row(0), Some(vec![0, 1])); assert_eq!(grid.get_row(0), Some(vec![0, 1]));
assert_eq!(grid.get_row(2), Some(vec![4, 5])); assert_eq!(grid.get_row(2), Some(vec![4, 5]));
assert_eq!(grid.get_row(3), None); assert_eq!(grid.get_row(3), None);
@ -619,10 +590,10 @@ mod tests {
#[test] #[test]
fn wrap() { fn wrap() {
let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap(); let grid = ValueGrid::from_vec(2, vec![0, 1, 2, 3, 4, 5]).unwrap();
assert_eq!(grid.height(), 3); assert_eq!(grid.height(), 3);
let grid = ValueGrid::wrap(4, &[0, 1, 2, 3, 4, 5]); let grid = ValueGrid::from_vec(4, vec![0, 1, 2, 3, 4, 5]);
assert_eq!(grid.err(), Some(TryLoadValueGridError::InvalidDimensions)); assert_eq!(grid, None);
} }
} }

View file

@ -140,7 +140,7 @@ mod tests_feature_cp437 {
#[test] #[test]
fn round_trip_cp437() { fn round_trip_cp437() {
let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']); let utf8 = CharGrid::load(2, 2, &['Ä', 'x', '\n', '$']).unwrap();
let cp437 = Cp437Grid::from(utf8.clone()); let cp437 = Cp437Grid::from(utf8.clone());
let actual = CharGrid::from(cp437); let actual = CharGrid::from(cp437);
assert_eq!(actual, utf8); assert_eq!(actual, utf8);