Skip to content

Commit 4833dc7

Browse files
committed
refactor(lib/image): offload logging to an outer function
1 parent 80247f1 commit 4833dc7

File tree

2 files changed

+247
-113
lines changed

2 files changed

+247
-113
lines changed

rust-lib/src/image_validator.rs

Lines changed: 196 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,48 @@ use std::fs::File;
33
use std::io::BufReader;
44
use std::path::Path;
55

6+
#[derive(Debug, Clone, PartialEq)]
7+
pub enum GpsDetectionResult {
8+
Found,
9+
NotFound,
10+
}
11+
12+
#[derive(Debug, Clone, PartialEq)]
13+
pub enum ValidationResult {
14+
Valid {
15+
reason: ValidReason,
16+
},
17+
Invalid {
18+
reason: InvalidReason,
19+
gps_data: Option<String>,
20+
},
21+
Skipped {
22+
reason: SkipReason,
23+
},
24+
}
25+
26+
#[derive(Debug, Clone, PartialEq)]
27+
pub enum ValidReason {
28+
HasExifNoGps,
29+
NoExifData,
30+
BlankExifValues,
31+
}
32+
33+
#[derive(Debug, Clone, PartialEq)]
34+
pub enum InvalidReason {
35+
GpsInfoFound,
36+
InvalidFormat(String),
37+
FileTooLarge(String),
38+
ExifError(String),
39+
}
40+
41+
/// Reasons why file validation was skipped
42+
#[derive(Debug, Clone, PartialEq)]
43+
pub enum SkipReason {
44+
/// File extension is in skip list
45+
SkippedExtension,
46+
}
47+
648
const SKIP_EXTENSIONS: &[&str] = &[
749
".md", ".mermaid", ".mp3", ".mp4", ".webm", ".pptx", ".svg", ".txt", ".ico",
850
];
@@ -50,14 +92,20 @@ fn should_skip_validation(source: &str) -> bool {
5092
}
5193

