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,
} = 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) {
Ok(grid) => grid,
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();
Ok(Self {
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;
Ok(Self {
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,
1,
&[true, false, true, false, true, false, true, false],
);
).unwrap();
let converted = Bitmap::try_from(&original).unwrap();
let reconverted = ValueGrid::from(&converted);
assert_eq!(original, reconverted);

View file

@ -21,7 +21,7 @@ pub type BrightnessGrid = ValueGrid<Brightness>;
impl BrightnessGrid {
/// Like [Self::load], but ignoring any out-of-range brightness values
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()
.map(|brightness| (*brightness).into())
.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.height(),
&brightnesses,
))
).unwrap())
}
}
@ -85,7 +85,7 @@ mod tests {
Brightness::MIN,
Brightness::MAX
]
),
).unwrap(),
BrightnessGrid::saturating_load(2, 2, &[255u8, 23, 0, 42])
);
}

View file

@ -140,7 +140,11 @@ impl CharGrid {
bytes: Vec<u8>,
) -> Result<CharGrid, LoadUtf8Error> {
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() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.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();
// comma will be removed because line is too long and wrap is off
@ -97,7 +97,7 @@ mod tests {
fn load_ascii_wrap() {
let chars = ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
.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();
// 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.
///
/// returns: [ValueGrid] that contains a copy of the provided data
///
/// # Panics
///
/// - when the dimensions and data size do not match exactly.
/// returns: [ValueGrid] that contains a copy of the provided data,
/// or None if the dimensions do not match the data size.
#[must_use]
pub fn load(width: usize, height: usize, data: &[T]) -> Self {
assert_eq!(
width * height,
data.len(),
"dimension mismatch for data {data:?}"
);
Self {
pub fn load(width: usize, height: usize, data: &[T]) -> Option<Self> {
if width * height != data.len() {
return None;
}
Some(Self {
data: Vec::from(data),
width,
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]
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 height = len / width;
assert_eq!(
0,
len % width,
"dimension mismatch - len {len} is not dividable by {width}"
);
Self {
if len % width != 0 {
return None;
}
Some(Self {
data,
width,
height,
}
})
}
/// 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].
///
/// # Examples
///
/// ```
/// # use servicepoint::ValueGrid;
/// let grid = ValueGrid::wrap(2, &[0, 1, 2, 3, 4, 5]).unwrap();
/// ```
#[must_use]
pub fn wrap(
width: usize,
data: &[T],
) -> Result<Self, TryLoadValueGridError> {
let len = data.len();
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,
})
) -> Option<Self> {
Self::from_vec(width, data.to_vec())
}
/// Iterate over all cells in [ValueGrid].
@ -230,7 +201,7 @@ impl<T: Value> ValueGrid<T> {
.iter()
.map(|elem| f(*elem))
.collect::<Vec<_>>();
ValueGrid::load(self.width(), self.height(), &data)
ValueGrid::load(self.width(), self.height(), &data).unwrap()
}
/// Copies a row from the grid.
@ -486,7 +457,7 @@ mod tests {
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]);
}
@ -525,7 +496,7 @@ mod tests {
#[test]
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 (x, val) in row.enumerate() {
assert_eq!(*val, (x + y) as u8);
@ -536,20 +507,20 @@ mod tests {
#[test]
#[should_panic]
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);
}
#[test]
#[should_panic]
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);
}
#[test]
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);
*top_left += 5;
@ -565,7 +536,7 @@ mod tests {
#[test]
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(-1, 0, 8);
grid.set_optional(0, 8, 42);
@ -577,7 +548,7 @@ mod tests {
#[test]
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(1), Some(vec![1, 3, 5]));
assert_eq!(grid.get_col(2), None);
@ -598,7 +569,7 @@ mod tests {
#[test]
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(2), Some(vec![4, 5]));
assert_eq!(grid.get_row(3), None);
@ -619,10 +590,10 @@ mod tests {
#[test]
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);
let grid = ValueGrid::wrap(4, &[0, 1, 2, 3, 4, 5]);
assert_eq!(grid.err(), Some(TryLoadValueGridError::InvalidDimensions));
let grid = ValueGrid::from_vec(4, vec![0, 1, 2, 3, 4, 5]);
assert_eq!(grid, None);
}
}

View file

@ -140,7 +140,7 @@ mod tests_feature_cp437 {
#[test]
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 actual = CharGrid::from(cp437);
assert_eq!(actual, utf8);