From 0805981e753ecd02bf8c7bdd0ac24d9ca7dc7b52 Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Fri, 14 Aug 2026 10:17:06 +0700 Subject: [PATCH] feat: include paths and nested messages in errors --- static-serve-macro/src/error.rs | 74 +++++++++++++++++++-------- static-serve-macro/src/lib.rs | 88 +++++++++++++++++++++++---------- 2 files changed, 114 insertions(+), 48 deletions(-) diff --git a/static-serve-macro/src/error.rs b/static-serve-macro/src/error.rs index fe766c7..55379d2 100644 --- a/static-serve-macro/src/error.rs +++ b/static-serve-macro/src/error.rs @@ -14,32 +14,64 @@ pub(crate) enum Error { UnknownFileExtension(Option), #[error("File extension for file {} is not valid unicode", 0.to_string())] InvalidFileExtension(OsString), - #[error("Cannot canonicalize assets directory")] - CannotCanonicalizeDirectory(#[source] io::Error), - #[error("Cannot canonicalize asset file")] - CannotCanonicalizeFile(#[source] io::Error), + #[error("Cannot canonicalize assets directory {dir}: {error}")] + CannotCanonicalizeDirectory { + dir: String, + #[source] + error: io::Error, + }, + #[error("Cannot canonicalize asset file {entry}: {error}")] + CannotCanonicalizeFile { + entry: PathBuf, + #[source] + error: io::Error, + }, #[error("Cannot make file path {0} relative to directory")] CannotMakeFileRelative(PathBuf), - #[error("File path is not utf-8")] - FilePathIsNotUtf8, - #[error("Invalid unicode in directory name")] - InvalidUnicodeInDirectoryName, - #[error("Cannot canonicalize ignore path")] - CannotCanonicalizeIgnorePath(#[source] io::Error), - #[error("Error while compressing with gzip")] - Gzip(#[from] GzipType), - #[error("Error while compressing with zstd")] - Zstd(#[from] ZstdType), - #[error("Error while reading entry contents")] - CannotReadEntryContents(#[source] io::Error), + #[error("File path {0} is not utf-8")] + FilePathIsNotUtf8(PathBuf), + #[error("Invalid unicode in directory name {0}")] + InvalidUnicodeInDirectoryName(PathBuf), + #[error("Cannot canonicalize ignore path {path}: {error}")] + CannotCanonicalizeIgnorePath { + path: PathBuf, + #[source] + error: io::Error, + }, + #[error("Error while compressing entry {entry} with gzip: {error}")] + Gzip { + entry: PathBuf, + #[source] + error: GzipType, + }, + #[error("Error while compressing entry {entry} with zstd: {error}")] + Zstd { + entry: PathBuf, + #[source] + error: ZstdType, + }, + #[error("Error while reading entry {entry} contents: {error}")] + CannotReadEntryContents { + entry: PathBuf, + #[source] + error: io::Error, + }, #[error("Error while parsing glob pattern")] Pattern(#[source] PatternError), - #[error("Error reading path for glob")] + #[error("Error reading path for glob: {0}")] Glob(#[source] GlobError), - #[error("Cannot get entry metadata")] - CannotGetMetadata(#[source] io::Error), - #[error("Cannot canonicalize directory for cache-busting")] - CannotCanonicalizeCacheBustedDir(#[source] io::Error), + #[error("Cannot get entry {entry} metadata: {error}")] + CannotGetMetadata { + entry: PathBuf, + #[source] + error: io::Error, + }, + #[error("Cannot canonicalize directory {dir} for cache-busting: {error}")] + CannotCanonicalizeCacheBustedDir { + dir: PathBuf, + #[source] + error: io::Error, + }, #[error("Multiple files map to the same web path {web_path}: {first_file} and {second_file}")] DuplicateWebPath { web_path: String, diff --git a/static-serve-macro/src/lib.rs b/static-serve-macro/src/lib.rs index 3a15203..3df2025 100644 --- a/static-serve-macro/src/lib.rs +++ b/static-serve-macro/src/lib.rs @@ -487,6 +487,7 @@ fn parse_dirs(input: ParseStream) -> syn::Result> { Ok(dirs) } +#[expect(clippy::too_many_lines)] fn generate_static_routes( assets_dir: &LitStr, ignore_paths: &IgnorePaths, @@ -497,16 +498,22 @@ fn generate_static_routes( ) -> Result { let assets_dir_abs = Path::new(&assets_dir.value()) .canonicalize() - .map_err(Error::CannotCanonicalizeDirectory)?; + .map_err(|error| Error::CannotCanonicalizeDirectory { + dir: assets_dir.value(), + error, + })?; let assets_dir_abs_str = assets_dir_abs .to_str() - .ok_or(Error::InvalidUnicodeInDirectoryName)?; + .ok_or_else(|| Error::InvalidUnicodeInDirectoryName(assets_dir_abs.clone()))?; let canon_ignore_paths = ignore_paths .0 .iter() .map(|d| { d.canonicalize() - .map_err(Error::CannotCanonicalizeIgnorePath) + .map_err(|error| Error::CannotCanonicalizeIgnorePath { + path: d.clone(), + error, + }) }) .collect::, _>>()?; let canon_cache_busted_dirs = cache_busted_paths @@ -514,20 +521,32 @@ fn generate_static_routes( .iter() .map(|d| { d.canonicalize() - .map_err(Error::CannotCanonicalizeCacheBustedDir) + .map_err(|error| Error::CannotCanonicalizeCacheBustedDir { + dir: d.clone(), + error, + }) }) .collect::, _>>()?; let canon_cache_busted_files = cache_busted_paths .files .iter() - .map(|file| file.canonicalize().map_err(Error::CannotCanonicalizeFile)) + .map(|file| { + file.canonicalize() + .map_err(|error| Error::CannotCanonicalizeFile { + entry: file.clone(), + error, + }) + }) .collect::, _>>()?; let mut routes = Vec::new(); let mut seen_web_paths = HashMap::new(); for entry in glob(&format!("{assets_dir_abs_str}/**/*")).map_err(Error::Pattern)? { let entry = entry.map_err(Error::Glob)?; - let metadata = entry.metadata().map_err(Error::CannotGetMetadata)?; + let metadata = entry.metadata().map_err(|error| Error::CannotGetMetadata { + entry: entry.clone(), + error, + })?; if metadata.is_dir() { continue; } @@ -551,8 +570,10 @@ fn generate_static_routes( let entry = entry .canonicalize() - .map_err(Error::CannotCanonicalizeFile)?; - let entry_str = entry.to_str().ok_or(Error::FilePathIsNotUtf8)?; + .map_err(|error| Error::CannotCanonicalizeFile { entry, error })?; + let entry_str = entry + .to_str() + .ok_or_else(|| Error::FilePathIsNotUtf8(entry.clone()))?; let EmbeddedFileInfo { entry_path, content_type, @@ -632,8 +653,13 @@ fn generate_static_handler( ) -> Result { let asset_file_abs = Path::new(&asset_file.value()) .canonicalize() - .map_err(Error::CannotCanonicalizeFile)?; - let asset_file_abs_str = asset_file_abs.to_str().ok_or(Error::FilePathIsNotUtf8)?; + .map_err(|error| Error::CannotCanonicalizeFile { + entry: Path::new(&asset_file.value()).to_path_buf(), + error, + })?; + let asset_file_abs_str = asset_file_abs + .to_str() + .ok_or_else(|| Error::FilePathIsNotUtf8(asset_file_abs.clone()))?; let EmbeddedFileInfo { entry_path: _, @@ -707,12 +733,15 @@ impl EmbeddedFileInfo { cache_busted: bool, allow_unknown_extensions: bool, ) -> Result { - let contents = fs::read(pathbuf).map_err(Error::CannotReadEntryContents)?; + let contents = fs::read(pathbuf).map_err(|error| Error::CannotReadEntryContents { + entry: pathbuf.clone(), + error, + })?; // Optionally compress files let (maybe_gzip, maybe_zstd) = if should_compress.value { - let gzip = gzip_compress(&contents)?; - let zstd = zstd_compress(&contents)?; + let gzip = gzip_compress(&contents, pathbuf)?; + let zstd = zstd_compress(&contents, pathbuf)?; (gzip, zstd) } else { (None, None) @@ -751,27 +780,32 @@ impl EmbeddedFileInfo { } } -fn gzip_compress(contents: &[u8]) -> Result, Error> { +fn gzip_compress(contents: &[u8], entry: &Path) -> Result, Error> { let mut compressor = GzEncoder::new(Vec::new(), flate2::Compression::best()); - compressor - .write_all(contents) - .map_err(|e| Error::Gzip(GzipType::CompressorWrite(e)))?; - let compressed = compressor - .finish() - .map_err(|e| Error::Gzip(GzipType::EncoderFinish(e)))?; + compressor.write_all(contents).map_err(|e| Error::Gzip { + entry: entry.to_path_buf(), + error: GzipType::CompressorWrite(e), + })?; + let compressed = compressor.finish().map_err(|e| Error::Gzip { + entry: entry.to_path_buf(), + error: GzipType::EncoderFinish(e), + })?; Ok(maybe_get_compressed(&compressed, contents)) } -fn zstd_compress(contents: &[u8]) -> Result, Error> { +fn zstd_compress(contents: &[u8], entry: &Path) -> Result, Error> { let level = *zstd::compression_level_range().end(); let mut encoder = zstd::Encoder::new(Vec::new(), level).unwrap(); - write_to_zstd_encoder(&mut encoder, contents) - .map_err(|e| Error::Zstd(ZstdType::EncoderWrite(e)))?; - - let compressed = encoder - .finish() - .map_err(|e| Error::Zstd(ZstdType::EncoderFinish(e)))?; + write_to_zstd_encoder(&mut encoder, contents).map_err(|e| Error::Zstd { + entry: entry.to_path_buf(), + error: ZstdType::EncoderWrite(e), + })?; + + let compressed = encoder.finish().map_err(|e| Error::Zstd { + entry: entry.to_path_buf(), + error: ZstdType::EncoderFinish(e), + })?; Ok(maybe_get_compressed(&compressed, contents)) }