1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
use crate::{Colors, Config, Countable, DimensionError, WallSwitchError, WallSwitchResult};
use serde::{Deserialize, Serialize};
use std::{fmt, num::ParseIntError};
/// Dimension - width and length - of an image.
///
/// Image Size, Attribute, properties.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Dimension {
/// width of an image
pub width: u64,
/// length of an image
pub height: u64,
}
impl Default for Dimension {
/// Default 4K UHD Resolution
fn default() -> Self {
Dimension {
width: 3840,
height: 2160,
}
}
}
impl Dimension {
/// Get an instance of Dimension by specifying concrete values for each of the fields.
pub fn new(string: &str) -> WallSwitchResult<Dimension> {
let numbers: Vec<u64> = split_str(string)?;
let (width, height) = (numbers[0], numbers[1]);
Ok(Dimension { width, height })
}
/// Get the minimum value between height and width.
pub fn minimum(&self) -> u64 {
self.height.min(self.width)
}
/// Get the maximum value between height and width.
pub fn maximum(&self) -> u64 {
self.height.max(self.width)
}
/// Check if the minimum and maximum value are valid.
pub fn is_valid(&self, config: &Config) -> bool {
config.in_range(self.minimum()) && config.in_range(self.maximum())
}
/// Get error messages related to the minimum value.
pub fn get_log_min(&self, config: &Config) -> String {
// Number of digits of maximum value
let num = self.maximum().count_chars();
let min = self.minimum();
let min = format!("{min:>num$}");
if !config.in_range(self.minimum()) {
format!(
"Minimum dimension: {min}. The condition ({config_min} <= {min} <= {config_max}) is false.\n",
min = min.yellow(),
config_min = config.min_dimension.green(),
config_max = config.max_dimension.green(),
)
} else {
"".to_string()
}
}
/// Get error messages related to the maximum value.
pub fn get_log_max(&self, config: &Config) -> String {
if !config.in_range(self.maximum()) {
format!(
"Maximum dimension: {max}. The condition ({config_min} <= {max} <= {config_max}) is false.\n",
max = self.maximum().yellow(),
config_min = config.min_dimension.green(),
config_max = config.max_dimension.green(),
)
} else {
"".to_string()
}
}
}
impl fmt::Display for Dimension {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let height = self.height;
let width = self.width;
let max = height.max(width);
let n = max.count_chars();
write!(
f,
"Dimension {{ height: {height:>n$}, width: {width:>n$} }}",
)
}
}
/**
Split string into two numbers
Example:
```
use wallswitch::{split_str, WallSwitchResult};
fn main() -> WallSwitchResult<()> {
let string1: &str = "123x4567";
let string2: &str = " 123 x 4567 \n";
let integers1: Vec<u64> = split_str(string1)?;
let integers2: Vec<u64> = split_str(string2)?;
assert_eq!(integers1, [123, 4567]);
assert_eq!(integers1, integers2);
Ok(())
}
```
*/
pub fn split_str(string: &str) -> WallSwitchResult<Vec<u64>> {
let numbers: Vec<u64> = string
.split('x')
.map(|s| s.trim().parse::<u64>())
.collect::<Result<Vec<u64>, ParseIntError>>()
.map_err(|parse_error| {
// Corrected: Directly return DimensionError, which WallSwitchError can convert from
WallSwitchError::InvalidDimension(DimensionError::InvalidParse(
string.to_string(),
parse_error,
))
})?;
if numbers.len() != 2 {
// Corrected: Directly return DimensionError, which WallSwitchError can convert from
return Err(WallSwitchError::InvalidDimension(
DimensionError::InvalidFormat,
));
}
if numbers.contains(&0) {
// Corrected: Directly return DimensionError, which WallSwitchError can convert from
return Err(WallSwitchError::InvalidDimension(
DimensionError::ZeroDimension,
));
}
Ok(numbers)
}
#[cfg(test)]
mod test_dimension {
use super::*;
#[test]
/// `cargo test -- --show-output split_str_sample_1`
fn split_str_sample_1() {
let string = " 123 x 4567 ";
let result = split_str(string);
match result {
Ok(numbers) => {
assert_eq!(numbers[0], 123);
assert_eq!(numbers[1], 4567);
}
Err(err) => panic!("Unexpected error: {err}"),
}
}
#[test]
/// `cargo test -- --show-output split_str_sample_2`
///
/// <https://doc.rust-lang.org/rust-by-example/error.html>
///
/// <https://world.hey.com/ksiva/easy-error-handling-in-rust-19555479>
fn split_str_sample_2() {
let string = "x4567";
let result: WallSwitchResult<Vec<u64>> = split_str(string);
dbg!(&result);
let result_to_string: Result<Vec<u64>, String> =
split_str(string).map_err(|e| e.to_string());
dbg!(&result_to_string);
let opt_error: String = result_to_string.unwrap_err();
let expected_exact_error_message = format!(
"Invalid dimension format '{string}': failed to parse integer - cannot parse integer from empty string"
);
// Assert that the result is an error
assert!(result.is_err());
// Pattern match to check the specific error variant
match result.unwrap_err() {
WallSwitchError::InvalidDimension(DimensionError::InvalidParse(err_string, _)) => {
assert_eq!(err_string, string);
}
other_error => panic!(
"Expected InvalidDimension(InvalidParse), but got: {:?}",
other_error
),
}
// Compare the full error string exactly
assert_eq!(opt_error, expected_exact_error_message);
}
#[test]
/// `cargo test -- --show-output split_str_sample_3`
fn split_str_sample_3() {
let string = "12ab3x4567";
let result: WallSwitchResult<Vec<u64>> = split_str(string);
dbg!(&result);
// Using `map_err` to convert to a String for comparison, which is fine
let result_to_string: Result<Vec<u64>, String> =
split_str(string).map_err(|e| e.to_string());
dbg!(&result_to_string);
let opt_error: String = result_to_string.unwrap_err(); // .to_string() is already done in map_err
let parse_error_reason = "invalid digit found in string"; // The actual ParseIntError message
let expected_output_part = format!(
"Invalid dimension format '{string}': failed to parse integer - {parse_error_reason}"
);
// Assert that the result is an error
assert!(result.is_err());
// Pattern match to check the specific error variant
// We expect WallSwitchError::InvalidDimension wrapping DimensionError::InvalidParse
match result.unwrap_err() {
WallSwitchError::InvalidDimension(DimensionError::InvalidParse(
err_string,
parse_err,
)) => {
// Check if the contained string matches expectations
assert_eq!(err_string, string);
// Check the underlying ParseIntError's message
assert_eq!(parse_err.to_string(), parse_error_reason);
}
other_error => panic!(
"Expected InvalidDimension(InvalidParse), but got: {:?}",
other_error
),
}
// Compare the full error string from .to_string()
// Using contains as the error message might include more details than just `output`
assert!(opt_error.contains(&expected_output_part));
}
#[test]
/// `cargo test -- --show-output split_str_sample_4`
fn split_str_sample_4() {
let string = "57x124x89";
let result = split_str(string);
dbg!(&result);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid dimension format: expected two numbers (width x height)"
);
}
#[test]
/// `cargo test -- --show-output split_str_sample_5`
fn split_str_sample_5() {
let string = "57x0";
let result = split_str(string);
dbg!(&result);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Zero is not a valid dimension component"
);
}
}