From 77a8c01f349c5ffc061c416aaecadc79099014a7 Mon Sep 17 00:00:00 2001 From: Nia <7388349+conways-glider@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:48:27 -0600 Subject: [PATCH] Change Theme trait to return Result<> (#45) * Change Theme trait to return results * Add ThemeError --- examples/main.rs | 1 + src/error.rs | 24 ++++++++++- src/lib.rs | 8 ++-- src/theme/error.rs | 15 +++++++ src/{theme.rs => theme/mod.rs} | 76 +++++++++++++++++++++++++++------- 5 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 src/theme/error.rs rename src/{theme.rs => theme/mod.rs} (68%) diff --git a/examples/main.rs b/examples/main.rs index 13592e0..9971be2 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,4 +1,5 @@ use identicon_rs::error::IdenticonError; +use identicon_rs::theme::HSLRange; use identicon_rs::Identicon; fn main() -> Result<(), IdenticonError> { diff --git a/src/error.rs b/src/error.rs index 51f3b96..0e0a5a9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,8 @@ use thiserror::Error; -/// Identicon errors. +use crate::theme; + +/// Identicon errors #[derive(Error, Debug)] pub enum IdenticonError { /// Failed to generate the image. @@ -34,11 +36,15 @@ pub enum IdenticonError { /// Currently set scale value. scale: u32, }, + + /// Indicates an issue with the provided theme. + #[error(transparent)] + ThemeError(#[from] theme::error::ThemeError), } #[cfg(test)] mod tests { - use crate::error::IdenticonError; + use crate::{error::IdenticonError, theme::error::ThemeError}; #[test] fn generate_image_error_works() { @@ -76,4 +82,18 @@ mod tests { "identicon size too large: 5, must be less or equal to identicon scale: 3"; assert_eq!(expected_text, error.to_string()); } + + #[test] + fn theme_error_works() { + let theme_error = ThemeError::ThemeProcessingError("bad field".to_string()); + let identicon_error: IdenticonError = theme_error.into(); + match identicon_error { + IdenticonError::ThemeError(inner_error) => match inner_error { + ThemeError::ThemeProcessingError(_) => assert!(true), + _ => assert!(false, "wrong inner error type"), + }, + _ => assert!(false, "wrong error type"), + } + // assert_eq!(theme_error, identicon_error); + } } diff --git a/src/lib.rs b/src/lib.rs index fd99751..3554805 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,8 +179,8 @@ impl Identicon { let grid = grid::generate_full_grid(self.size, &self.hash); // Create pixel objects - let color_active = self.theme.main_color(&self.hash); - let color_background = self.theme.background_color(&self.hash); + let color_active = self.theme.main_color(&self.hash)?; + let color_background = self.theme.background_color(&self.hash)?; let pixel_active = image::Rgb([color_active.red, color_active.green, color_active.blue]); let pixel_background = image::Rgb([ color_background.red, @@ -312,7 +312,9 @@ mod tests { let image = Identicon::new("test"); let grid = crate::grid::generate_full_grid(image.size, &image.hash); - let color = crate::theme::default_theme().main_color(&image.hash); + let color = crate::theme::default_theme() + .main_color(&image.hash) + .expect("could not get color"); assert_eq!(expected_color, color); diff --git a/src/theme/error.rs b/src/theme/error.rs new file mode 100644 index 0000000..d7b55c0 --- /dev/null +++ b/src/theme/error.rs @@ -0,0 +1,15 @@ +use thiserror::Error; + +/// Theme Errors +/// +/// These are provided for writing new types of themes using the Theme trait. +#[derive(Error, Debug)] +pub enum ThemeError { + /// Theme failed validation + #[error("theme validation failed: {0}")] + ThemeValidationError(String), + + /// Theme failed to generate a color + #[error("theme processing failed: {0}")] + ThemeProcessingError(String), +} diff --git a/src/theme.rs b/src/theme/mod.rs similarity index 68% rename from src/theme.rs rename to src/theme/mod.rs index f171f75..0063c54 100644 --- a/src/theme.rs +++ b/src/theme/mod.rs @@ -1,12 +1,19 @@ +use error::ThemeError; + use crate::{color::RGB, map_values::map_values}; +/// Theme Errors +/// +/// Identicon Errors can wrap these errors +pub mod error; + /// Trait defining requirements for an identicon theme pub trait Theme { /// This should return the main color within the identicon image - fn main_color(&self, hash: &[u8]) -> RGB; + fn main_color(&self, hash: &[u8]) -> Result; /// This should return the background color within the identicon image - fn background_color(&self, hash: &[u8]) -> RGB; + fn background_color(&self, hash: &[u8]) -> Result; } /// Simple selection theme struct @@ -27,14 +34,26 @@ pub struct Selection { } impl Theme for Selection { - fn main_color(&self, hash: &[u8]) -> RGB { - let index = hash[0 % hash.len()] as usize % self.main.len(); - self.main[index] + fn main_color(&self, hash: &[u8]) -> Result { + if self.main.is_empty() { + Err(ThemeError::ThemeValidationError( + "main color selection is empty".to_string(), + )) + } else { + let index = hash[0 % hash.len()] as usize % self.main.len(); + Ok(self.main[index]) + } } - fn background_color(&self, hash: &[u8]) -> RGB { - let index = hash[2 % hash.len()] as usize % self.background.len(); - self.background[index] + fn background_color(&self, hash: &[u8]) -> Result { + if self.background.is_empty() { + Err(ThemeError::ThemeValidationError( + "background color selection is empty".to_string(), + )) + } else { + let index = hash[2 % hash.len()] as usize % self.background.len(); + Ok(self.background[index]) + } } } @@ -79,8 +98,31 @@ pub struct HSLRange { background: Vec, } +impl HSLRange { + fn validate(&self) -> Result<(), ThemeError> { + if self.hue_max < self.hue_min { + Err(ThemeError::ThemeValidationError( + "hue_max must be larger than hue_min".to_string(), + )) + } else if self.saturation_max < self.saturation_min { + Err(ThemeError::ThemeValidationError( + "saturation_max must be larger than saturation_min".to_string(), + )) + } else if self.lightness_max < self.lightness_min { + Err(ThemeError::ThemeValidationError( + "lightness_max must be larger than lightness_min".to_string(), + )) + } else { + Ok(()) + } + } +} + impl Theme for HSLRange { - fn main_color(&self, hash: &[u8]) -> RGB { + fn main_color(&self, hash: &[u8]) -> Result { + // Validate the fields + self.validate()?; + // Compute hash for hue space in larger bitspace let hue_hash = ((hash[0 % hash.len()] as u16) << 8) | hash[1 % hash.len()] as u16; @@ -138,16 +180,22 @@ impl Theme for HSLRange { let green = (g_prime + m) * 255.0; let blue = (b_prime + m) * 255.0; - RGB { + Ok(RGB { red: red as u8, green: green as u8, blue: blue as u8, - } + }) } - fn background_color(&self, hash: &[u8]) -> RGB { - let index = hash[2 % hash.len()] as usize % self.background.len(); - self.background[index] + fn background_color(&self, hash: &[u8]) -> Result { + if self.background.is_empty() { + Err(ThemeError::ThemeValidationError( + "background color selection is empty".to_string(), + )) + } else { + let index = hash[2 % hash.len()] as usize % self.background.len(); + Ok(self.background[index]) + } } }