rust-embed v8.11.0

#rust#proc-macro#http

git clone https://git.pyrossh.dev/rust-embed

rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.


8e80155Tommy Yu 2026-05-20T16:27:26+12:00
Make Way to avoid compile failure on undefined env
Files changed (2) hide show
  1. impl/src/lib.rs +87 -57
  2. tests/interpolated_path.rs +20 -0
impl/src/lib.rs CHANGED
@@ -17,7 +17,7 @@ use std::{
17
17
  use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue};
18
18
 
19
19
  fn embedded(
20
- ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String],
20
+ ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: Option<String>, prefix: Option<&str>, includes: &[String], excludes: &[String],
21
21
  metadata_only: bool, crate_path: &syn::Path,
22
22
  ) -> syn::Result<TokenStream2> {
23
23
  extern crate rust_embed_utils;
@@ -28,7 +28,11 @@ fn embedded(
28
28
  let includes: Vec<&str> = includes.iter().map(AsRef::as_ref).collect();
29
29
  let excludes: Vec<&str> = excludes.iter().map(AsRef::as_ref).collect();
30
30
  let matcher = PathMatcher::new(&includes, &excludes);
31
+ let entries = absolute_folder_path.clone()
32
+ .map(|v| rust_embed_utils::get_files(v, matcher))
33
+ .into_iter()
34
+ .flatten();
31
- for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(absolute_folder_path.clone(), matcher) {
35
+ for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in entries {
32
36
  match_values.insert(
33
37
  rel_path.clone(),
34
38
  embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only, crate_path)?,
@@ -111,7 +115,7 @@ fn embedded(
111
115
  }
112
116
 
113
117
  fn dynamic(
114
- ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool, crate_path: &syn::Path,
118
+ ident: &syn::Ident, folder_path: Option<String>, prefix: Option<&str>, includes: &[String], excludes: &[String], metadata_only: bool, crate_path: &syn::Path,
115
119
  ) -> TokenStream2 {
116
120
  let (handle_prefix, map_iter) = if let ::std::option::Option::Some(prefix) = prefix {
117
121
  (
@@ -136,27 +140,17 @@ fn dynamic(
136
140
  .map(|mut file| { file.data = ::std::default::Default::default(); file })
137
141
  });
138
142
 
143
+ let methods = if let Some(ref folder_path) = folder_path {
139
- let non_canonical_folder_path = Path::new(&folder_path);
144
+ let non_canonical_folder_path = Path::new(&folder_path);
140
- let canonical_folder_path = non_canonical_folder_path
145
+ let canonical_folder_path = non_canonical_folder_path
141
- .canonicalize()
146
+ .canonicalize()
142
- .or_else(|err| match err {
147
+ .or_else(|err| match err {
143
- err if err.kind() == ErrorKind::NotFound => Ok(non_canonical_folder_path.to_owned()),
148
+ err if err.kind() == ErrorKind::NotFound => Ok(non_canonical_folder_path.to_owned()),
144
- err => Err(err),
149
+ err => Err(err),
145
- })
150
+ })
146
- .expect("folder path must resolve to an absolute path");
151
+ .expect("folder path must resolve to an absolute path");
147
- let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode");
152
+ let canonical_folder_path = canonical_folder_path.to_str().expect("absolute folder path must be valid unicode");
148
-
149
- quote! {
153
+ quote! {
150
- #[cfg(debug_assertions)]
151
- impl #ident {
152
-
153
-
154
- fn matcher() -> #crate_path::utils::PathMatcher {
155
- #declare_includes
156
- #declare_excludes
157
- static PATH_MATCHER: ::std::sync::OnceLock<#crate_path::utils::PathMatcher> = ::std::sync::OnceLock::new();
158
- PATH_MATCHER.get_or_init(|| #crate_path::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone()
159
- }
160
154
  /// Get an embedded file and its metadata.
161
155
  pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> {
162
156
  #handle_prefix
@@ -191,10 +185,38 @@ fn dynamic(
191
185
  pub fn iter() -> impl ::std::iter::Iterator<Item = ::std::borrow::Cow<'static, str>> + 'static {
192
186
  use ::std::path::Path;
193
187
 
194
-
195
188
  #crate_path::utils::get_files(::std::string::String::from(#folder_path), Self::matcher())
196
189
  .map(|e| #map_iter)
197
190
  }
191
+ }
192
+ } else {
193
+ quote! {
194
+ /// Get an embedded file and its metadata.
195
+ pub fn get(file_path: &str) -> ::std::option::Option<#crate_path::EmbeddedFile> {
196
+ // No folder_path defined so nothing can be returned.
197
+ None
198
+ }
199
+
200
+ /// Iterates over the file paths in the folder.
201
+ pub fn iter() -> impl ::std::iter::Iterator<Item = ::std::borrow::Cow<'static, str>> + 'static {
202
+ None.into_iter()
203
+ }
204
+ }
205
+ };
206
+
207
+ quote! {
208
+ #[cfg(debug_assertions)]
209
+ impl #ident {
210
+
211
+
212
+ fn matcher() -> #crate_path::utils::PathMatcher {
213
+ #declare_includes
214
+ #declare_excludes
215
+ static PATH_MATCHER: ::std::sync::OnceLock<#crate_path::utils::PathMatcher> = ::std::sync::OnceLock::new();
216
+ PATH_MATCHER.get_or_init(|| #crate_path::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone()
217
+ }
218
+
219
+ #methods
198
220
  }
199
221
 
200
222
  #[cfg(debug_assertions)]
@@ -211,7 +233,7 @@ fn dynamic(
211
233
  }
212
234
 
213
235
  fn generate_assets(
214
- ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: String, prefix: Option<String>, includes: Vec<String>, excludes: Vec<String>,
236
+ ident: &syn::Ident, relative_folder_path: Option<&str>, absolute_folder_path: Option<String>, prefix: Option<String>, includes: Vec<String>, excludes: Vec<String>,
215
237
  metadata_only: bool, crate_path: &syn::Path,
216
238
  ) -> syn::Result<TokenStream2> {
217
239
  let embedded_impl = embedded(
@@ -346,7 +368,7 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
346
368
  "#[derive(RustEmbed)] must contain one attribute like this #[folder = \"examples/public/\"]",
347
369
  ));
348
370
  }
349
- let folder_path = folder_paths.remove(0);
371
+ let folder_path = Some(folder_paths.remove(0));
350
372
 
351
373
  let prefix = find_attribute_values(ast, "prefix").into_iter().next();
352
374
  let includes = find_attribute_values(ast, "include");
@@ -362,41 +384,49 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> syn::Result<TokenStream2> {
362
384
  ));
363
385
  }
364
386
 
387
+ // .map_err(|v| syn::Error::new_spanned(ast, v.to_string()))?
365
388
  #[cfg(feature = "interpolate-folder-path")]
366
- let folder_path = shellexpand::full(&folder_path)
389
+ let folder_path = match shellexpand::full(&folder_path.unwrap()) {
390
+ Ok(v) => Ok(Some(v.to_string())),
391
+ Err(v) if v.cause == std::env::VarError::NotPresent && allow_missing => Ok(None),
367
- .map_err(|v| syn::Error::new_spanned(ast, v.to_string()))?
392
+ Err(v) => Err(syn::Error::new_spanned(ast, v.to_string())),
368
- .to_string();
393
+ }?;
369
394
 
370
395
  // Base relative paths on the Cargo.toml location
396
+ let (relative_path, absolute_folder_path) = if let Some(folder_path) = folder_path {
371
- let (relative_path, absolute_folder_path) = if Path::new(&folder_path).is_relative() {
397
+ let (relative_path, absolute_folder_path) = if Path::new(&folder_path).is_relative() {
372
- let absolute_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
398
+ let absolute_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap())
373
- .join(&folder_path)
399
+ .join(&folder_path)
374
- .to_str()
400
+ .to_str()
375
- .unwrap()
401
+ .unwrap()
376
- .to_owned();
402
+ .to_owned();
377
- (Some(folder_path.clone()), absolute_path)
403
+ (Some(folder_path.clone()), absolute_path)
378
- } else {
404
+ } else {
379
- if cfg!(feature = "compression") {
405
+ if cfg!(feature = "compression") {
380
- return Err(syn::Error::new_spanned(ast, "`folder` must be a relative path under `compression` feature."));
406
+ return Err(syn::Error::new_spanned(ast, "`folder` must be a relative path under `compression` feature."));
381
- }
407
+ }
382
- (None, folder_path)
408
+ (None, folder_path)
383
- };
409
+ };
384
-
385
- if !Path::new(&absolute_folder_path).exists() && !allow_missing {
386
- let mut message = format!(
387
- "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
388
- absolute_folder_path,
389
- std::env::current_dir().unwrap().to_str().unwrap()
390
- );
391
410
 
411
+ if !Path::new(&absolute_folder_path).exists() && !allow_missing {
412
+ let mut message = format!(
413
+ "#[derive(RustEmbed)] folder '{}' does not exist. cwd: '{}'",
414
+ absolute_folder_path,
415
+ std::env::current_dir().unwrap().to_str().unwrap()
416
+ );
417
+
392
- // Add a message about the interpolate-folder-path feature if the path may
418
+ // Add a message about the interpolate-folder-path feature if the path may
393
- // include a variable
419
+ // include a variable
394
- if absolute_folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
420
+ if absolute_folder_path.contains('$') && cfg!(not(feature = "interpolate-folder-path")) {
395
- message += "\nA variable has been detected. RustEmbed can expand variables \
421
+ message += "\nA variable has been detected. RustEmbed can expand variables \
396
- when the `interpolate-folder-path` feature is enabled.";
422
+ when the `interpolate-folder-path` feature is enabled.";
397
- }
423
+ }
398
424
 
399
- return Err(syn::Error::new_spanned(ast, message));
425
+ return Err(syn::Error::new_spanned(ast, message));
426
+ };
427
+ (relative_path, Some(absolute_folder_path))
428
+ } else {
429
+ (None, None)
400
430
  };
401
431
 
402
432
  generate_assets(
tests/interpolated_path.rs CHANGED
@@ -5,6 +5,26 @@ use rust_embed::Embed;
5
5
  #[folder = "$CARGO_MANIFEST_DIR/examples/public/"]
6
6
  struct Asset;
7
7
 
8
+ static CHECK_UNDEFINED: Option<&str> = option_env!("__UNDEFINED__");
9
+
10
+ #[derive(Embed)]
11
+ #[folder = "$__UNDEFINED__/examples/public/"]
12
+ #[allow_missing = true]
13
+ struct UndefinedAsset;
14
+
15
+ #[test]
16
+ fn undefined_usable() {
17
+ assert!(CHECK_UNDEFINED.is_none(), "__UNDEFINED__ should not be defined at compile time");
18
+ assert!(UndefinedAsset::get("index.html").is_none(), "index.html should not exist");
19
+
20
+ let mut num_files = 0;
21
+ for file in UndefinedAsset::iter() {
22
+ assert!(UndefinedAsset::get(file.as_ref()).is_some());
23
+ num_files += 1;
24
+ }
25
+ assert_eq!(num_files, 0);
26
+ }
27
+
8
28
  #[test]
9
29
  fn get_works() {
10
30
  assert!(Asset::get("index.html").is_some(), "index.html should exist");