Repository: denoland/deno
Pull request: fix(npm): add some context to errors on tarball extraction failure

Noticed this while working on a different PR.

Changed files:
filename: resolvers/npm_cache/tarball_extract.rs
patch: @@ -192,11 +192,39 @@ fn verify_tarball_integrity(
   Ok(())
 }
 
+#[derive(Debug, Copy, Clone)]
+pub enum IoErrorOperation {
+  Creating,
+  Canonicalizing,
+}
+
+impl std::fmt::Display for IoErrorOperation {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    match self {
+      IoErrorOperation::Creating => write!(f, "creating"),
+      IoErrorOperation::Canonicalizing => write!(f, "canonicalizing"),
+    }
+  }
+}
+
+#[derive(Debug, thiserror::Error, deno_error::JsError)]
+#[class(generic)]
+#[error("Failed {} '{}'", operation, path.display())]
+pub struct IoWithPathError {
+  pub path: PathBuf,
+  pub operation: IoErrorOperation,
+  #[source]
+  pub source: std::io::Error,
+}
+
 #[derive(Debug, thiserror::Error, deno_error::JsError)]
 pub enum ExtractTarballError {
   #[class(inherit)]
   #[error(transparent)]
   Io(#[from] std::io::Error),
+  #[class(inherit)]
+  #[error(transparent)]
+  IoWithPath(#[from] IoWithPathError),
   #[class(generic)]
   #[error(
     "Extracted directory '{0}' of npm tarball was not in output directory."
@@ -209,8 +237,21 @@ fn extract_tarball(
   data: &[u8],
   output_folder: &Path,
 ) -> Result<(), ExtractTarballError> {
-  sys.fs_create_dir_all(output_folder)?;
-  let output_folder = sys.fs_canonicalize(output_folder)?;
+  sys
+    .fs_create_dir_all(output_folder)
+    .map_err(|source| IoWithPathError {
+      path: output_folder.to_path_buf(),
+      operation: IoErrorOperation::Creating,
+      source,
+    })?;
+  let output_folder =
+    sys
+      .fs_canonicalize(output_folder)
+      .map_err(|source| IoWithPathError {
+        path: output_folder.to_path_buf(),
+        operation: IoErrorOperation::Canonicalizing,
+        source,
+      })?;
   let tar = GzDecoder::new(data);
   let mut archive = Archive::new(tar);
   archive.set_overwrite(true);
@@ -237,8 +278,21 @@ fn extract_tarball(
       absolute_path.parent().unwrap()
     };
     if created_dirs.insert(dir_path.to_path_buf()) {
-      sys.fs_create_dir_all(dir_path)?;
-      let canonicalized_dir = sys.fs_canonicalize(dir_path)?;
+      sys
+        .fs_create_dir_all(dir_path)
+        .map_err(|source| IoWithPathError {
+          path: output_folder.to_path_buf(),
+          operation: IoErrorOperation::Creating,
+          source,
+        })?;
+      let canonicalized_dir =
+        sys
+          .fs_canonicalize(dir_path)
+          .map_err(|source| IoWithPathError {
+            path: output_folder.to_path_buf(),
+            operation: IoErrorOperation::Canonicalizing,
+            source,
+          })?;
       if !canonicalized_dir.starts_with(&output_folder) {
         return Err(ExtractTarballError::NotInOutputDirectory(
           canonicalized_dir.to_path_buf(),
@@ -250,10 +304,23 @@ fn extract_tarball(
     match entry_type {
       EntryType::Regular => {
         // todo(dsherret): switch back to using the sys so that this
-        // crate can work in Wasm
-        // let open_options = OpenOptions::new_write();
-        // let mut f = sys.fs_open(&absolute_path, &open_options)?;
-        // std::io::copy(&mut entry, &mut f)?;
+        // crate can work in Wasm. Beware that doing this broke
+        // stuff. See https://github.com/denoland/deno/pull/29156
+        // let mut f =
+        //     sys
+        //     .fs_open(&absolute_path, &open_options)
+        //     .map_err(|source| IoWithPathError {
+        //       path: absolute_path.to_path_buf(),
+        //       operation: IoErrorOperation::Opening,
+        //       source,
+        //     })?;
+        // std::io::copy(&mut entry, &mut f).map_err(|source| {
+        //   IoWithPathError {
+        //     path: absolute_path,
+        //     operation: IoErrorOperation::Writing,
+        //     source,
+        //   }
+        // })?;
         entry.unpack(&absolute_path)?;
       }
       EntryType::Symlink | EntryType::Link => {