chie_shared/utils/
content.rs

1//! Content and file utility functions.
2
3/// Detect common file types from MIME type strings.
4/// Returns a category hint that can help with content classification.
5///
6/// # Examples
7///
8/// ```
9/// use chie_shared::mime_to_category_hint;
10///
11/// // Classify different MIME types
12/// assert_eq!(mime_to_category_hint("video/mp4"), "video");
13/// assert_eq!(mime_to_category_hint("audio/mpeg"), "audio");
14/// assert_eq!(mime_to_category_hint("image/png"), "image");
15/// assert_eq!(mime_to_category_hint("text/plain"), "document");
16/// assert_eq!(mime_to_category_hint("application/pdf"), "document");
17/// assert_eq!(mime_to_category_hint("application/zip"), "software");
18///
19/// // Case insensitive
20/// assert_eq!(mime_to_category_hint("VIDEO/MP4"), "video");
21///
22/// // Unknown types
23/// assert_eq!(mime_to_category_hint("unknown/type"), "other");
24/// ```
25#[must_use]
26pub fn mime_to_category_hint(mime_type: &str) -> &'static str {
27    let mime_lower = mime_type.to_lowercase();
28
29    if mime_lower.starts_with("video/") {
30        "video"
31    } else if mime_lower.starts_with("audio/") {
32        "audio"
33    } else if mime_lower.starts_with("image/") {
34        "image"
35    } else if mime_lower.starts_with("text/") || mime_lower.contains("document") {
36        "document"
37    } else if mime_lower.contains("application/") {
38        if mime_lower.contains("pdf") {
39            "document"
40        } else if mime_lower.contains("zip") || mime_lower.contains("archive") {
41            "software"
42        } else {
43            "other"
44        }
45    } else {
46        "other"
47    }
48}
49
50/// Get file extension from filename.
51/// Returns the extension without the dot, or empty string if no extension.
52///
53/// # Examples
54///
55/// ```
56/// use chie_shared::get_file_extension;
57///
58/// // Extract extensions
59/// assert_eq!(get_file_extension("document.pdf"), "pdf");
60/// assert_eq!(get_file_extension("video.mp4"), "mp4");
61/// assert_eq!(get_file_extension("archive.tar.gz"), "gz"); // Gets last extension
62///
63/// // Files without extensions
64/// assert_eq!(get_file_extension("README"), "");
65/// assert_eq!(get_file_extension("noextension"), "");
66///
67/// // Hidden files
68/// assert_eq!(get_file_extension(".gitignore"), "gitignore");
69/// ```
70#[must_use]
71pub fn get_file_extension(filename: &str) -> &str {
72    filename
73        .rfind('.')
74        .map(|pos| &filename[pos + 1..])
75        .unwrap_or("")
76}
77
78/// Check if a filename has a valid extension (non-empty and alphanumeric).
79#[must_use]
80pub fn has_valid_extension(filename: &str) -> bool {
81    if let Some(ext) = filename.rfind('.').map(|pos| &filename[pos + 1..]) {
82        !ext.is_empty() && ext.chars().all(|c| c.is_alphanumeric())
83    } else {
84        false
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_mime_to_category_hint() {
94        assert_eq!(mime_to_category_hint("video/mp4"), "video");
95        assert_eq!(mime_to_category_hint("VIDEO/MPEG"), "video");
96        assert_eq!(mime_to_category_hint("audio/mpeg"), "audio");
97        assert_eq!(mime_to_category_hint("AUDIO/WAV"), "audio");
98        assert_eq!(mime_to_category_hint("image/png"), "image");
99        assert_eq!(mime_to_category_hint("IMAGE/JPEG"), "image");
100        assert_eq!(mime_to_category_hint("text/plain"), "document");
101        assert_eq!(mime_to_category_hint("application/pdf"), "document");
102        assert_eq!(mime_to_category_hint("application/zip"), "software");
103        assert_eq!(mime_to_category_hint("application/json"), "other");
104        assert_eq!(mime_to_category_hint("unknown/type"), "other");
105    }
106
107    #[test]
108    fn test_get_file_extension() {
109        assert_eq!(get_file_extension("file.txt"), "txt");
110        assert_eq!(get_file_extension("document.pdf"), "pdf");
111        assert_eq!(get_file_extension("archive.tar.gz"), "gz");
112        assert_eq!(get_file_extension("noext"), "");
113        assert_eq!(get_file_extension(".hidden"), "hidden");
114        assert_eq!(get_file_extension("path/to/file.mp4"), "mp4");
115    }
116
117    #[test]
118    fn test_has_valid_extension() {
119        assert!(has_valid_extension("file.txt"));
120        assert!(has_valid_extension("document.pdf"));
121        assert!(has_valid_extension("archive.tar"));
122        assert!(!has_valid_extension("noext"));
123        assert!(!has_valid_extension("file."));
124        assert!(!has_valid_extension("file.tx t")); // Space in extension
125        assert!(has_valid_extension("file.mp4"));
126    }
127}