opendal_layer_mime_guess/
lib.rs1#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22use std::sync::Arc;
23
24use opendal_core::raw::*;
25use opendal_core::*;
26
27#[derive(Clone, Debug, Default)]
63#[non_exhaustive]
64pub struct MimeGuessLayer {}
65
66impl MimeGuessLayer {
67 pub fn new() -> Self {
69 Self::default()
70 }
71}
72
73impl Layer for MimeGuessLayer {
74 fn apply_service(&self, inner: Servicer) -> Servicer {
75 Arc::new(self.layer(inner))
76 }
77}
78
79impl MimeGuessLayer {
80 fn layer(&self, inner: Servicer) -> MimeGuessAccessor {
81 MimeGuessAccessor(inner)
82 }
83}
84
85#[doc(hidden)]
86#[derive(Debug)]
87pub struct MimeGuessAccessor(Servicer);
88
89fn mime_from_path(path: &str) -> Option<&str> {
90 mime_guess::from_path(path).first_raw()
91}
92
93fn opwrite_with_mime(path: &str, op: OpWrite) -> OpWrite {
94 if op.content_type().is_some() {
95 return op;
96 }
97
98 if let Some(mime) = mime_from_path(path) {
99 return op.with_content_type(mime);
100 }
101
102 op
103}
104
105fn rpstat_with_mime(path: &str, rp: RpStat) -> RpStat {
106 rp.map_metadata(|metadata| {
107 if metadata.content_type().is_some() {
108 return metadata;
109 }
110
111 if let Some(mime) = mime_from_path(path) {
112 return metadata.with_content_type(mime.into());
113 }
114
115 metadata
116 })
117}
118
119impl Service for MimeGuessAccessor {
120 type Reader = oio::Reader;
121 type Writer = oio::Writer;
122 type Lister = oio::Lister;
123 type Deleter = oio::Deleter;
124 type Copier = oio::Copier;
125
126 fn info(&self) -> ServiceInfo {
127 self.0.info()
128 }
129
130 fn capability(&self) -> Capability {
131 self.0.capability()
132 }
133
134 async fn create_dir(
135 &self,
136 ctx: &OperationContext,
137 path: &str,
138 args: OpCreateDir,
139 ) -> Result<RpCreateDir> {
140 self.0.create_dir(ctx, path, args).await
141 }
142
143 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
144 self.0.read(ctx, path, args)
145 }
146
147 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
148 self.0.write(ctx, path, opwrite_with_mime(path, args))
149 }
150
151 fn copy(
152 &self,
153 ctx: &OperationContext,
154 from: &str,
155 to: &str,
156 args: OpCopy,
157 opts: OpCopier,
158 ) -> Result<Self::Copier> {
159 self.0.copy(ctx, from, to, args, opts)
160 }
161
162 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
163 self.0
164 .stat(ctx, path, args)
165 .await
166 .map(|rp| rpstat_with_mime(path, rp))
167 }
168
169 async fn rename(
170 &self,
171 ctx: &OperationContext,
172 from: &str,
173 to: &str,
174 args: OpRename,
175 ) -> Result<RpRename> {
176 self.0.rename(ctx, from, to, args).await
177 }
178
179 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
180 self.0.delete(ctx)
181 }
182
183 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
184 self.0.list(ctx, path, args)
185 }
186
187 async fn presign(
188 &self,
189 ctx: &OperationContext,
190 path: &str,
191 args: OpPresign,
192 ) -> Result<RpPresign> {
193 self.0.presign(ctx, path, args).await
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use futures::TryStreamExt;
200
201 use super::*;
202
203 const DATA: &str = "<html>test</html>";
204 const CUSTOM: &str = "text/custom";
205 const HTML: &str = "text/html";
206
207 #[tokio::test]
208 async fn test_async() -> Result<()> {
209 let op = Operator::new(services::Memory::default())?.layer(MimeGuessLayer::new());
210
211 op.write("test0.html", DATA).await?;
212 assert_eq!(op.stat("test0.html").await?.content_type(), Some(HTML));
213
214 op.write("test1.asdfghjkl", DATA).await?;
215 assert_eq!(op.stat("test1.asdfghjkl").await?.content_type(), None);
216
217 op.write_with("test2.html", DATA)
218 .content_type(CUSTOM)
219 .await?;
220 assert_eq!(op.stat("test2.html").await?.content_type(), Some(CUSTOM));
221
222 let entries = op
223 .lister_with("")
224 .await?
225 .and_then(|entry| {
226 let op = op.clone();
227 async move { op.stat(entry.path()).await }
228 })
229 .try_collect::<Vec<_>>()
230 .await?;
231 assert_eq!(entries[0].content_type(), Some(HTML));
232 assert_eq!(entries[1].content_type(), None);
233 assert_eq!(entries[2].content_type(), Some(CUSTOM));
234
235 Ok(())
236 }
237}