nano_web/
response_buffer.rs1use 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 pub fn from_accept_encoding(accept: &str) -> Self {
19 let mut best = Self::Identity;
20 for part in accept.split(',') {
21 let mut segments = part.split(';');
22 let token = segments.next().unwrap_or("").trim();
23
24 let rejected = segments.any(|s| {
26 s.trim()
27 .strip_prefix("q=")
28 .and_then(|v| v.trim().parse::<f32>().ok())
29 .is_some_and(|q| q == 0.0)
30 });
31 if rejected {
32 continue;
33 }
34
35 match token {
36 "br" => return Self::Brotli, "zstd" => best = Self::Zstd,
38 "gzip" if !matches!(best, Self::Zstd) => best = Self::Gzip,
39 _ => {}
40 }
41 }
42 best
43 }
44}
45
46#[derive(Debug, Clone)]
47pub struct ResponseBuffer {
48 pub body: Bytes,
49 pub content_type: Arc<str>,
50 pub content_encoding: Option<&'static str>,
51 pub etag: Arc<str>,
52 pub last_modified: Arc<str>,
53 pub cache_control: Arc<str>,
54 pub content_length: Arc<str>,
55 pub vary_encoding: bool,
57}
58
59impl ResponseBuffer {
60 pub fn new(
61 body: Bytes,
62 content_type: Arc<str>,
63 content_encoding: Option<&'static str>,
64 etag: Arc<str>,
65 last_modified: Arc<str>,
66 cache_control: Arc<str>,
67 vary_encoding: bool,
68 ) -> Self {
69 let content_length: Arc<str> = Arc::from(body.len().to_string().as_str());
70 Self {
71 body,
72 content_type,
73 content_encoding,
74 etag,
75 last_modified,
76 cache_control,
77 content_length,
78 vary_encoding,
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_encoding_priority() {
89 assert_eq!(
90 Encoding::from_accept_encoding("gzip, br, zstd"),
91 Encoding::Brotli
92 );
93 assert_eq!(Encoding::from_accept_encoding("br"), Encoding::Brotli);
94 assert_eq!(Encoding::from_accept_encoding("gzip, zstd"), Encoding::Zstd);
95 assert_eq!(Encoding::from_accept_encoding("zstd"), Encoding::Zstd);
96 assert_eq!(Encoding::from_accept_encoding("gzip"), Encoding::Gzip);
97 assert_eq!(
98 Encoding::from_accept_encoding("deflate"),
99 Encoding::Identity
100 );
101 assert_eq!(Encoding::from_accept_encoding(""), Encoding::Identity);
102 }
103
104 #[test]
105 fn test_encoding_no_substring_false_positives() {
106 assert_eq!(
107 Encoding::from_accept_encoding("vibrant"),
108 Encoding::Identity
109 );
110 assert_eq!(Encoding::from_accept_encoding("broken"), Encoding::Identity);
111 }
112
113 #[test]
114 fn test_encoding_with_quality_values() {
115 assert_eq!(
116 Encoding::from_accept_encoding("gzip;q=1.0, br;q=0.8"),
117 Encoding::Brotli
118 );
119 assert_eq!(
120 Encoding::from_accept_encoding("gzip;q=0.5, zstd;q=1.0"),
121 Encoding::Zstd
122 );
123 }
124
125 #[test]
126 fn test_encoding_respects_q_zero() {
127 assert_eq!(
129 Encoding::from_accept_encoding("br;q=0, gzip"),
130 Encoding::Gzip
131 );
132 assert_eq!(
133 Encoding::from_accept_encoding("br;q=0, zstd;q=0, gzip"),
134 Encoding::Gzip
135 );
136 assert_eq!(
137 Encoding::from_accept_encoding("br;q=0, zstd;q=0, gzip;q=0"),
138 Encoding::Identity
139 );
140 assert_eq!(
141 Encoding::from_accept_encoding("gzip;q=0"),
142 Encoding::Identity
143 );
144 }
145}