opendal/layers/
mime_guess.rs1use crate::raw::*;
19use crate::Result;
20
21#[derive(Debug, Clone, Default)]
62#[non_exhaustive]
63pub struct MimeGuessLayer {}
64
65impl<A: Access> Layer<A> for MimeGuessLayer {
66 type LayeredAccess = MimeGuessAccessor<A>;
67
68 fn layer(&self, inner: A) -> Self::LayeredAccess {
69 MimeGuessAccessor(inner)
70 }
71}
72
73#[derive(Clone, Debug)]
74pub struct MimeGuessAccessor<A: Access>(A);
75
76fn mime_from_path(path: &str) -> Option<&str> {
77 mime_guess::from_path(path).first_raw()
78}
79
80fn opwrite_with_mime(path: &str, op: OpWrite) -> OpWrite {
81 if op.content_type().is_some() {
82 return op;
83 }
84
85 if let Some(mime) = mime_from_path(path) {
86 return op.with_content_type(mime);
87 }
88
89 op
90}
91
92fn rpstat_with_mime(path: &str, rp: RpStat) -> RpStat {
93 rp.map_metadata(|metadata| {
94 if metadata.content_type().is_some() {
95 return metadata;
96 }
97
98 if let Some(mime) = mime_from_path(path) {
99 return metadata.with_content_type(mime.into());
100 }
101
102 metadata
103 })
104}
105
106impl<A: Access> LayeredAccess for MimeGuessAccessor<A> {
107 type Inner = A;
108 type Reader = A::Reader;
109 type Writer = A::Writer;
110 type Lister = A::Lister;
111 type Deleter = A::Deleter;
112
113 fn inner(&self) -> &Self::Inner {
114 &self.0
115 }
116
117 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
118 self.inner().read(path, args).await
119 }
120
121 async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
122 self.inner()
123 .write(path, opwrite_with_mime(path, args))
124 .await
125 }
126
127 async fn stat(&self, path: &str, args: OpStat) -> Result<RpStat> {
128 self.inner()
129 .stat(path, args)
130 .await
131 .map(|rp| rpstat_with_mime(path, rp))
132 }
133
134 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
135 self.inner().delete().await
136 }
137
138 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
139 self.inner().list(path, args).await
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use futures::TryStreamExt;
146
147 use super::*;
148 use crate::services::Memory;
149 use crate::Metadata;
150 use crate::Operator;
151
152 const DATA: &str = "<html>test</html>";
153 const CUSTOM: &str = "text/custom";
154 const HTML: &str = "text/html";
155
156 #[tokio::test]
157 async fn test_async() {
158 let op = Operator::new(Memory::default())
159 .unwrap()
160 .layer(MimeGuessLayer::default())
161 .finish();
162
163 op.write("test0.html", DATA).await.unwrap();
164 assert_eq!(
165 op.stat("test0.html").await.unwrap().content_type(),
166 Some(HTML)
167 );
168
169 op.write("test1.asdfghjkl", DATA).await.unwrap();
170 assert_eq!(
171 op.stat("test1.asdfghjkl").await.unwrap().content_type(),
172 None
173 );
174
175 op.write_with("test2.html", DATA)
176 .content_type(CUSTOM)
177 .await
178 .unwrap();
179 assert_eq!(
180 op.stat("test2.html").await.unwrap().content_type(),
181 Some(CUSTOM)
182 );
183
184 let entries: Vec<Metadata> = op
185 .lister_with("")
186 .await
187 .unwrap()
188 .and_then(|entry| {
189 let op = op.clone();
190 async move { op.stat(entry.path()).await }
191 })
192 .try_collect()
193 .await
194 .unwrap();
195 assert_eq!(entries[0].content_type(), Some(HTML));
196 assert_eq!(entries[1].content_type(), None);
197 assert_eq!(entries[2].content_type(), Some(CUSTOM));
198 }
199}