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