airsprotocols_mcpserver_filesystem/binary/
format.rs

1//! File format detection and validation
2
3// Layer 1: Standard library imports
4use std::path::Path;
5
6// Layer 2: Third-party crate imports
7// (None needed for this module)
8
9// Layer 3: Internal module imports
10// (None needed yet)
11
12/// File format detection using magic numbers and content analysis
13#[derive(Debug)]
14pub struct FormatDetector;
15
16/// Supported file formats for binary processing
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum FileFormat {
19    // Image formats
20    Jpeg,
21    Png,
22    Gif,
23    WebP,
24    Tiff,
25    Bmp,
26
27    // Document formats
28    Pdf,
29
30    // Text formats
31    Text,
32
33    // Unknown/unsupported format
34    Unknown,
35}
36
37impl FormatDetector {
38    /// Create a new format detector
39    pub fn new() -> Self {
40        Self
41    }
42
43    /// Detect file format from file content (magic numbers)
44    pub fn detect_from_bytes(&self, bytes: &[u8]) -> FileFormat {
45        if let Some(kind) = infer::get(bytes) {
46            match kind.mime_type() {
47                "image/jpeg" => FileFormat::Jpeg,
48                "image/png" => FileFormat::Png,
49                "image/gif" => FileFormat::Gif,
50                "image/webp" => FileFormat::WebP,
51                "image/tiff" => FileFormat::Tiff,
52                "image/bmp" => FileFormat::Bmp,
53                "application/pdf" => FileFormat::Pdf,
54                _ => FileFormat::Unknown,
55            }
56        } else {
57            // Check if it's likely text content
58            if bytes
59                .iter()
60                .all(|&b| b.is_ascii() || b.is_ascii_whitespace())
61            {
62                FileFormat::Text
63            } else {
64                FileFormat::Unknown
65            }
66        }
67    }
68
69    /// Detect file format from file extension (fallback method)
70    pub fn detect_from_extension<P: AsRef<Path>>(&self, path: P) -> FileFormat {
71        if let Some(extension) = path.as_ref().extension() {
72            match extension.to_string_lossy().to_lowercase().as_str() {
73                "jpg" | "jpeg" => FileFormat::Jpeg,
74                "png" => FileFormat::Png,
75                "gif" => FileFormat::Gif,
76                "webp" => FileFormat::WebP,
77                "tiff" | "tif" => FileFormat::Tiff,
78                "bmp" => FileFormat::Bmp,
79                "pdf" => FileFormat::Pdf,
80                "txt" | "md" | "rs" | "py" | "js" | "html" | "css" | "json" | "toml" | "yml"
81                | "yaml" => FileFormat::Text,
82                _ => FileFormat::Unknown,
83            }
84        } else {
85            FileFormat::Unknown
86        }
87    }
88
89    /// Check if a format is supported for processing
90    pub fn is_supported(&self, format: FileFormat) -> bool {
91        !matches!(format, FileFormat::Unknown)
92    }
93}
94
95impl Default for FormatDetector {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101impl FileFormat {
102    /// Check if the format is an image
103    pub fn is_image(&self) -> bool {
104        matches!(
105            self,
106            FileFormat::Jpeg
107                | FileFormat::Png
108                | FileFormat::Gif
109                | FileFormat::WebP
110                | FileFormat::Tiff
111                | FileFormat::Bmp
112        )
113    }
114
115    /// Check if the format is a document
116    pub fn is_document(&self) -> bool {
117        matches!(self, FileFormat::Pdf)
118    }
119
120    /// Check if the format is text
121    pub fn is_text(&self) -> bool {
122        matches!(self, FileFormat::Text)
123    }
124
125    /// Get the MIME type for the format
126    pub fn mime_type(&self) -> &'static str {
127        match self {
128            FileFormat::Jpeg => "image/jpeg",
129            FileFormat::Png => "image/png",
130            FileFormat::Gif => "image/gif",
131            FileFormat::WebP => "image/webp",
132            FileFormat::Tiff => "image/tiff",
133            FileFormat::Bmp => "image/bmp",
134            FileFormat::Pdf => "application/pdf",
135            FileFormat::Text => "text/plain",
136            FileFormat::Unknown => "application/octet-stream",
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_format_detector_creation() {
147        let detector = FormatDetector::new();
148        // Basic creation test - just verify the detector can be created
149        assert!(std::mem::size_of_val(&detector) == std::mem::size_of::<FormatDetector>());
150    }
151
152    #[test]
153    fn test_detect_from_extension() {
154        let detector = FormatDetector::new();
155
156        assert_eq!(
157            detector.detect_from_extension("image.jpg"),
158            FileFormat::Jpeg
159        );
160        assert_eq!(
161            detector.detect_from_extension("document.pdf"),
162            FileFormat::Pdf
163        );
164        assert_eq!(
165            detector.detect_from_extension("script.rs"),
166            FileFormat::Text
167        );
168        assert_eq!(
169            detector.detect_from_extension("unknown.xyz"),
170            FileFormat::Unknown
171        );
172    }
173
174    #[test]
175    fn test_file_format_properties() {
176        assert!(FileFormat::Jpeg.is_image());
177        assert!(!FileFormat::Jpeg.is_document());
178        assert!(!FileFormat::Jpeg.is_text());
179
180        assert!(FileFormat::Pdf.is_document());
181        assert!(!FileFormat::Pdf.is_image());
182        assert!(!FileFormat::Pdf.is_text());
183
184        assert!(FileFormat::Text.is_text());
185        assert!(!FileFormat::Text.is_image());
186        assert!(!FileFormat::Text.is_document());
187    }
188
189    #[test]
190    fn test_mime_types() {
191        assert_eq!(FileFormat::Jpeg.mime_type(), "image/jpeg");
192        assert_eq!(FileFormat::Pdf.mime_type(), "application/pdf");
193        assert_eq!(FileFormat::Text.mime_type(), "text/plain");
194        assert_eq!(FileFormat::Unknown.mime_type(), "application/octet-stream");
195    }
196
197    #[test]
198    fn test_is_supported() {
199        let detector = FormatDetector::new();
200
201        assert!(detector.is_supported(FileFormat::Jpeg));
202        assert!(detector.is_supported(FileFormat::Pdf));
203        assert!(detector.is_supported(FileFormat::Text));
204        assert!(!detector.is_supported(FileFormat::Unknown));
205    }
206
207    #[test]
208    fn test_detect_from_bytes_text() {
209        let detector = FormatDetector::new();
210        let text_bytes = b"Hello, world!";
211
212        assert_eq!(detector.detect_from_bytes(text_bytes), FileFormat::Text);
213    }
214}