5294
/// Check if EXIF data contains any GPS information
53-
fn has_gps_information(exif_data: &exif::Exif) -> bool {
54-
GPS_TAGS
95+
fn has_gps_information(exif_data: &exif::Exif) -> GpsDetectionResult {
96+
let has_gps = GPS_TAGS
5597
.iter()
56-
.any(|&tag| exif_data.get_field(tag, exif::In::PRIMARY).is_some())
98+
.any(|&tag| exif_data.get_field(tag, exif::In::PRIMARY).is_some());
99+
100+
if has_gps {
101+
GpsDetectionResult::Found
102+
} else {
103+
GpsDetectionResult::NotFound
104+
}
57105
}
58106

59-
/// Collect GPS data from EXIF and format as JSON
60-
fn collect_gps_data(exif_data: &exif::Exif, source: &str) -> Result<(), String> {
107+
/// Collect GPS data from EXIF and format as JSON string
108+
fn collect_gps_data(exif_data: &exif::Exif, source: &str) -> Result<String, String> {
61109
let mut gps_data = Map::new();
62110
for &tag in GPS_TAGS {
63111
if let Some(field) = exif_data.get_field(tag, exif::In::PRIMARY) {
@@ -73,60 +121,38 @@ fn collect_gps_data(exif_data: &exif::Exif, source: &str) -> Result<(), String>
73121
"gps": gps_data
74122
});
75123

76-
match serde_json::to_string_pretty(&json_output) {
77-
Ok(formatted_json) => {
78-
println!("{}", formatted_json);
79-
Ok(())
80-
}
81-
Err(json_err) => {
82-
crate::log_error(format!(
83-
"{}: Failed to format GPS data as JSON - {}",
84-
source, json_err
85-
))
86-
.map_err(|e| e.to_string())?;
87-
Ok(())
88-
}
89-
}
124+
serde_json::to_string_pretty(&json_output)
125+
.map_err(|e| format!("Failed to format GPS data as JSON: {}", e))
90126
}
91127

92-
/// Handle EXIF reading errors with appropriate logging
93-
fn handle_exif_error(source: &str, exif_err: exif::Error) -> Result<bool, String> {
128+
fn handle_exif_error(exif_err: exif::Error) -> ValidationResult {
94129
match exif_err {
95-
exif::Error::InvalidFormat(msg) => {
96-
crate::log_error(format!("{}: Invalid file format - {}", source, msg))
97-
.map_err(|e| e.to_string())?;
98-
return Ok(false);
99-
}
100-
exif::Error::NotFound(_msg) => {
101-
crate::log_debug(format!("{}: No EXIF data found", source)).map_err(|e| e.to_string())?;
102-
return Ok(true);
103-
}
104-
exif::Error::BlankValue(msg) => {
105-
crate::log_warn(format!("{}: EXIF contains blank values - {}", source, msg))
106-
.map_err(|e| e.to_string())?;
107-
return Ok(true);
108-
}
109-
exif::Error::TooBig(msg) => {
110-
crate::log_error(format!(
111-
"{}: File is too large to process - {}",
112-
source, msg
113-
))
114-
.map_err(|e| e.to_string())?;
115-
return Ok(false);
116-
}
117-
_ => {
118-
crate::log_error(format!("{}: EXIF reading failed - {}", source, exif_err))
119-
.map_err(|e| e.to_string())?;
120-
return Ok(false);
121-
}
130+
exif::Error::InvalidFormat(msg) => ValidationResult::Invalid {
131+
reason: InvalidReason::InvalidFormat(msg.to_string()),
132+
gps_data: None,
133+
},
134+
exif::Error::NotFound(_msg) => ValidationResult::Valid {
135+
reason: ValidReason::NoExifData,
136+
},
137+
exif::Error::BlankValue(_msg) => ValidationResult::Valid {
138+
reason: ValidReason::BlankExifValues,
139+
},
140+
exif::Error::TooBig(msg) => ValidationResult::Invalid {
141+
reason: InvalidReason::FileTooLarge(msg.to_string()),
142+
gps_data: None,
143+
},
144+
_ => ValidationResult::Invalid {
145+
reason: InvalidReason::ExifError(exif_err.to_string()),
146+
gps_data: None,
147+
},
122148
}
123149
}
124150

125-
pub fn is_valid(source: &str) -> Result<bool, String> {
151+
pub fn is_valid(source: &str) -> Result<ValidationResult, String> {
126152
if should_skip_validation(source) {
127-
crate::log_warn(format!("asset validation skipped - : {}", source))
128-
.map_err(|e| e.to_string())?;
129-
return Ok(true);
153+
return Ok(ValidationResult::Skipped {
154+
reason: SkipReason::SkippedExtension,
155+
});
130156
}
131157

132158
let path = Path::new(source);
@@ -139,20 +165,22 @@ pub fn is_valid(source: &str) -> Result<bool, String> {
139165

140166
// Read EXIF data
141167
match exif::Reader::new().read_from_container(&mut reader) {
142-
Ok(exif_data) => {
143-
if has_gps_information(&exif_data) {
144-
crate::log_error(format!("{}: has GPS info", source)).map_err(|e| e.to_string())?;
145-
146-
collect_gps_data(&exif_data, source)?;
147-
return Ok(false);
148-
} else {
149-
crate::log_warn(format!("{}: has EXIF", source)).map_err(|e| e.to_string())?;
150-
return Ok(true);
151-
}
152-
}
153-
Err(exif_err) => {
154-
return handle_exif_error(source, exif_err);
155-
}
168+
Ok(exif_data) => match has_gps_information(&exif_data) {
169+
GpsDetectionResult::Found => match collect_gps_data(&exif_data, source) {
170+
Ok(gps_json) => Ok(ValidationResult::Invalid {
171+
reason: InvalidReason::GpsInfoFound,
172+
gps_data: Some(gps_json),
173+
}),
174+
Err(_json_error) => Ok(ValidationResult::Invalid {
175+
reason: InvalidReason::GpsInfoFound,
176+
gps_data: None,
177+
}),
178+
},
179+
GpsDetectionResult::NotFound => Ok(ValidationResult::Valid {
180+
reason: ValidReason::HasExifNoGps,
181+
}),
182+
},
183+
Err(exif_err) => Ok(handle_exif_error(exif_err)),
156184
}
157185
}
158186

@@ -186,71 +214,127 @@ mod tests {
186214
#[test]
187215
fn test_handle_exif_error_types() {
188216
// Test different error types
189-
let test_file = "test.jpg";
190217

191-
// Test InvalidFormat error - should return Ok(false)
218+
// Test InvalidFormat error - should return Invalid
192219
let invalid_format_err = exif::Error::InvalidFormat("Invalid JPEG");
193-
let result = handle_exif_error(test_file, invalid_format_err);
194-
assert!(result.is_ok());
195-
assert_eq!(result.unwrap(), false);
220+
let result = handle_exif_error(invalid_format_err);
221+
match result {
222+
ValidationResult::Invalid {
223+
reason: InvalidReason::InvalidFormat(_),
224+
..
225+
} => {}
226+
_ => panic!("Expected Invalid with InvalidFormat reason"),
227+
}
196228

197-
// Test NotFound error - should return Ok(true)
229+
// Test NotFound error - should return Valid with NoExifData
198230
let not_found_err = exif::Error::NotFound("No EXIF");
199-
let result = handle_exif_error(test_file, not_found_err);
200-
assert!(result.is_ok());
201-
assert_eq!(result.unwrap(), true);
231+
let result = handle_exif_error(not_found_err);
232+
match result {
233+
ValidationResult::Valid {
234+
reason: ValidReason::NoExifData,
235+
} => {}
236+
_ => panic!("Expected Valid with NoExifData reason"),
237+
}
202238

203-
// Test BlankValue error - should return Ok(true)
239+
// Test BlankValue error - should return Valid with BlankExifValues
204240
let blank_value_err = exif::Error::BlankValue("Empty value");
205-
let result = handle_exif_error(test_file, blank_value_err);
206-
assert!(result.is_ok());
207-
assert_eq!(result.unwrap(), true);
241+
let result = handle_exif_error(blank_value_err);
242+
match result {
243+
ValidationResult::Valid {
244+
reason: ValidReason::BlankExifValues,
245+
} => {}
246+
_ => panic!("Expected Valid with BlankExifValues reason"),
247+
}
208248

209-
// Test TooBig error - should return Ok(false)
249+
// Test TooBig error - should return Invalid with FileTooLarge
210250
let too_big_err = exif::Error::TooBig("File too large");
211-
let result = handle_exif_error(test_file, too_big_err);
212-
assert!(result.is_ok());
213-
assert_eq!(result.unwrap(), false);
251+
let result = handle_exif_error(too_big_err);
252+
match result {
253+
ValidationResult::Invalid {
254+
reason: InvalidReason::FileTooLarge(_),
255+
..
256+
} => {}
257+
_ => panic!("Expected Invalid with FileTooLarge reason"),
258+
}
214259
}
215260

216261
#[test]
217262
fn test_skip_extensions() {
218-
assert!(is_valid("test.md").unwrap());
219-
assert!(is_valid("test.svg").unwrap());
220-
assert!(is_valid("test.txt").unwrap());
263+
// Test that skip extensions return Skipped result
264+
match is_valid("test.md").unwrap() {
265+
ValidationResult::Skipped {
266+
reason: SkipReason::SkippedExtension,
267+
} => {}
268+
_ => panic!("Expected Skipped result for .md file"),
269+
}
270+
271+
match is_valid("test.svg").unwrap() {
272+
ValidationResult::Skipped {
273+
reason: SkipReason::SkippedExtension,
274+
} => {}
275+
_ => panic!("Expected Skipped result for .svg file"),
276+
}
277+
278+
match is_valid("test.txt").unwrap() {
279+
ValidationResult::Skipped {
280+
reason: SkipReason::SkippedExtension,
281+
} => {}
282+
_ => panic!("Expected Skipped result for .txt file"),
283+
}
284+
221285
// Test case insensitive extensions
222-
assert!(is_valid("test.MD").unwrap());
223-
assert!(is_valid("test.SVG").unwrap());
224-
assert!(is_valid("test.TXT").unwrap());
225-
assert!(is_valid("test.Mp4").unwrap());
286+
match is_valid("test.MD").unwrap() {
287+
ValidationResult::Skipped {
288+
reason: SkipReason::SkippedExtension,
289+
} => {}
290+
_ => panic!("Expected Skipped result for .MD file"),
291+
}
292+
293+
match is_valid("test.SVG").unwrap() {
294+
ValidationResult::Skipped {
295+
reason: SkipReason::SkippedExtension,
296+
} => {}
297+
_ => panic!("Expected Skipped result for .SVG file"),
298+
}
299+
300+
match is_valid("test.TXT").unwrap() {
301+
ValidationResult::Skipped {
302+
reason: SkipReason::SkippedExtension,
303+
} => {}
304+
_ => panic!("Expected Skipped result for .TXT file"),
305+
}
306+
307+
match is_valid("test.Mp4").unwrap() {
308+
ValidationResult::Skipped {
309+
reason: SkipReason::SkippedExtension,
310+
} => {}
311+
_ => panic!("Expected Skipped result for .Mp4 file"),
312+
}
226313
}
227314

228315
#[test]
229316
fn test_unsupported_image_formats() {
230-
// These formats are not in SKIP_EXTENSIONS and files don't exist,
231-
// so they should return Err with "File not found"
232-
let result = is_valid("test.gif");
233-
assert!(result.is_err());
234-
assert!(result.unwrap_err().contains("File not found"));
235-
236-
let result = is_valid("test.bmp");
237-
assert!(result.is_err());
238-
assert!(result.unwrap_err().contains("File not found"));
239-
317+
// These should return ValidationResult indicating valid (no GPS found) or error
318+
// since these files don't exist, they should return an error
319+
assert!(is_valid("test.gif").is_err());
320+
assert!(is_valid("test.bmp").is_err());
240321
// .ico is in SKIP_EXTENSIONS, so it should be skipped
241-
assert!(is_valid("test.ico").unwrap());
322+
match is_valid("test.ico").unwrap() {
323+
ValidationResult::Skipped {
324+
reason: SkipReason::SkippedExtension,
325+
} => {}
326+
_ => panic!("Expected Skipped result for .ico file"),
327+
}
242328

243329
// Test case insensitive
244-
let result = is_valid("test.GIF");
245-
assert!(result.is_err());
246-
assert!(result.unwrap_err().contains("File not found"));
247-
248-
let result = is_valid("test.BMP");
249-
assert!(result.is_err());
250-
assert!(result.unwrap_err().contains("File not found"));
251-
252-
// .ICO is in SKIP_EXTENSIONS, so it should be skipped (case insensitive)
253-
assert!(is_valid("test.ICO").unwrap());
330+
assert!(is_valid("test.GIF").is_err());
331+
assert!(is_valid("test.BMP").is_err());
332+
match is_valid("test.ICO").unwrap() {
333+
ValidationResult::Skipped {
334+
reason: SkipReason::SkippedExtension,
335+
} => {}
336+
_ => panic!("Expected Skipped result for .ICO file"),
337+
}
254338
}
255339

256340
#[test]
@@ -275,7 +359,7 @@ mod tests {
275359
assert!(result.is_err());
276360
assert!(result.unwrap_err().contains("File not found"));
277361

278-
// Test filename with multiple dots - .md is in SKIP_EXTENSIONS
362+
// Test filename with multiple dots
279363
assert!(should_skip_validation("file.backup.md"));
280364

281365
// Test very long filenames - .jpg is not in SKIP_EXTENSIONS

0 commit comments

Comments
 (0)