Repository: pyrossh/rust-embed (language: rust)

Issue:
Store the result from `mime_guess` statically at compile time?
The examples all show using `mime_guess` at runtime while serving the file to guess the mimetype, but it would be nice if there were a `mime_guess` feature that would make it statically compile the result of `mime_guess` into the `EmbeddedFile` type.


Discussion / hints:
Yeah seems like a nice idea. Want to raise a PR for it?
If implemented this should be behind a feature flag.
> Yeah seems like a nice idea. Want to raise a PR for it?

I'm not really sure how to implement it, but I'll take a look soon.

> If implemented this should be behind a feature flag.

Yeah, that's why I said it should be behind a feature called `mime_guess`

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index f3a4cb4..974dcb4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,6 +56,11 @@ name = "include_exclude"
 path = "tests/include_exclude.rs"
 required-features = ["include-exclude"]
 
+[[test]]
+name = "mime_guess"
+path = "tests/mime_guess.rs"
+required-features = ["mime-guess"]
+
 [dependencies]
 walkdir = "2.3.1"
 rust-embed-impl = { version = "6.3.1", path = "impl"}
@@ -79,6 +84,7 @@ sha2 = "0.10"
 debug-embed = ["rust-embed-impl/debug-embed", "rust-embed-utils/debug-embed"]
 interpolate-folder-path = ["rust-embed-impl/interpolate-folder-path"]
 compression = ["rust-embed-impl/compression", "include-flate"]
+mime-guess = ["rust-embed-impl/mime-guess"]
 include-exclude = ["rust-embed-impl/include-exclude", "rust-embed-utils/include-exclude"]
 actix = ["actix-web", "mime_guess"]
 warp-ex = ["warp", "tokio", "mime_guess"]
diff --git a/impl/Cargo.toml b/impl/Cargo.toml
index 53f7ac6..b932f23 100644
--- a/impl/Cargo.toml
+++ b/impl/Cargo.toml
@@ -30,4 +30,5 @@ optional = true
 debug-embed = []
 interpolate-folder-path = ["shellexpand"]
 compression = []
