exarch-rs 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -167,11 +167,13 @@ Builder-style security configuration.
167
167
 
168
168
  ```typescript
169
169
  const config = new SecurityConfig()
170
- .maxFileSize(bytes) // Max size per file
171
- .maxTotalSize(bytes) // Max total extraction size
172
- .maxFileCount(count) // Max number of files
173
- .maxCompressionRatio(n) // Max compression ratio (zip bomb detection)
174
- .setAllowSolidArchives(true); // Allow solid 7z archives (default: false)
170
+ .maxFileSize(bytes) // Max size per file
171
+ .maxTotalSize(bytes) // Max total extraction size
172
+ .maxFileCount(count) // Max number of files
173
+ .maxCompressionRatio(n) // Max compression ratio (zip bomb detection)
174
+ .allowedExtensions([".txt", ".md"]) // Restrict to a set of extensions
175
+ .bannedPathComponents(["__MACOSX"]) // Skip these path components
176
+ .setAllowSolidArchives(true); // Allow solid 7z archives (default: false)
175
177
  ```
176
178
 
177
179
  ## Security Features
@@ -203,6 +205,8 @@ The library provides built-in protection against:
203
205
 
204
206
  **Note:** 7z creation is not yet supported. Solid and encrypted 7z archives are rejected for security reasons. Unix symlinks inside 7z archives are reported as regular files (sevenz-rust2 API limitation).
205
207
 
208
+ **Note:** Since v0.4.0, partial extraction failures return the `ExtractionReport` accumulated up to the failure point without the inner error text being duplicated, and the report is now correctly delivered across the FFI boundary instead of being dropped early.
209
+
206
210
  ## Comparison with tar-fs
207
211
 
208
212
  ```javascript
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exarch-rs",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Memory-safe archive extraction library with built-in security validation",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -36,8 +36,8 @@
36
36
  }
37
37
  },
38
38
  "devDependencies": {
39
- "@biomejs/biome": "^2.0",
40
- "@napi-rs/cli": "^3.5"
39
+ "@biomejs/biome": "^2.4.15",
40
+ "@napi-rs/cli": "^3.6.2"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">= 18"
package/src/error.rs CHANGED
@@ -182,7 +182,18 @@ pub fn convert_error(err: CoreError) -> Error {
182
182
  msg.push_str(&reason);
183
183
  Error::new(Status::GenericFailure, msg)
184
184
  }
185
- CoreError::PartialExtraction { source, .. } => convert_error(*source),
185
+ CoreError::PartialExtraction { source, report } => {
186
+ let inner = convert_error(*source);
187
+ let mut msg = String::with_capacity(inner.reason.len() + 64);
188
+ msg.push_str("PARTIAL_EXTRACTION: ");
189
+ msg.push_str(&inner.reason);
190
+ let _ = write!(
191
+ &mut msg,
192
+ " | filesExtracted={}, bytesWritten={}",
193
+ report.files_extracted, report.bytes_written
194
+ );
195
+ Error::new(Status::GenericFailure, msg)
196
+ }
186
197
  }
187
198
  }
188
199
 
@@ -349,4 +360,40 @@ mod tests {
349
360
  assert!(err_str.contains("IO_ERROR"));
350
361
  assert!(err_str.contains("file not found"));
351
362
  }
363
+
364
+ /// Regression test for #210: `convert_error` must embed `filesExtracted`
365
+ /// and `bytesWritten` in the error message for `PartialExtraction`.
366
+ #[test]
367
+ fn test_partial_extraction_node_message_format() {
368
+ use exarch_core::ExtractionReport;
369
+ use exarch_core::QuotaResource;
370
+
371
+ let report = ExtractionReport {
372
+ files_extracted: 3,
373
+ bytes_written: 1024,
374
+ ..ExtractionReport::default()
375
+ };
376
+ let source = CoreError::QuotaExceeded {
377
+ resource: QuotaResource::FileCount { current: 4, max: 3 },
378
+ };
379
+ let err = CoreError::PartialExtraction {
380
+ source: Box::new(source),
381
+ report,
382
+ };
383
+
384
+ let napi_err = convert_error(err);
385
+ let msg = napi_err.reason.clone();
386
+ assert!(
387
+ msg.contains("PARTIAL_EXTRACTION"),
388
+ "message must contain PARTIAL_EXTRACTION, got: {msg}"
389
+ );
390
+ assert!(
391
+ msg.contains("filesExtracted=3"),
392
+ "message must contain filesExtracted=3, got: {msg}"
393
+ );
394
+ assert!(
395
+ msg.contains("bytesWritten=1024"),
396
+ "message must contain bytesWritten=1024, got: {msg}"
397
+ );
398
+ }
352
399
  }
package/src/report.rs CHANGED
@@ -285,7 +285,7 @@ mod tests {
285
285
  core_report.files_extracted = 100_000;
286
286
  core_report.directories_created = 50_000;
287
287
  core_report.bytes_written = 10_000_000_000; // 10 GB
288
- core_report.duration = Duration::from_secs(3600); // 1 hour
288
+ core_report.duration = Duration::from_hours(1);
289
289
 
290
290
  let report = ExtractionReport::from(core_report);
291
291
 
@@ -312,7 +312,7 @@ mod tests {
312
312
  #[test]
313
313
  fn test_extraction_report_duration_hours() {
314
314
  let mut core_report = CoreReport::new();
315
- core_report.duration = Duration::from_secs(7200); // 2 hours
315
+ core_report.duration = Duration::from_hours(2);
316
316
 
317
317
  let report = ExtractionReport::from(core_report);
318
318