Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 53 additions & 21 deletions static-serve-macro/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,64 @@ pub(crate) enum Error {
UnknownFileExtension(Option<OsString>),
#[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,
Expand Down
88 changes: 61 additions & 27 deletions static-serve-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ fn parse_dirs(input: ParseStream) -> syn::Result<Vec<(PathBuf, Span)>> {
Ok(dirs)
}

#[expect(clippy::too_many_lines)]
fn generate_static_routes(
assets_dir: &LitStr,
ignore_paths: &IgnorePaths,
Expand All @@ -497,37 +498,55 @@ fn generate_static_routes(
) -> Result<TokenStream, error::Error> {
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::<Result<Vec<_>, _>>()?;
let canon_cache_busted_dirs = cache_busted_paths
.dirs
.iter()
.map(|d| {
d.canonicalize()
.map_err(Error::CannotCanonicalizeCacheBustedDir)
.map_err(|error| Error::CannotCanonicalizeCacheBustedDir {
dir: d.clone(),
error,
})
})
.collect::<Result<Vec<_>, _>>()?;
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::<Result<Vec<_>, _>>()?;

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;
}
Expand All @@ -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,
Expand Down Expand Up @@ -632,8 +653,13 @@ fn generate_static_handler(
) -> Result<TokenStream, error::Error> {
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: _,
Expand Down Expand Up @@ -707,12 +733,15 @@ impl EmbeddedFileInfo {
cache_busted: bool,
allow_unknown_extensions: bool,
) -> Result<Self, Error> {
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)
Expand Down Expand Up @@ -751,27 +780,32 @@ impl EmbeddedFileInfo {
}
}

fn gzip_compress(contents: &[u8]) -> Result<Option<LitByteStr>, Error> {
fn gzip_compress(contents: &[u8], entry: &Path) -> Result<Option<LitByteStr>, 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<Option<LitByteStr>, Error> {
fn zstd_compress(contents: &[u8], entry: &Path) -> Result<Option<LitByteStr>, 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))
}
Expand Down