Skip to main content

nano_web/
response_buffer.rs

1use bytes::Bytes;
2use std::sync::Arc;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum Encoding {
6    Identity,
7    Gzip,
8    Brotli,
9    Zstd,
10}
11
12impl Encoding {
13    pub const ALL: [Self; 4] = [Self::Identity, Self::Gzip, Self::Brotli, Self::Zstd];
14
15    /// Parse Accept-Encoding header, priority: br > zstd > gzip > identity.
16    /// Splits on comma to avoid substring false positives (e.g. "br" matching "vibrant").
17    #[inline]
18    pub fn from_accept_encoding(accept: &str) -> Self {
19        let mut best = Self::Identity;
20        for part in accept.split(',') {
21            let token = part.split(';').next().unwrap_or("").trim();
22            match token {
23                "br" => return Self::Brotli, // highest priority, short-circuit
24                "zstd" => best = Self::Zstd,
25                "gzip" if !matches!(best, Self::Zstd) => best = Self::Gzip,
26                _ => {}
27            }
28        }
29        best
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct ResponseBuffer {
35    pub body: Bytes,
36    pub content_type: Arc<str>,
37    pub content_encoding: Option<&'static str>,
38    pub etag: Arc<str>,
39    pub last_modified: Arc<str>,
40    pub cache_control: Arc<str>,
41}
42
43impl ResponseBuffer {
44    pub fn new(
45        body: Bytes,
46        content_type: Arc<str>,
47        content_encoding: Option<&'static str>,
48        etag: Arc<str>,
49        last_modified: Arc<str>,
50        cache_control: Arc<str>,
51    ) -> Self {
52        Self {
53            body,
54            content_type,
55            content_encoding,
56            etag,
57            last_modified,
58            cache_control,
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn test_encoding_priority() {
69        assert_eq!(
70            Encoding::from_accept_encoding("gzip, br, zstd"),
71            Encoding::Brotli
72        );
73        assert_eq!(Encoding::from_accept_encoding("br"), Encoding::Brotli);
74        assert_eq!(Encoding::from_accept_encoding("gzip, zstd"), Encoding::Zstd);
75        assert_eq!(Encoding::from_accept_encoding("zstd"), Encoding::Zstd);
76        assert_eq!(Encoding::from_accept_encoding("gzip"), Encoding::Gzip);
77        assert_eq!(
78            Encoding::from_accept_encoding("deflate"),
79            Encoding::Identity
80        );
81        assert_eq!(Encoding::from_accept_encoding(""), Encoding::Identity);
82    }
83
84    #[test]
85    fn test_encoding_no_substring_false_positives() {
86        assert_eq!(
87            Encoding::from_accept_encoding("vibrant"),
88            Encoding::Identity
89        );
90        assert_eq!(Encoding::from_accept_encoding("broken"), Encoding::Identity);
91    }
92
93    #[test]
94    fn test_encoding_with_quality_values() {
95        assert_eq!(
96            Encoding::from_accept_encoding("gzip;q=1.0, br;q=0.8"),
97            Encoding::Brotli
98        );
99        assert_eq!(
100            Encoding::from_accept_encoding("gzip;q=0.5, zstd;q=1.0"),
101            Encoding::Zstd
102        );
103    }
104}