1use crate::{Colors, Config, Countable, MyResult, ResultExt};
2use serde::{Deserialize, Serialize};
3use std::{error::Error, fmt, num::ParseIntError};
4
5#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
9pub struct Dimension {
10 pub width: u64,
12 pub height: u64,
14}
15
16impl Default for Dimension {
17 fn default() -> Self {
19 Dimension {
20 width: 3840,
21 height: 2160,
22 }
23 }
24}
25
26impl Dimension {
27 pub fn new(string: &str) -> MyResult<Dimension> {
29 let numbers: Vec<u64> = split_str(string).unwrap_result();
30 let (width, height) = (numbers[0], numbers[1]);
31 Ok(Dimension { width, height })
32 }
33
34 pub fn minimum(&self) -> u64 {
36 self.height.min(self.width)
37 }
38
39 pub fn maximum(&self) -> u64 {
41 self.height.max(self.width)
42 }
43
44 pub fn is_valid(&self, config: &Config) -> bool {
46 config.in_range(self.minimum()) && config.in_range(self.maximum())
47 }
48
49 pub fn get_log_min(&self, config: &Config) -> String {
51 let num = self.maximum().count_chars();
53 let min = self.minimum();
54 let min = format!("{min:>num$}");
55 if !config.in_range(self.minimum()) {
56 format!(
57 "Minimum dimension: {min}. The condition ({config_min} <= {min} <= {config_max}) is false.\n",
58 min = min.yellow(),
59 config_min = config.min_dimension.green(),
60 config_max = config.max_dimension.green(),
61 )
62 } else {
63 "".to_string()
64 }
65 }
66
67 pub fn get_log_max(&self, config: &Config) -> String {
69 if !config.in_range(self.maximum()) {
70 format!(
71 "Maximum dimension: {max}. The condition ({config_min} <= {max} <= {config_max}) is false.\n",
72 max = self.maximum().yellow(),
73 config_min = config.min_dimension.green(),
74 config_max = config.max_dimension.green(),
75 )
76 } else {
77 "".to_string()
78 }
79 }
80}
81
82impl fmt::Display for Dimension {
83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 let height = self.height;
85 let width = self.width;
86
87 let max = height.max(width);
88 let n = max.count_chars();
89
90 write!(
91 f,
92 "Dimension {{ height: {height:>n$}, width: {width:>n$} }}",
93 )
94 }
95}
96
97pub fn split_str(string: &str) -> MyResult<Vec<u64>> {
119 let numbers: Vec<u64> = string
120 .split('x')
121 .map(|s| s.trim().parse::<u64>())
122 .collect::<Result<Vec<u64>, ParseIntError>>()
123 .map_err(|parse_error| {
124 DimError::InvalidParse(string.to_string(), parse_error)
126 })?;
127
128 if numbers.len() != 2 {
129 return Err(Box::new(DimError::InvalidFormat));
130 }
131
132 if numbers.contains(&0) {
133 return Err(Box::new(DimError::ZeroDimension));
134 }
135
136 Ok(numbers)
137}
138
139#[derive(Debug)]
140enum DimError {
141 InvalidFormat,
143 ZeroDimension,
145 InvalidParse(String, ParseIntError),
147}
148
149impl std::fmt::Display for DimError {
150 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
151 match self {
152 DimError::InvalidParse(string, parse_error) => {
153 write!(f, "Error: '{string}' split failed!\n{parse_error}",)
154 }
155 DimError::InvalidFormat => write!(f, "Invalid format: expected 'heightxwidth'."),
156 DimError::ZeroDimension => write!(f, "Width or height cannot be zero."),
157 }
158 }
159}
160
161impl Error for DimError {}
163
164#[cfg(test)]
165mod test_dimension {
166 use super::*;
167
168 #[test]
169 fn split_str_sample_1() {
171 let string = " 123 x 4567 ";
172
173 let result = split_str(string);
174
175 match result {
176 Ok(numbers) => {
177 assert_eq!(numbers[0], 123);
178 assert_eq!(numbers[1], 4567);
179 }
180 Err(err) => panic!("Unexpected error: {err}"),
181 }
182 }
183
184 #[test]
185 fn split_str_sample_2() {
191 let string = "x4567";
192
193 let result: MyResult<Vec<u64>> = split_str(string);
194 dbg!(&result);
195
196 let result_to_string: Result<Vec<u64>, String> =
197 split_str(string).map_err(|e| e.to_string());
198 dbg!(&result_to_string);
199
200 let opt_error: String = result_to_string.unwrap_err().to_string();
201 let parse_error = "cannot parse integer from empty string";
202 let output = format!("Error: '{string}' split failed!\n{parse_error}",);
203
204 let error = split_str(string).unwrap_err();
205 assert!(error.is::<DimError>());
206
207 assert!(result.is_err());
208 assert_eq!(opt_error, output);
209 }
210
211 #[test]
212 fn split_str_sample_3() {
214 let string = "12ab3x4567";
215
216 let result: MyResult<Vec<u64>> = split_str(string);
217 dbg!(&result);
218
219 let result_to_string: Result<Vec<u64>, String> =
220 split_str(string).map_err(|e| e.to_string());
221 dbg!(&result_to_string);
222
223 let opt_error: String = result_to_string.unwrap_err().to_string();
224 let parse_error = "invalid digit found in string";
225 let output = format!("Error: '{string}' split failed!\n{parse_error}",);
226
227 let error = split_str(string).unwrap_err();
228 assert!(error.is::<DimError>());
229
230 assert!(result.is_err());
231 assert_eq!(opt_error, output);
232 }
233
234 #[test]
235 fn split_str_sample_4() {
237 let string = "57x124x89";
238
239 let result = split_str(string);
240 dbg!(&result);
241 assert!(result.is_err());
242 assert_eq!(
243 result.unwrap_err().to_string(),
244 "Invalid format: expected 'heightxwidth'."
245 );
246 }
247
248 #[test]
249 fn split_str_sample_5() {
251 let string = "57x0";
252
253 let result = split_str(string);
254 dbg!(&result);
255 assert!(result.is_err());
256 assert_eq!(
257 result.unwrap_err().to_string(),
258 "Width or height cannot be zero."
259 );
260 }
261}