actix_web_rust_embed_responder/
rust_embed.rs

1use base64::{engine::general_purpose::STANDARD_NO_PAD as Base64Encoder, Engine};
2use chrono::TimeZone;
3use rust_embed::EmbeddedFile;
4use std::{borrow::Cow, ops::Deref};
5
6use crate::embed::{EmbedRespondable, EmbedResponse, IntoResponse};
7
8impl From<EmbeddedFile> for EmbedResponse<EmbeddedFile> {
9    fn from(file: EmbeddedFile) -> Self {
10        EmbedResponse {
11            file: Some(file),
12            compress: Default::default(),
13        }
14    }
15}
16
17impl From<Option<EmbeddedFile>> for EmbedResponse<EmbeddedFile> {
18    fn from(file: Option<EmbeddedFile>) -> Self {
19        EmbedResponse {
20            file,
21            compress: Default::default(),
22        }
23    }
24}
25
26impl IntoResponse<EmbeddedFile> for EmbeddedFile {
27    fn into_response(self) -> EmbedResponse<EmbeddedFile> {
28        self.into()
29    }
30}
31
32impl IntoResponse<EmbeddedFile> for Option<EmbeddedFile> {
33    fn into_response(self) -> EmbedResponse<EmbeddedFile> {
34        self.into()
35    }
36}
37
38impl EmbedRespondable for EmbeddedFile {
39    type Data = Cow<'static, [u8]>;
40    type DataGzip = Vec<u8>;
41    type DataBr = Vec<u8>;
42    type DataZstd = Vec<u8>;
43    type ETag = String;
44    type LastModified = String;
45    type MimeType = String;
46
47    fn data(&self) -> Self::Data {
48        self.data.clone()
49    }
50
51    fn data_gzip(&self) -> Option<Self::DataGzip> {
52        None
53    }
54
55    fn data_br(&self) -> Option<Self::DataBr> {
56        None
57    }
58
59    fn data_zstd(&self) -> Option<Self::DataZstd> {
60        None
61    }
62
63    fn last_modified(&self) -> Option<Self::LastModified> {
64        self.last_modified_timestamp().map(|timestamp| {
65            chrono::Utc
66                .timestamp_opt(timestamp, 0)
67                .unwrap()
68                .to_rfc2822()
69        })
70    }
71
72    fn last_modified_timestamp(&self) -> Option<i64> {
73        self.metadata
74            .last_modified()
75            // The last_modified value in rust-embed is u64, but it really
76            // should be i64. We'll try a safe conversion here.
77            .and_then(|v| v.try_into().ok())
78    }
79
80    fn etag(&self) -> Self::ETag {
81        format!("\"{}\"", Base64Encoder.encode(self.metadata.sha256_hash()))
82    }
83
84    fn mime_type(&self) -> Option<Self::MimeType> {
85        // rust-embed doesn't include the filename for the embedded file, so we
86        // can't guess the mime type. We could add `xdg-mime` to guess based on
87        // contents, but it will require the shared mime database to be
88        // available at runtime. In any case, it's okay if we just let the
89        // browser guess the mime type.
90        None
91    }
92}
93
94impl Deref for EmbedResponse<EmbeddedFile> {
95    type Target = Option<EmbeddedFile>;
96
97    fn deref(&self) -> &Self::Target {
98        &self.file
99    }
100}