~repos /rust-embed

#rust#proc-macro#http

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

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


2cecab75 AzureMarker

4 years ago
Add support for embedded file metadata
Cargo.toml CHANGED
@@ -32,6 +32,7 @@ path = "tests/interpolated_path.rs"
32
32
  required-features = ["interpolate-folder-path"]
33
33
 
34
34
  [dependencies]
35
+ sha2 = "0.9"
35
36
  walkdir = "2.3.1"
36
37
  rust-embed-impl = { version = "5.9.0", path = "impl"}
37
38
  rust-embed-utils = { version = "5.1.0", path = "utils"}
examples/actix.rs CHANGED
@@ -12,7 +12,7 @@ struct Asset;
12
12
  fn handle_embedded_file(path: &str) -> HttpResponse {
13
13
  match Asset::get(path) {
14
14
  Some(content) => {
15
- let body: Body = match content {
15
+ let body: Body = match content.data {
16
16
  Cow::Borrowed(bytes) => bytes.into(),
17
17
  Cow::Owned(bytes) => bytes.into(),
18
18
  };
examples/basic.rs CHANGED
@@ -6,5 +6,5 @@ struct Asset;
6
6
 
7
7
  fn main() {
8
8
  let index_html = Asset::get("index.html").unwrap();
9
- println!("{:?}", std::str::from_utf8(index_html.as_ref()));
9
+ println!("{:?}", std::str::from_utf8(index_html.data.as_ref()));
10
10
  }
examples/rocket.rs CHANGED
@@ -34,7 +34,7 @@ fn dist<'r>(file: PathBuf) -> response::Result<'r> {
34
34
  .and_then(OsStr::to_str)
35
35
  .ok_or_else(|| Status::new(400, "Could not get file extension"))?;
36
36
  let content_type = ContentType::from_extension(ext).ok_or_else(|| Status::new(400, "Could not get file content type"))?;
37
- response::Response::build().header(content_type).sized_body(Cursor::new(d)).ok()
37
+ response::Response::build().header(content_type).sized_body(Cursor::new(d.data)).ok()
38
38
  },
39
39
  )
40
40
  }
examples/warp.rs CHANGED
@@ -26,7 +26,7 @@ fn serve_impl(path: &str) -> Result<impl Reply, Rejection> {
26
26
  let asset = Asset::get(path).ok_or_else(warp::reject::not_found)?;
27
27
  let mime = mime_guess::from_path(path).first_or_octet_stream();
28
28
 
29
- let mut res = Response::new(asset.into());
29
+ let mut res = Response::new(asset.data.into());
30
30
  res.headers_mut().insert("content-type", HeaderValue::from_str(mime.as_ref()).unwrap());
31
31
  Ok(res)
32
32
  }
impl/src/lib.rs CHANGED
@@ -46,7 +46,7 @@ fn embedded(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> To
46
46
  quote! {
47
47
  #not_debug_attr
48
48
  impl #ident {
49
- pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
49
+ pub fn get(file_path: &str) -> Option<rust_embed::EmbeddedFile> {
50
50
  #handle_prefix
51
51
  match file_path.replace("\\", "/").as_str() {
52
52
  #(#match_values)*
@@ -66,7 +66,7 @@ fn embedded(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> To
66
66
 
67
67
  #not_debug_attr
68
68
  impl rust_embed::RustEmbed for #ident {
69
- fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
69
+ fn get(file_path: &str) -> Option<rust_embed::EmbeddedFile> {
70
70
  #ident::get(file_path)
71
71
  }
72
72
  fn iter() -> rust_embed::Filenames {
@@ -89,19 +89,11 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> Tok
89
89
  quote! {
90
90
  #[cfg(debug_assertions)]
91
91
  impl #ident {
92
- pub fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
92
+ pub fn get(file_path: &str) -> Option<rust_embed::EmbeddedFile> {
93
- use std::fs;
94
- use std::path::Path;
95
-
96
93
  #handle_prefix
97
94
 
98
- let file_path = Path::new(#folder_path).join(file_path.replace("\\", "/"));
95
+ let file_path = std::path::Path::new(#folder_path).join(file_path.replace("\\", "/"));
99
- match fs::read(file_path) {
96
+ rust_embed::utils::read_file_from_fs(&file_path).ok()
100
- Ok(contents) => Some(std::borrow::Cow::from(contents)),
101
- Err(_e) => {
102
- return None
103
- }
104
- }
105
97
  }
106
98
 
107
99
  pub fn iter() -> impl Iterator<Item = std::borrow::Cow<'static, str>> {
@@ -113,7 +105,7 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>) -> Tok
113
105
 
114
106
  #[cfg(debug_assertions)]
115
107
  impl rust_embed::RustEmbed for #ident {
116
- fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>> {
108
+ fn get(file_path: &str) -> Option<rust_embed::EmbeddedFile> {
117
109
  #ident::get(file_path)
118
110
  }
119
111
  fn iter() -> rust_embed::Filenames {
@@ -138,25 +130,34 @@ fn generate_assets(ident: &syn::Ident, folder_path: String, prefix: Option<Strin
138
130
  }
139
131
  }
140
132
 
141
- #[cfg(not(feature = "compression"))]
142
133
  fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
134
+ let file = rust_embed_utils::read_file_from_fs(Path::new(full_canonical_path)).expect("File should be readable");
135
+ let hash = file.metadata.sha256_hash();
136
+ let last_modified = match file.metadata.last_modified() {
137
+ Some(last_modified) => quote! { Some(#last_modified) },
138
+ None => quote! { None },
139
+ };
140
+
141
+ let embedding_code = if cfg!(feature = "compression") {
143
- quote! {
142
+ quote! {
144
- #rel_path => {
145
143
  let bytes = &include_bytes!(#full_canonical_path)[..];
146
- Some(std::borrow::Cow::from(bytes))
147
- },
148
- }
144
+ }
145
+ } else {
146
+ quote! {
147
+ rust_embed::flate!(static FILE: [u8] from #full_canonical_path);
148
+ let bytes = &FILE[..];
149
- }
149
+ }
150
+ };
150
151
 
151
- #[cfg(feature = "compression")]
152
- fn embed_file(rel_path: &str, full_canonical_path: &str) -> TokenStream2 {
153
152
  quote! {
154
- #rel_path => {
153
+ #rel_path => {
155
- rust_embed::flate!(static FILE: [u8] from #full_canonical_path);
154
+ #embedding_code
156
155
 
157
- let bytes = &FILE[..];
156
+ Some(rust_embed::EmbeddedFile {
158
- Some(std::borrow::Cow::from(bytes))
157
+ data: std::borrow::Cow::from(bytes),
158
+ metadata: rust_embed::Metadata::__rust_embed_new([#(#hash),*], #last_modified)
159
- },
159
+ })
160
+ }
160
161
  }
161
162
  }
162
163
 
@@ -213,7 +214,7 @@ fn impl_rust_embed(ast: &syn::DeriveInput) -> TokenStream2 {
213
214
  when the `interpolate-folder-path` feature is enabled.";
214
215
  }
215
216
 
216
- panic!(message);
217
+ panic!("{}", message);
217
218
  };
218
219
 
219
220
  generate_assets(&ast.ident, folder_path, prefix)
src/lib.rs CHANGED
@@ -3,13 +3,17 @@
3
3
  #[cfg_attr(feature = "compression", doc(hidden))]
4
4
  pub use include_flate::flate;
5
5
 
6
+ #[doc(hidden)]
7
+ pub use sha2;
8
+
6
9
  #[allow(unused_imports)]
7
10
  #[macro_use]
8
11
  extern crate rust_embed_impl;
9
12
  pub use rust_embed_impl::*;
10
13
 
14
+ pub use rust_embed_utils::{EmbeddedFile, Metadata};
15
+
11
16
  #[doc(hidden)]
12
- #[cfg(all(debug_assertions, not(feature = "debug-embed")))]
13
17
  pub extern crate rust_embed_utils as utils;
14
18
 
15
19
  /// A directory of binary assets.
@@ -37,7 +41,7 @@ pub trait RustEmbed {
37
41
  ///
38
42
  /// Otherwise, the bytes are read from the file system on each call and a
39
43
  /// `Cow::Owned(Vec<u8>)` is returned.
40
- fn get(file_path: &str) -> Option<std::borrow::Cow<'static, [u8]>>;
44
+ fn get(file_path: &str) -> Option<EmbeddedFile>;
41
45
 
42
46
  /// Iterates the files in this assets folder.
43
47
  ///
utils/Cargo.toml CHANGED
@@ -13,6 +13,7 @@ edition = "2018"
13
13
 
14
14
  [dependencies]
15
15
  walkdir = "2.3.1"
16
+ sha2 = "0.9"
16
17
 
17
18
  [features]
18
19
  debug-embed = []
utils/src/lib.rs CHANGED
@@ -1,4 +1,11 @@
1
1
  #![forbid(unsafe_code)]
2
+
3
+ use sha2::Digest;
4
+ use std::borrow::Cow;
5
+ use std::path::Path;
6
+ use std::time::SystemTime;
7
+ use std::{fs, io};
8
+
2
9
  #[cfg_attr(all(debug_assertions, not(feature = "debug-embed")), allow(unused))]
3
10
  pub struct FileEntry {
4
11
  pub rel_path: String,
@@ -26,6 +33,55 @@ pub fn get_files(folder_path: String) -> impl Iterator<Item = FileEntry> {
26
33
  })
27
34
  }
28
35
 
36
+ pub struct EmbeddedFile {
37
+ pub data: Cow<'static, [u8]>,
38
+ pub metadata: Metadata,
39
+ }
40
+
41
+ pub struct Metadata {
42
+ hash: [u8; 32],
43
+ last_modified: Option<u64>,
44
+ }
45
+
46
+ impl Metadata {
47
+ #[doc(hidden)]
48
+ pub fn __rust_embed_new(hash: [u8; 32], last_modified: Option<u64>) -> Self {
49
+ Self { hash, last_modified }
50
+ }
51
+
52
+ /// The SHA256 hash of the file
53
+ pub fn sha256_hash(&self) -> [u8; 32] {
54
+ self.hash
55
+ }
56
+
57
+ /// The last modified date in seconds since the UNIX epoch. If the underlying
58
+ /// platform/file-system does not support this, None is returned.
59
+ pub fn last_modified(&self) -> Option<u64> {
60
+ self.last_modified
61
+ }
62
+ }
63
+
64
+ pub fn read_file_from_fs(file_path: &Path) -> io::Result<EmbeddedFile> {
65
+ let data = fs::read(file_path)?;
66
+ let data = Cow::from(data);
67
+
68
+ let mut hasher = sha2::Sha256::new();
69
+ hasher.update(&data);
70
+ let hash: [u8; 32] = hasher.finalize().into();
71
+
72
+ let last_modified = fs::metadata(file_path)?.modified().ok().map(|last_modified| {
73
+ last_modified
74
+ .duration_since(SystemTime::UNIX_EPOCH)
75
+ .expect("Time before the UNIX epoch is unsupported")
76
+ .as_secs()
77
+ });
78
+
79
+ Ok(EmbeddedFile {
80
+ data,
81
+ metadata: Metadata { hash, last_modified },
82
+ })
83
+ }
84
+
29
85
  fn path_to_str<P: AsRef<std::path::Path>>(p: P) -> String {
30
86
  p.as_ref().to_str().expect("Path does not have a string representation").to_owned()
31
87
  }