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 ETag = String;
43    type LastModified = String;
44    type MimeType = String;
45
46    fn data(&self) -> Self::Data {
47        self.data.clone()
48    }
49
50    fn data_gzip(&self) -> Option<Self::DataGzip> {
51        None
52    }
53
54    fn data_br(&self) -> Option<Self::DataGzip> {
55        None
56    }
57
58    fn last_modified(&self) -> Option<Self::LastModified> {
59        self.last_modified_timestamp().map(|timestamp| {
60            chrono::Utc
61                .timestamp_opt(timestamp, 0)
62                .unwrap()
63                .to_rfc2822()
64        })
65    }
66
67    fn last_modified_timestamp(&self) -> Option<i64> {
68        self.metadata
69            .last_modified()
70            // The last_modified value in rust-embed is u64, but it really
71            // should be i64. We'll try a safe conversion here.
72            .and_then(|v| v.try_into().ok())
73    }
74
75    fn etag(&self) -> Self::ETag {
76        format!("\"{}\"", Base64Encoder.encode(self.metadata.sha256_hash()))
77    }
78
79    fn mime_type(&self) -> Option<Self::MimeType> {
80        // rust-embed doesn't include the filename for the embedded file, so we
81        // can't guess the mime type. We could add `xdg-mime` to guess based on
82        // contents, but it will require the shared mime database to be
83        // available at runtime. In any case, it's okay if we just let the
84        // browser guess the mime type.
85        None
86    }
87}
88
89impl Deref for EmbedResponse<EmbeddedFile> {
90    type Target = Option<EmbeddedFile>;
91
92    fn deref(&self) -> &Self::Target {
93        &self.file
94    }
95}