1use anyhow::Result;
2use base64::{engine::general_purpose, Engine as _};
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone, Copy)]
8pub enum ImageFormat {
9 Jpeg,
10 Png,
11 Gif,
12 WebP,
13}
14
15impl ImageFormat {
16 pub fn from_extension(ext: &str) -> Option<Self> {
18 match ext.to_lowercase().as_str() {
19 "jpg" | "jpeg" => Some(ImageFormat::Jpeg),
20 "png" => Some(ImageFormat::Png),
21 "gif" => Some(ImageFormat::Gif),
22 "webp" => Some(ImageFormat::WebP),
23 _ => None,
24 }
25 }
26
27 pub fn mime_type(&self) -> &'static str {
29 match self {
30 ImageFormat::Jpeg => "image/jpeg",
31 ImageFormat::Png => "image/png",
32 ImageFormat::Gif => "image/gif",
33 ImageFormat::WebP => "image/webp",
34 }
35 }
36}
37
38pub fn process_image_file(path: &Path) -> Result<String> {
40 if !path.exists() {
42 anyhow::bail!("Image file not found: {}", path.display());
43 }
44
45 let extension = path
47 .extension()
48 .and_then(|ext| ext.to_str())
49 .ok_or_else(|| anyhow::anyhow!("No file extension found"))?;
50
51 let format = ImageFormat::from_extension(extension)
52 .ok_or_else(|| anyhow::anyhow!("Unsupported image format: {}", extension))?;
53
54 let image_data = fs::read(path)?;
56
57 const MAX_SIZE: usize = 20 * 1024 * 1024; if image_data.len() > MAX_SIZE {
60 anyhow::bail!(
61 "Image file too large: {} bytes (max: {} bytes)",
62 image_data.len(),
63 MAX_SIZE
64 );
65 }
66
67 let base64_data = general_purpose::STANDARD.encode(&image_data);
69
70 let data_url = format!("data:{};base64,{}", format.mime_type(), base64_data);
72
73 Ok(data_url)
74}
75
76pub fn process_image_url(url: &str) -> Result<String> {
78 if !url.starts_with("http://") && !url.starts_with("https://") {
81 anyhow::bail!("Invalid image URL: must start with http:// or https://");
82 }
83
84 Ok(url.to_string())
85}
86
87pub fn process_images(paths: &[String]) -> Result<Vec<String>> {
89 let mut processed_images = Vec::new();
90
91 for path_str in paths {
92 let processed = if path_str.starts_with("http://") || path_str.starts_with("https://") {
93 process_image_url(path_str)?
94 } else {
95 let path = Path::new(path_str);
96 process_image_file(path)?
97 };
98
99 processed_images.push(processed);
100 }
101
102 Ok(processed_images)
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_image_format_detection() {
111 assert!(matches!(
112 ImageFormat::from_extension("jpg"),
113 Some(ImageFormat::Jpeg)
114 ));
115 assert!(matches!(
116 ImageFormat::from_extension("JPEG"),
117 Some(ImageFormat::Jpeg)
118 ));
119 assert!(matches!(
120 ImageFormat::from_extension("png"),
121 Some(ImageFormat::Png)
122 ));
123 assert!(matches!(
124 ImageFormat::from_extension("gif"),
125 Some(ImageFormat::Gif)
126 ));
127 assert!(matches!(
128 ImageFormat::from_extension("webp"),
129 Some(ImageFormat::WebP)
130 ));
131 assert!(ImageFormat::from_extension("txt").is_none());
132 }
133
134 #[test]
135 fn test_mime_types() {
136 assert_eq!(ImageFormat::Jpeg.mime_type(), "image/jpeg");
137 assert_eq!(ImageFormat::Png.mime_type(), "image/png");
138 assert_eq!(ImageFormat::Gif.mime_type(), "image/gif");
139 assert_eq!(ImageFormat::WebP.mime_type(), "image/webp");
140 }
141}