Skip to main content

rust_serv/memory_cache/
cached_file.rs

1//! Cached file representation
2
3use std::time::{Duration, Instant};
4use bytes::Bytes;
5
6/// A cached file with metadata
7#[derive(Clone, Debug)]
8pub struct CachedFile {
9    /// File content
10    pub content: Bytes,
11    /// MIME type
12    pub mime_type: String,
13    /// ETag for cache validation
14    pub etag: String,
15    /// Last modified timestamp (Unix timestamp)
16    pub last_modified: u64,
17    /// When this cache entry was created
18    pub created_at: Instant,
19    /// Time-to-live duration
20    pub ttl: Duration,
21    /// File size in bytes
22    pub size: usize,
23}
24
25impl CachedFile {
26    /// Create a new cached file
27    pub fn new(
28        content: Bytes,
29        mime_type: String,
30        etag: String,
31        last_modified: u64,
32        ttl: Duration,
33    ) -> Self {
34        let size = content.len();
35        Self {
36            content,
37            mime_type,
38            etag,
39            last_modified,
40            created_at: Instant::now(),
41            ttl,
42            size,
43        }
44    }
45
46    /// Check if the cache entry has expired
47    pub fn is_expired(&self) -> bool {
48        self.created_at.elapsed() > self.ttl
49    }
50
51    /// Get the age of the cache entry
52    pub fn age(&self) -> Duration {
53        self.created_at.elapsed()
54    }
55
56    /// Check if the ETag matches
57    pub fn etag_matches(&self, etag: &str) -> bool {
58        self.etag == etag
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_cached_file_creation() {
68        let content = Bytes::from("test content");
69        let cached = CachedFile::new(
70            content.clone(),
71            "text/plain".to_string(),
72            "\"abc123\"".to_string(),
73            1234567890,
74            Duration::from_secs(300),
75        );
76
77        assert_eq!(cached.content, content);
78        assert_eq!(cached.mime_type, "text/plain");
79        assert_eq!(cached.etag, "\"abc123\"");
80        assert_eq!(cached.last_modified, 1234567890);
81        assert_eq!(cached.size, 12);
82    }
83
84    #[test]
85    fn test_cached_file_not_expired() {
86        let cached = CachedFile::new(
87            Bytes::from("test"),
88            "text/plain".to_string(),
89            "\"etag\"".to_string(),
90            0,
91            Duration::from_secs(300),
92        );
93
94        assert!(!cached.is_expired());
95    }
96
97    #[test]
98    fn test_cached_file_expired() {
99        let mut cached = CachedFile::new(
100            Bytes::from("test"),
101            "text/plain".to_string(),
102            "\"etag\"".to_string(),
103            0,
104            Duration::from_secs(0),
105        );
106        // Force expiration by setting created_at to the past
107        cached.created_at = Instant::now() - Duration::from_secs(1);
108
109        assert!(cached.is_expired());
110    }
111
112    #[test]
113    fn test_cached_file_age() {
114        let cached = CachedFile::new(
115            Bytes::from("test"),
116            "text/plain".to_string(),
117            "\"etag\"".to_string(),
118            0,
119            Duration::from_secs(300),
120        );
121
122        let age = cached.age();
123        assert!(age < Duration::from_millis(100));
124    }
125
126    #[test]
127    fn test_etag_matches() {
128        let cached = CachedFile::new(
129            Bytes::from("test"),
130            "text/plain".to_string(),
131            "\"abc123\"".to_string(),
132            0,
133            Duration::from_secs(300),
134        );
135
136        assert!(cached.etag_matches("\"abc123\""));
137        assert!(!cached.etag_matches("\"xyz789\""));
138    }
139
140    #[test]
141    fn test_cached_file_size() {
142        let content = Bytes::from("Hello, World!");
143        let cached = CachedFile::new(
144            content,
145            "text/plain".to_string(),
146            "\"etag\"".to_string(),
147            0,
148            Duration::from_secs(300),
149        );
150
151        assert_eq!(cached.size, 13);
152    }
153
154    #[test]
155    fn test_cached_file_clone() {
156        let cached = CachedFile::new(
157            Bytes::from("test"),
158            "text/plain".to_string(),
159            "\"etag\"".to_string(),
160            0,
161            Duration::from_secs(300),
162        );
163
164        let cloned = cached.clone();
165        assert_eq!(cached.content, cloned.content);
166        assert_eq!(cached.mime_type, cloned.mime_type);
167        assert_eq!(cached.etag, cloned.etag);
168    }
169
170    #[test]
171    fn test_empty_cached_file() {
172        let cached = CachedFile::new(
173            Bytes::new(),
174            "text/plain".to_string(),
175            "\"etag\"".to_string(),
176            0,
177            Duration::from_secs(300),
178        );
179
180        assert_eq!(cached.size, 0);
181        assert_eq!(cached.content.len(), 0);
182    }
183}