Skip to content

Commit 0fc2a71

Browse files
structwafelpaolobarbolini
authored andcommitted
feat: add ignore_files option
1 parent 05d398c commit 0fc2a71

File tree

2 files changed

+130
-1
lines changed

2 files changed

+130
-1
lines changed

static-serve-macro/src/lib.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ impl ToTokens for EmbedAsset {
156156
struct EmbedAssets {
157157
assets_dir: AssetsDir,
158158
validated_ignore_dirs: IgnoreDirs,
159+
validated_ignore_files: IgnoreFiles,
159160
should_compress: ShouldCompress,
160161
should_strip_html_ext: ShouldStripHtmlExt,
161162
cache_busted_paths: CacheBustedPaths,
@@ -168,6 +169,7 @@ impl Parse for EmbedAssets {
168169
// Default to no compression
169170
let mut maybe_should_compress = None;
170171
let mut maybe_ignore_dirs = None;
172+
let mut maybe_ignore_files = None;
171173
let mut maybe_should_strip_html_ext = None;
172174
let mut maybe_cache_busted_paths = None;
173175

@@ -185,6 +187,10 @@ impl Parse for EmbedAssets {
185187
let value = input.parse()?;
186188
maybe_ignore_dirs = Some(value);
187189
}
190+
"ignore_files" => {
191+
let value = input.parse()?;
192+
maybe_ignore_files = Some(value);
193+
}
188194
"strip_html_ext" => {
189195
let value = input.parse()?;
190196
maybe_should_strip_html_ext = Some(value);
@@ -196,7 +202,7 @@ impl Parse for EmbedAssets {
196202
_ => {
197203
return Err(syn::Error::new(
198204
key.span(),
199-
"Unknown key in embed_assets! macro. Expected `compress`, `ignore_dirs`, `strip_html_ext`, or `cache_busted_paths`",
205+
"Unknown key in embed_assets! macro. Expected `compress`, `ignore_dirs`, `ignore_files`, `strip_html_ext`, or `cache_busted_paths`",
200206
));
201207
}
202208
}
@@ -219,6 +225,9 @@ impl Parse for EmbedAssets {
219225
let ignore_dirs_with_span = maybe_ignore_dirs.unwrap_or(IgnoreDirsWithSpan(vec![]));
220226
let validated_ignore_dirs = validate_ignore_dirs(ignore_dirs_with_span, &assets_dir.0)?;
221227

228+
let ignore_files_with_span = maybe_ignore_files.unwrap_or(IgnoreFilesWithSpan(vec![]));
229+
let validated_ignore_files = validate_ignore_files(ignore_files_with_span, &assets_dir.0)?;
230+
222231
let maybe_cache_busted_paths =
223232
maybe_cache_busted_paths.unwrap_or(CacheBustedPathsWithSpan(vec![]));
224233
let cache_busted_paths =
@@ -227,6 +236,7 @@ impl Parse for EmbedAssets {
227236
Ok(Self {
228237
assets_dir,
229238
validated_ignore_dirs,
239+
validated_ignore_files,
230240
should_compress,
231241
should_strip_html_ext,
232242
cache_busted_paths,
@@ -238,13 +248,15 @@ impl ToTokens for EmbedAssets {
238248
fn to_tokens(&self, tokens: &mut TokenStream) {
239249
let AssetsDir(assets_dir) = &self.assets_dir;
240250
let ignore_dirs = &self.validated_ignore_dirs;
251+
let ignore_files = &self.validated_ignore_files;
241252
let ShouldCompress(should_compress) = &self.should_compress;
242253
let ShouldStripHtmlExt(should_strip_html_ext) = &self.should_strip_html_ext;
243254
let cache_busted_paths = &self.cache_busted_paths;
244255

245256
let result = generate_static_routes(
246257
assets_dir,
247258
ignore_dirs,
259+
ignore_files,
248260
should_compress,
249261
should_strip_html_ext,
250262
cache_busted_paths,
@@ -306,6 +318,10 @@ struct IgnoreDirs(Vec<PathBuf>);
306318

307319
struct IgnoreDirsWithSpan(Vec<(PathBuf, Span)>);
308320

321+
struct IgnoreFiles(Vec<PathBuf>);
322+
323+
struct IgnoreFilesWithSpan(Vec<(PathBuf, Span)>);
324+
309325
impl Parse for IgnoreDirsWithSpan {
310326
fn parse(input: ParseStream) -> syn::Result<Self> {
311327
let dirs = parse_dirs(input)?;
@@ -314,6 +330,14 @@ impl Parse for IgnoreDirsWithSpan {
314330
}
315331
}
316332

333+
impl Parse for IgnoreFilesWithSpan {
334+
fn parse(input: ParseStream) -> syn::Result<Self> {
335+
let files = parse_dirs(input)?; // reuse parse_dirs since it's just parsing paths
336+
337+
Ok(IgnoreFilesWithSpan(files))
338+
}
339+
}
340+
317341
fn validate_ignore_dirs(
318342
ignore_dirs: IgnoreDirsWithSpan,
319343
assets_dir: &LitStr,
@@ -350,6 +374,42 @@ fn validate_ignore_dirs(
350374
Ok(IgnoreDirs(valid_ignore_dirs))
351375
}
352376

377+
fn validate_ignore_files(
378+
ignore_files: IgnoreFilesWithSpan,
379+
assets_dir: &LitStr,
380+
) -> syn::Result<IgnoreFiles> {
381+
let mut valid_ignore_files = Vec::new();
382+
for (file, span) in ignore_files.0 {
383+
let full_path = PathBuf::from(assets_dir.value()).join(&file);
384+
match fs::metadata(&full_path) {
385+
Ok(meta) if meta.is_dir() => {
386+
return Err(syn::Error::new(
387+
span,
388+
"The specified ignored file is a directory. Use ignore_dirs instead.",
389+
));
390+
}
391+
Ok(_) => valid_ignore_files.push(full_path),
392+
Err(e) if matches!(e.kind(), std::io::ErrorKind::NotFound) => {
393+
return Err(syn::Error::new(
394+
span,
395+
"The specified ignored file does not exist",
396+
))
397+
}
398+
Err(e) => {
399+
return Err(syn::Error::new(
400+
span,
401+
format!(
402+
"Error reading ignored file {}: {}",
403+
file.to_string_lossy(),
404+
DisplayFullError(&e)
405+
),
406+
))
407+
}
408+
}
409+
}
410+
Ok(IgnoreFiles(valid_ignore_files))
411+
}
412+
353413
struct ShouldCompress(LitBool);
354414

355415
impl Parse for ShouldCompress {
@@ -453,6 +513,7 @@ fn parse_dirs(input: ParseStream) -> syn::Result<Vec<(PathBuf, Span)>> {
453513
fn generate_static_routes(
454514
assets_dir: &LitStr,
455515
ignore_dirs: &IgnoreDirs,
516+
ignore_files: &IgnoreFiles,
456517
should_compress: &LitBool,
457518
should_strip_html_ext: &LitBool,
458519
cache_busted_paths: &CacheBustedPaths,
@@ -468,6 +529,11 @@ fn generate_static_routes(
468529
.iter()
469530
.map(|d| d.canonicalize().map_err(Error::CannotCanonicalizeIgnoreDir))
470531
.collect::<Result<Vec<_>, _>>()?;
532+
let canon_ignore_files = ignore_files
533+
.0
534+
.iter()
535+
.map(|f| f.canonicalize().map_err(Error::CannotCanonicalizeFile))
536+
.collect::<Result<Vec<_>, _>>()?;
471537
let canon_cache_busted_dirs = cache_busted_paths
472538
.dirs
473539
.iter()
@@ -498,6 +564,11 @@ fn generate_static_routes(
498564
continue;
499565
}
500566

567+
// Skip `entry`s which are explicitly ignored files
568+
if canon_ignore_files.contains(&entry) {
569+
continue;
570+
}
571+
501572
let mut is_entry_cache_busted = false;
502573
if canon_cache_busted_dirs
503574
.iter()

static-serve/tests/tests.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,3 +1079,61 @@ async fn handles_dir_with_cache_control_on_filename_and_dir() {
10791079
let expected_body_bytes = include_bytes!("../../../static-serve/test_assets/small/app.js");
10801080
assert_eq!(*collected_body_bytes, *expected_body_bytes);
10811081
}
1082+
1083+
#[tokio::test]
1084+
async fn router_created_ignore_files() {
1085+
embed_assets!(
1086+
"../static-serve/test_assets/small",
1087+
ignore_files = ["app.js"]
1088+
);
1089+
let router: Router<()> = static_router();
1090+
1091+
// app.js should be ignored, but styles.css should be available
1092+
assert!(router.has_routes());
1093+
1094+
// Request for app.js should succeed
1095+
let request = create_request("/styles.css", &Compression::None);
1096+
let response = get_response(router.clone(), request).await;
1097+
let (parts, _) = response.into_parts();
1098+
assert!(parts.status.is_success());
1099+
1100+
// Request for index.html should return 404 since it's ignored
1101+
let request = create_request("/app.js", &Compression::None);
1102+
let response = get_response(router, request).await;
1103+
let (parts, _) = response.into_parts();
1104+
assert_eq!(parts.status, StatusCode::NOT_FOUND);
1105+
}
1106+
1107+
#[tokio::test]
1108+
async fn router_created_ignore_multiple_files() {
1109+
embed_assets!(
1110+
"../static-serve/test_assets/big",
1111+
ignore_files = ["app.js", "styles.css"]
1112+
);
1113+
let router: Router<()> = static_router();
1114+
1115+
// All files in /big should be ignored, but files in /big/immutable should still be available
1116+
assert!(router.has_routes());
1117+
1118+
// Request for ignored files should return 404
1119+
let request = create_request("/app.js", &Compression::None);
1120+
let response = get_response(router.clone(), request).await;
1121+
let (parts, _) = response.into_parts();
1122+
assert_eq!(parts.status, StatusCode::NOT_FOUND);
1123+
1124+
let request = create_request("/styles.css", &Compression::None);
1125+
let response = get_response(router.clone(), request).await;
1126+
let (parts, _) = response.into_parts();
1127+
assert_eq!(parts.status, StatusCode::NOT_FOUND);
1128+
1129+
// Request for files in subdirectory should succeed
1130+
let request = create_request("/immutable/app.js", &Compression::None);
1131+
let response = get_response(router.clone(), request).await;
1132+
let (parts, _) = response.into_parts();
1133+
assert!(parts.status.is_success());
1134+
1135+
let request = create_request("/immutable/styles.css", &Compression::None);
1136+
let response = get_response(router, request).await;
1137+
let (parts, _) = response.into_parts();
1138+
assert!(parts.status.is_success());
1139+
}

0 commit comments

Comments
 (0)