+mime-guess = ["rust-embed-utils/mime-guess"]
 include-exclude = ["rust-embed-utils/include-exclude"]
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 3345d93..aadb685 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -174,6 +174,13 @@ fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
     Some(last_modified) => quote! { Some(#last_modified) },
     None => quote! { None },
   };
+  #[cfg(feature = "mime-guess")]
+  let mimetype_tokens = {
+    let mt = file.metadata.mimetype();
+    quote! { , #mt }
+  };
+  #[cfg(not(feature = "mime-guess"))]
+  let mimetype_tokens = TokenStream2::new();
 
   let embedding_code = if cfg!(feature = "compression") {
     quote! {
@@ -192,7 +199,7 @@ fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
 
           Some(rust_embed::EmbeddedFile {
               data: std::borrow::Cow::from(bytes),
-              metadata: rust_embed::Metadata::__rust_embed_new([#(#hash),*], #last_modified)
+              metadata: rust_embed::Metadata::__rust_embed_new([#(#hash),*], #last_modified #mimetype_tokens)
           })
       }
   }
diff --git a/utils/Cargo.toml b/utils/Cargo.toml
index 7fc5cfd..4d38faa 100644
--- a/utils/Cargo.toml
+++ b/utils/Cargo.toml
@@ -14,6 +14,7 @@ edition = "2018"
 [dependencies]
 walkdir = "2.3.1"
 sha2 = "0.10.5"
+mime_guess = { version = "2.0.4", optional = true }
 
 [dependencies.globset]
 version = "0.4.8"
@@ -21,4 +22,5 @@ optional = true
 
 [features]
 debug-embed = []
+mime-guess = ["mime_guess"]
 include-exclude = ["globset"]
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index 6ff30ab..3ded6c7 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -87,12 +87,19 @@ pub struct EmbeddedFile {
 pub struct Metadata {
   hash: [u8; 32],
   last_modified: Option<u64>,
+  #[cfg(feature = "mime-guess")]
+  mimetype: Cow<'static, str>,
 }
 
 impl Metadata {
   #[doc(hidden)]
-  pub fn __rust_embed_new(hash: [u8; 32], last_modified: Option<u64>) -> Self {
-    Self { hash, last_modified }
+  pub fn __rust_embed_new(hash: [u8; 32], last_modified: Option<u64>, #[cfg(feature = "mime-guess")] mimetype: &'static str) -> Self {
+    Self {
+      hash,
+      last_modified,
+      #[cfg(feature = "mime-guess")]
+      mimetype: mimetype.into(),
+    }
   }
 
   /// The SHA256 hash of the file
@@ -105,6 +112,12 @@ impl Metadata {
   pub fn last_modified(&self) -> Option<u64> {
     self.last_modified
   }
+
+  /// The mime type of the file
+  #[cfg(feature = "mime-guess")]
+  pub fn mimetype(&self) -> &str {
+    &self.mimetype
+  }
 }
 
 pub fn read_file_from_fs(file_path: &Path) -> io::Result<EmbeddedFile> {
@@ -127,11 +140,16 @@ pub fn read_file_from_fs(file_path: &Path) -> io::Result<EmbeddedFile> {
       .as_secs()
   });
 
+  #[cfg(feature = "mime-guess")]
+  let mimetype = mime_guess::from_path(file_path).first_or_octet_stream().to_string();
+
   Ok(EmbeddedFile {
     data,
     metadata: Metadata {
       hash,
       last_modified: source_date_epoch.or(last_modified),
+      #[cfg(feature = "mime-guess")]
+      mimetype: mimetype.into(),
     },
   })
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5177d52..6d96dfc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -33,6 +33,8 @@ jobs:
           cargo test --test lib
           cargo test --test lib --features "debug-embed"
           cargo test --test lib --features "compression" --release
+          cargo test --test mime_guess --features "mime-guess"
+          cargo test --test mime_guess --features "mime-guess" --release
           cargo test --test interpolated_path --features "interpolate-folder-path"
           cargo test --test interpolated_path --features "interpolate-folder-path" --release
           cargo build --example basic
diff --git a/tests/mime_guess.rs b/tests/mime_guess.rs
new file mode 100644
index 0000000..b3cab50
--- /dev/null
+++ b/tests/mime_guess.rs
@@ -0,0 +1,35 @@
+use rust_embed::{EmbeddedFile, RustEmbed};
+
+#[derive(RustEmbed)]
+#[folder = "examples/public/"]
+struct Asset;
+
+#[test]
+fn html_mime_is_correct() {
+  let html_file: EmbeddedFile = Asset::get("index.html").expect("index.html exists");
+  assert_eq!(html_file.metadata.mimetype(), "text/html");
+}
+
+#[test]
+fn css_mime_is_correct() {
+  let css_file: EmbeddedFile = Asset::get("main.css").expect("main.css exists");
+  assert_eq!(css_file.metadata.mimetype(), "text/css");
+}
+
+#[test]
+fn js_mime_is_correct() {
+  let js_file: EmbeddedFile = Asset::get("main.js").expect("main.js exists");
+  assert_eq!(js_file.metadata.mimetype(), "application/javascript");
+}
+
+#[test]
+fn jpg_mime_is_correct() {
+  let jpg_file: EmbeddedFile = Asset::get("images/flower.jpg").expect("flower.jpg exists");
+  assert_eq!(jpg_file.metadata.mimetype(), "image/jpeg");
+}
+
+#[test]
+fn png_mime_is_correct() {
+  let png_file: EmbeddedFile = Asset::get("images/llama.png").expect("llama.png exists");
+  assert_eq!(png_file.metadata.mimetype(), "image/png");
+}