diff --git a/Cargo.toml b/Cargo.toml index 6ad7ac6..54e2d80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ rust-embed-for-web-utils = { version = "11.2.1", path = "utils" } chrono = { version = "0.4", default-features = false } flate2 = "1.0" brotli = "6.0" +zstd = "0.13" actix-web = "4.4" [features] @@ -32,6 +33,7 @@ include-exclude = [ "rust-embed-for-web-impl/include-exclude", "rust-embed-for-web-utils/include-exclude", ] +compression-zstd = ["rust-embed-for-web-impl/compression-zstd", "rust-embed-for-web-utils/compression-zstd"] [workspace] members = ["impl", "utils"] @@ -39,6 +41,11 @@ members = ["impl", "utils"] [[test]] name = "compression" path = "tests/compression.rs" +required-features = ["always-embed", "compression-zstd"] + +[[test]] +name = "compression_without_zstd" +path = "tests/compression_without_zstd.rs" required-features = ["always-embed"] [[test]] @@ -46,6 +53,11 @@ name = "gzip" path = "tests/gzip.rs" required-features = ["always-embed"] +[[test]] +name = "zstd" +path = "tests/zstd.rs" +required-features = ["always-embed", "compression-zstd"] + [[test]] name = "include-exclude" path = "tests/include-exclude.rs" diff --git a/README.md b/README.md index e056db5..09fcba1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ executable in exchange for better performance at runtime. In particular: or decompress anything at runtime. - If the compression makes little difference, for example a jpeg file won't compress much further if at all, then the compressed version is not included. - - You can also disable this behavior by adding an attribute `#[gzip = false]` and `#[br = false]` + - You can also disable this behavior by adding an attribute `#[gzip = false]`, `#[br = false]`, or `#[zstd = false]` When disabled, the compressed files won't be included for that embed. - Some metadata that is useful for web headers like `ETag` and `Last-Modified` are computed ahead of time and embedded into the executable. This makes it @@ -68,17 +68,19 @@ The path for the `folder` is resolved relative to where `Cargo.toml` is. ### Disabling compression -You can add `#[gzip = false]` and/or `#[br = false]` attributes to your embed to -disable gzip and brotli compression for the files in that embed. +You can add `#[gzip = false]`, `#[br = false]`, and/or `#[zstd = false]` attributes to your embed to +disable gzip, brotli, and/or zstd compression for the files in that embed. `rust-embed-for-web` will only include compressed files where the compression actually makes files smaller so files that won't compress well like images or archives already don't include their compressed versions. However you can ## Features -Both of the following features are enabled by default. +### Default Features -### `interpolate-folder-path` +The following features are enabled by default. + +#### `interpolate-folder-path` Allow environment variables and `~`s to be used in the `folder` path. Example: @@ -91,7 +93,7 @@ struct Asset; `~` will expand into your home folder, and `${PROJECT_NAME}` will expand into the value of the `PROJECT_NAME` environment variable. -### `include-exclude` +#### `include-exclude` You can filter which files are embedded by adding one or more `#[include = "*.txt"]` and `#[exclude = "*.jpg"]` attributes. Matching is done on relative file paths --the paths you use for the `.get` call-- via [`globset`](https://docs.rs/globset/latest/globset/). @@ -111,7 +113,26 @@ For example, if you wanted to exclude all `.svg` files except for one named struct Assets; ``` -### `prefix` +### Optional Features + +#### `compression-zstd` + +Enables zstd compression support for embedded files. When enabled, files will be compressed with zstd (in addition to gzip and brotli), allowing you to serve zstd-compressed content to clients that support it. + +**Note:** This feature is **not enabled by default** because the `zstd` crate uses C bindings, which may not be compatible with all build environments. + +To enable zstd compression, add this to your `Cargo.toml`: + +```toml +[dependencies] +rust-embed-for-web = { version = "11.2.1", features = ["compression-zstd"] } +``` + +You can also disable zstd compression for specific embeds using the `#[zstd = false]` attribute as described in the "Disabling compression" section above. + +### Other Configuration + +#### `prefix` You can specify a prefix, which will be added to the path of the files. For example: diff --git a/impl/Cargo.toml b/impl/Cargo.toml index 098a502..e86b644 100644 --- a/impl/Cargo.toml +++ b/impl/Cargo.toml @@ -27,6 +27,7 @@ walkdir = "2.4.0" # Compression flate2 = "1.0" brotli = "6.0" +zstd = { version = "0.13", optional = true } globset = { version = "0.4", optional = true } @@ -39,3 +40,4 @@ default = [] interpolate-folder-path = ["shellexpand"] include-exclude = ["rust-embed-for-web-utils/include-exclude", "globset"] always-embed = [] +compression-zstd = ["zstd", "rust-embed-for-web-utils/compression-zstd"] diff --git a/impl/src/attributes.rs b/impl/src/attributes.rs index 42dec84..9068311 100644 --- a/impl/src/attributes.rs +++ b/impl/src/attributes.rs @@ -42,6 +42,7 @@ pub(crate) fn read_attribute_config(ast: &syn::DeriveInput) -> Config { "exclude" => parse_str(attribute).map(|v| config.add_exclude(v)), "gzip" => parse_bool(attribute).map(|v| config.set_gzip(v)), "br" => parse_bool(attribute).map(|v| config.set_br(v)), + "zstd" => parse_bool(attribute).map(|v| config.set_zstd(v)), _ => None, }; } diff --git a/impl/src/compress.rs b/impl/src/compress.rs index a0a8ff0..445241e 100644 --- a/impl/src/compress.rs +++ b/impl/src/compress.rs @@ -2,6 +2,8 @@ use std::io::{BufReader, Write}; use brotli::enc::BrotliEncoderParams; use flate2::{write::GzEncoder, Compression}; +#[cfg(feature = "compression-zstd")] +use zstd::stream::write::Encoder as ZstdEncoder; /// Only include the compressed version if it is at least this much smaller than /// the uncompressed version. @@ -39,3 +41,31 @@ pub(crate) fn compress_br(data: &[u8]) -> Option> { None } } + +/// Compresses data using zstd compression. +/// +/// Returns the compressed data if it's smaller than the threshold, `None` otherwise. +/// Uses compression level 3 as a balance between compression ratio and speed. +#[cfg(feature = "compression-zstd")] +pub(crate) fn compress_zstd(data: &[u8]) -> Option> { + let mut data_zstd: Vec = Vec::new(); + // Level 3 provides good compression with reasonable speed for build-time compression + let mut encoder = ZstdEncoder::new(&mut data_zstd, 3).expect("Failed to create zstd encoder"); + encoder + .write_all(data) + .expect("Failed to compress zstd data"); + encoder + .finish() + .expect("Failed to finish compression of zstd data"); + + if data_zstd.len() < ((data.len() as f64) * COMPRESSION_INCLUDE_THRESHOLD) as usize { + Some(data_zstd) + } else { + None + } +} + +#[cfg(not(feature = "compression-zstd"))] +pub(crate) fn compress_zstd(_data: &[u8]) -> Option> { + None +} diff --git a/impl/src/embed.rs b/impl/src/embed.rs index 6b775ee..7c777c6 100644 --- a/impl/src/embed.rs +++ b/impl/src/embed.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream as TokenStream2; use rust_embed_for_web_utils::{get_files, Config, DynamicFile, EmbedableFile, FileEntry}; -use crate::compress::{compress_br, compress_gzip}; +use crate::compress::{compress_br, compress_gzip, compress_zstd}; /// Anything that can be embedded into the program. /// @@ -70,6 +70,11 @@ impl<'t> MakeEmbed for EmbedDynamicFile<'t> { } else { None::>.make_embed() }; + let data_zstd = if self.config.should_zstd() { + compress_zstd(&data).make_embed() + } else { + None::>.make_embed() + }; let data = data.make_embed(); let hash = file.hash().make_embed(); let etag = file.etag().make_embed(); @@ -83,6 +88,7 @@ impl<'t> MakeEmbed for EmbedDynamicFile<'t> { #data, #data_gzip, #data_br, + #data_zstd, #hash, #etag, #last_modified, diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 4484382..8f38ff8 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -87,7 +87,10 @@ fn impl_rust_embed_for_web(ast: &syn::DeriveInput) -> TokenStream2 { } } -#[proc_macro_derive(RustEmbed, attributes(folder, prefix, include, exclude, gzip, br))] +#[proc_macro_derive( + RustEmbed, + attributes(folder, prefix, include, exclude, gzip, br, zstd) +)] /// A folder that is embedded into your program. /// /// For example: diff --git a/tests/basic.rs b/tests/basic.rs index dab08bb..399607f 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -41,6 +41,7 @@ fn file_name_exists() { #[test] fn readme_example() { let index = Embed::get("index.html").unwrap().data(); + #[allow(clippy::useless_asref)] let contents = std::str::from_utf8(index.as_ref()).unwrap(); assert!(!contents.is_empty()); } diff --git a/tests/compression.rs b/tests/compression.rs index dcf5a68..baf17b0 100644 --- a/tests/compression.rs +++ b/tests/compression.rs @@ -11,6 +11,7 @@ struct Embed; fn html_files_are_compressed() { assert!(Embed::get("index.html").unwrap().data_gzip().is_some()); assert!(Embed::get("index.html").unwrap().data_br().is_some()); + assert!(Embed::get("index.html").unwrap().data_zstd().is_some()); } #[test] @@ -20,6 +21,10 @@ fn image_files_are_not_compressed() { .data_gzip() .is_none()); assert!(Embed::get("images/flower.jpg").unwrap().data_br().is_none()); + assert!(Embed::get("images/flower.jpg") + .unwrap() + .data_zstd() + .is_none()); } #[test] @@ -27,7 +32,7 @@ fn compression_gzip_roundtrip() { let compressed = Embed::get("index.html").unwrap().data_gzip().unwrap(); let mut decompressed: Vec = Vec::new(); let mut decoder = GzDecoder::new(&mut decompressed); - decoder.write_all(&compressed[..]).unwrap(); + decoder.write_all(compressed).unwrap(); decoder.finish().unwrap(); let decompressed_body = String::from_utf8_lossy(&decompressed[..]); assert!(decompressed_body.starts_with("")); @@ -37,8 +42,16 @@ fn compression_gzip_roundtrip() { fn compression_br_roundtrip() { let compressed = Embed::get("index.html").unwrap().data_br().unwrap(); let mut decompressed: Vec = Vec::new(); - let mut data_read = BufReader::new(&compressed[..]); + let mut data_read = BufReader::new(compressed); brotli::BrotliDecompress(&mut data_read, &mut decompressed).unwrap(); let decompressed_body = String::from_utf8_lossy(&decompressed[..]); assert!(decompressed_body.starts_with("")); } + +#[test] +fn compression_zstd_roundtrip() { + let compressed = Embed::get("index.html").unwrap().data_zstd().unwrap(); + let decompressed = zstd::bulk::decompress(compressed, 1024 * 1024).unwrap(); + let decompressed_body = String::from_utf8_lossy(&decompressed[..]); + assert!(decompressed_body.starts_with("")); +} diff --git a/tests/compression_without_zstd.rs b/tests/compression_without_zstd.rs new file mode 100644 index 0000000..51fdd31 --- /dev/null +++ b/tests/compression_without_zstd.rs @@ -0,0 +1,64 @@ +use std::io::{BufReader, Write}; + +use flate2::write::GzDecoder; +use rust_embed_for_web::{EmbedableFile, RustEmbed}; + +#[derive(RustEmbed)] +#[folder = "examples/public"] +struct Embed; + +#[test] +fn html_files_gzip_and_br_compression() { + assert!(Embed::get("index.html").unwrap().data_gzip().is_some()); + assert!(Embed::get("index.html").unwrap().data_br().is_some()); +} + +#[test] +fn zstd_behavior_without_feature() { + // When compression-zstd feature is not enabled, data_zstd should return None + #[cfg(not(feature = "compression-zstd"))] + { + assert!(Embed::get("index.html").unwrap().data_zstd().is_none()); + } + + // When compression-zstd feature is enabled, it may return Some or None based on effectiveness + #[cfg(feature = "compression-zstd")] + { + // Just test that the method doesn't panic + let _ = Embed::get("index.html").unwrap().data_zstd(); + } +} + +#[test] +fn image_files_are_not_compressed() { + assert!(Embed::get("images/flower.jpg") + .unwrap() + .data_gzip() + .is_none()); + assert!(Embed::get("images/flower.jpg").unwrap().data_br().is_none()); + assert!(Embed::get("images/flower.jpg") + .unwrap() + .data_zstd() + .is_none()); +} + +#[test] +fn compression_gzip_roundtrip() { + let compressed = Embed::get("index.html").unwrap().data_gzip().unwrap(); + let mut decompressed: Vec = Vec::new(); + let mut decoder = GzDecoder::new(&mut decompressed); + decoder.write_all(compressed).unwrap(); + decoder.finish().unwrap(); + let decompressed_body = String::from_utf8_lossy(&decompressed[..]); + assert!(decompressed_body.starts_with("")); +} + +#[test] +fn compression_br_roundtrip() { + let compressed = Embed::get("index.html").unwrap().data_br().unwrap(); + let mut decompressed: Vec = Vec::new(); + let mut data_read = BufReader::new(compressed); + brotli::BrotliDecompress(&mut data_read, &mut decompressed).unwrap(); + let decompressed_body = String::from_utf8_lossy(&decompressed[..]); + assert!(decompressed_body.starts_with("")); +} diff --git a/tests/dynamic.rs b/tests/dynamic.rs new file mode 100644 index 0000000..f8dcef2 --- /dev/null +++ b/tests/dynamic.rs @@ -0,0 +1,104 @@ +use rust_embed_for_web::{EmbedableFile, RustEmbed}; + +// This test is designed to run in debug mode without always-embed feature +// to test the DynamicFile code paths that always return None for compressed data +#[derive(RustEmbed)] +#[folder = "examples/public/"] +struct DynamicAssets; + +#[test] +fn dynamic_file_compressed_data_is_none() { + // In debug mode without always-embed, this should use DynamicFile + let file = DynamicAssets::get("index.html").unwrap(); + + // When always-embed is not enabled, DynamicFile always returns None for compressed data + #[cfg(not(feature = "always-embed"))] + { + assert!(file.data_gzip().is_none()); + assert!(file.data_br().is_none()); + assert!(file.data_zstd().is_none()); + } + + // When always-embed is enabled, EmbeddedFile may have compressed data + #[cfg(feature = "always-embed")] + { + // Just verify the file exists and has data - compression depends on the build + assert!(!file.data().is_empty()); + } + + // But it should always have the original data + assert!(!file.data().is_empty()); +} + +#[test] +fn dynamic_file_image_compressed_data_is_none() { + // Test with an image file too + let file = DynamicAssets::get("images/flower.jpg").unwrap(); + + // When always-embed is not enabled, DynamicFile always returns None for compressed data + #[cfg(not(feature = "always-embed"))] + { + assert!(file.data_gzip().is_none()); + assert!(file.data_br().is_none()); + assert!(file.data_zstd().is_none()); + } + + // When always-embed is enabled, EmbeddedFile may have compressed data (usually None for images) + #[cfg(feature = "always-embed")] + { + // Just verify the file exists and has data + assert!(!file.data().is_empty()); + } + + // But it should always have the original data + assert!(!file.data().is_empty()); +} + +#[test] +fn explicit_dynamic_compression_coverage() { + // Explicitly test to ensure coverage of DynamicFile compression methods + let file = DynamicAssets::get("index.html").unwrap(); + + // When always-embed is not enabled, test the DynamicFile paths + #[cfg(not(feature = "always-embed"))] + { + // Test each compression method explicitly to ensure coverage + let gzip_result = file.data_gzip(); + assert_eq!(gzip_result, None); + + let br_result = file.data_br(); + assert_eq!(br_result, None); + + let zstd_result = file.data_zstd(); + assert_eq!(zstd_result, None); + } + + // When always-embed is enabled, test the EmbeddedFile paths + #[cfg(feature = "always-embed")] + { + // Just verify the methods work - the actual compressed data depends on build configuration + let _gzip_result = file.data_gzip(); + let _br_result = file.data_br(); + let _zstd_result = file.data_zstd(); + } + + // Ensure we have actual data though + let actual_data = file.data(); + assert!(!actual_data.is_empty()); +} + +#[test] +fn specific_dynamic_none_coverage() { + // Create a DynamicFile directly to ensure we test the None paths + use rust_embed_for_web::{DynamicFile, EmbedableFile}; + + let file = DynamicFile::read_from_fs("examples/public/index.html").unwrap(); + + // These should all return None for DynamicFile, ensuring coverage of those lines + assert!(file.data_gzip().is_none()); + assert!(file.data_br().is_none()); + assert!(file.data_zstd().is_none()); + + // But the regular data should work + assert!(!file.data().is_empty()); +} diff --git a/tests/feature_zstd.rs b/tests/feature_zstd.rs new file mode 100644 index 0000000..e3f415c --- /dev/null +++ b/tests/feature_zstd.rs @@ -0,0 +1,43 @@ +use rust_embed_for_web::{EmbedableFile, RustEmbed}; + +#[derive(RustEmbed)] +#[folder = "examples/public/"] +struct Assets; + +#[test] +fn zstd_feature_behavior() { + let file = Assets::get("index.html").unwrap(); + + // Test that zstd behavior matches feature flag + #[cfg(feature = "compression-zstd")] + { + // When feature is enabled, data_zstd might return Some or None + // depending on build configuration and compression effectiveness + let zstd_data = file.data_zstd(); + // Just verify that the method exists and doesn't panic + match zstd_data { + Some(_) => { + // Zstd compression was effective + } + None => { + // Zstd compression was not effective or disabled for this file + } + } + } + + #[cfg(not(feature = "compression-zstd"))] + { + // When feature is disabled, data_zstd should always return None + assert!(file.data_zstd().is_none()); + } +} + +#[test] +fn zstd_default_trait_implementation() { + use rust_embed_for_web::{DynamicFile, EmbedableFile}; + + let file = DynamicFile::read_from_fs("examples/public/index.html").unwrap(); + + // For DynamicFile, data_zstd should always return None regardless of feature + assert!(file.data_zstd().is_none()); +} diff --git a/tests/zstd.rs b/tests/zstd.rs new file mode 100644 index 0000000..1ca2451 --- /dev/null +++ b/tests/zstd.rs @@ -0,0 +1,39 @@ +use rust_embed_for_web::{EmbedableFile, RustEmbed}; + +#[derive(RustEmbed)] +#[folder = "examples/public/"] +struct DefaultZstd; + +#[derive(RustEmbed)] +#[folder = "examples/public/"] +#[zstd = false] +struct FalseZstd; + +#[derive(RustEmbed)] +#[folder = "examples/public/"] +#[zstd = true] +struct TrueZstd; + +#[test] +fn zstd_is_used_by_default() { + let file = DefaultZstd::get("index.html").unwrap(); + assert!(file.data_zstd().is_some()); +} + +#[test] +fn zstd_is_used_when_enabled() { + let file = TrueZstd::get("index.html").unwrap(); + assert!(file.data_zstd().is_some()); +} + +#[test] +fn zstd_is_not_available_when_disabled() { + let file = FalseZstd::get("index.html").unwrap(); + assert!(file.data_zstd().is_none()); +} + +#[test] +fn image_files_dont_get_zstd_compressed() { + let file = DefaultZstd::get("images/flower.jpg").unwrap(); + assert!(file.data_zstd().is_none()); +} diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 6e3bf9f..540b4be 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -26,3 +26,4 @@ globset = { version = "0.4", optional = true } [features] default = [] include-exclude = ["globset"] +compression-zstd = [] diff --git a/utils/src/config.rs b/utils/src/config.rs index d4571da..524bbd4 100644 --- a/utils/src/config.rs +++ b/utils/src/config.rs @@ -9,6 +9,7 @@ pub struct Config { exclude: Vec, gzip: bool, br: bool, + zstd: bool, } impl Default for Config { @@ -20,6 +21,10 @@ impl Default for Config { exclude: vec![], gzip: true, br: true, + #[cfg(feature = "compression-zstd")] + zstd: true, + #[cfg(not(feature = "compression-zstd"))] + zstd: false, } } } @@ -56,6 +61,11 @@ impl Config { self.br = status; } + /// Enable or disable zstd compression for embedded files. + pub fn set_zstd(&mut self, status: bool) { + self.zstd = status; + } + #[cfg(feature = "include-exclude")] pub fn get_includes(&self) -> &Vec { &self.include @@ -99,4 +109,19 @@ impl Config { pub fn should_br(&self) -> bool { self.br } + + /// Check if zstd compression should be used for embedded files. + /// + /// Returns `false` when the compression-zstd feature is not enabled, + /// even if the config value is set to `true`. + pub fn should_zstd(&self) -> bool { + #[cfg(feature = "compression-zstd")] + { + self.zstd + } + #[cfg(not(feature = "compression-zstd"))] + { + false + } + } } diff --git a/utils/src/file/common.rs b/utils/src/file/common.rs index 44b7615..e7862e0 100644 --- a/utils/src/file/common.rs +++ b/utils/src/file/common.rs @@ -37,6 +37,21 @@ pub trait EmbedableFile { /// not precompressed, either because the file doesn't benefit from /// compression or because gzip was disabled with `#[br = false]`. fn data_br(&self) -> Option; + /// The contents of the file, compressed with zstd. + /// + /// This is `Some` if precompression has been done. `None` if the file was + /// not precompressed, either because the file doesn't benefit from + /// compression or because zstd was disabled with `#[zstd = false]`. + #[cfg(feature = "compression-zstd")] + fn data_zstd(&self) -> Option; + + /// The contents of the file, compressed with zstd. + /// + /// Always returns `None` when the compression-zstd feature is disabled. + #[cfg(not(feature = "compression-zstd"))] + fn data_zstd(&self) -> Option { + None + } /// The UNIX timestamp of when the file was last modified. fn last_modified_timestamp(&self) -> Option; /// The rfc2822 encoded last modified date. This is the format you use for diff --git a/utils/src/file/dynamic.rs b/utils/src/file/dynamic.rs index 527b71a..631e22c 100644 --- a/utils/src/file/dynamic.rs +++ b/utils/src/file/dynamic.rs @@ -48,6 +48,11 @@ impl EmbedableFile for DynamicFile { None } + #[cfg(feature = "compression-zstd")] + fn data_zstd(&self) -> Option { + None + } + fn last_modified(&self) -> Option { self.last_modified_timestamp() .map(|v| chrono::Utc.timestamp_opt(v, 0).unwrap().to_rfc2822()) diff --git a/utils/src/file/embed.rs b/utils/src/file/embed.rs index 9878671..eff7d43 100644 --- a/utils/src/file/embed.rs +++ b/utils/src/file/embed.rs @@ -15,6 +15,8 @@ pub struct EmbeddedFile { data: &'static [u8], data_gzip: Option<&'static [u8]>, data_br: Option<&'static [u8]>, + #[cfg(feature = "compression-zstd")] + data_zstd: Option<&'static [u8]>, hash: &'static str, etag: &'static str, last_modified: Option<&'static str>, @@ -42,6 +44,11 @@ impl EmbedableFile for EmbeddedFile { self.data_br } + #[cfg(feature = "compression-zstd")] + fn data_zstd(&self) -> Option { + self.data_zstd + } + fn last_modified(&self) -> Option { self.last_modified } @@ -68,6 +75,41 @@ impl EmbeddedFile { #[allow(clippy::too_many_arguments)] /// This is used internally in derived code to create embedded file objects. /// You don't want to manually use this function! + #[cfg(feature = "compression-zstd")] + pub fn __internal_make( + // Make sure that the order of these parameters is correct in respect to + // the file contents! And if you are changing or reordering any of + // these, make sure to update the corresponding call in `impl` + name: &'static str, + data: &'static [u8], + data_gzip: Option<&'static [u8]>, + data_br: Option<&'static [u8]>, + data_zstd: Option<&'static [u8]>, + hash: &'static str, + etag: &'static str, + last_modified: Option<&'static str>, + last_modified_timestamp: Option, + mime_type: Option<&'static str>, + ) -> EmbeddedFile { + EmbeddedFile { + name, + data, + data_gzip, + data_br, + data_zstd, + hash, + etag, + last_modified, + last_modified_timestamp, + mime_type, + } + } + + #[doc(hidden)] + #[allow(clippy::too_many_arguments)] + /// This is used internally in derived code to create embedded file objects. + /// You don't want to manually use this function! + #[cfg(not(feature = "compression-zstd"))] pub fn __internal_make( // Make sure that the order of these parameters is correct in respect to // the file contents! And if you are changing or reordering any of @@ -76,6 +118,7 @@ impl EmbeddedFile { data: &'static [u8], data_gzip: Option<&'static [u8]>, data_br: Option<&'static [u8]>, + _data_zstd: Option<&'static [u8]>, // Ignored when feature disabled hash: &'static str, etag: &'static str, last_modified: Option<&'static str>, diff --git a/utils/src/lib.rs b/utils/src/lib.rs index fb9eeb3..9fdf4ef 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -27,7 +27,7 @@ pub fn get_files<'t>( .filter(|e| e.file_type().is_file()) .filter_map(move |e| { let rel_path = path_to_str(e.path().strip_prefix(folder_path).unwrap()); - let rel_path = format!("{}{}", prefix, rel_path); + let rel_path = format!("{prefix}{rel_path}"); let full_canonical_path = path_to_str(std::fs::canonicalize(e.path()).expect("Could not get canonical path"));