@@ -3,6 +3,48 @@ use std::fs::File;
3
3
use std:: io:: BufReader ;
4
4
use std:: path:: Path ;
5
5
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
+
6
48
const SKIP_EXTENSIONS : & [ & str ] = & [
7
49
".md" , ".mermaid" , ".mp3" , ".mp4" , ".webm" , ".pptx" , ".svg" , ".txt" , ".ico" ,
8
50
] ;
@@ -50,14 +92,20 @@ fn should_skip_validation(source: &str) -> bool {
50
92
}
51
93
52
94
/// 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
55
97
. 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
+ }
57
105
}
58
106
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 > {
61
109
let mut gps_data = Map :: new ( ) ;
62
110
for & tag in GPS_TAGS {
63
111
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>
73
121
"gps" : gps_data
74
122
} ) ;
75
123
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) )
90
126
}
91
127
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 {
94
129
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
+ } ,
122
148
}
123
149
}
124
150
125
- pub fn is_valid ( source : & str ) -> Result < bool , String > {
151
+ pub fn is_valid ( source : & str ) -> Result < ValidationResult , String > {
126
152
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
+ } ) ;
130
156
}
131
157
132
158
let path = Path :: new ( source) ;
@@ -139,20 +165,22 @@ pub fn is_valid(source: &str) -> Result<bool, String> {
139
165
140
166
// Read EXIF data
141
167
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) ) ,
156
184
}
157
185
}
158
186
@@ -186,71 +214,127 @@ mod tests {
186
214
#[ test]
187
215
fn test_handle_exif_error_types ( ) {
188
216
// Test different error types
189
- let test_file = "test.jpg" ;
190
217
191
- // Test InvalidFormat error - should return Ok(false)
218
+ // Test InvalidFormat error - should return Invalid
192
219
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
+ }
196
228
197
- // Test NotFound error - should return Ok(true)
229
+ // Test NotFound error - should return Valid with NoExifData
198
230
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
+ }
202
238
203
- // Test BlankValue error - should return Ok(true)
239
+ // Test BlankValue error - should return Valid with BlankExifValues
204
240
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
+ }
208
248
209
- // Test TooBig error - should return Ok(false)
249
+ // Test TooBig error - should return Invalid with FileTooLarge
210
250
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
+ }
214
259
}
215
260
216
261
#[ test]
217
262
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
+
221
285
// 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
+ }
226
313
}
227
314
228
315
#[ test]
229
316
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( ) ) ;
240
321
// .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
+ }
242
328
243
329
// 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
+ }
254
338
}
255
339
256
340
#[ test]
@@ -275,7 +359,7 @@ mod tests {
275
359
assert ! ( result. is_err( ) ) ;
276
360
assert ! ( result. unwrap_err( ) . contains( "File not found" ) ) ;
277
361
278
- // Test filename with multiple dots - .md is in SKIP_EXTENSIONS
362
+ // Test filename with multiple dots
279
363
assert ! ( should_skip_validation( "file.backup.md" ) ) ;
280
364
281
365
// Test very long filenames - .jpg is not in SKIP_EXTENSIONS
0 commit comments