Skip to content

Commit 30a7f9b

Browse files
mickvandijkeclaude
authored andcommitted
fix: address review issues — dead code, swallowed errors, unclear messages, test duplication
- Remove unreachable `<error>` branches in `debug_bytes` (len > 6 is already proven by the early return, so direct indexing is safe) - Propagate read errors in Node.js and Python file-read iterators instead of silently returning None on I/O failure - Improve `into_datamap()` doc and error messages to explain that `chunks()` must be fully consumed before calling it - Deduplicate 5 near-identical streaming roundtrip integration tests into 2 parameterized helpers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7c33333 commit 30a7f9b

5 files changed

Lines changed: 86 additions & 102 deletions

File tree

nodejs/src/lib.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,11 @@ pub fn streaming_encrypt_from_file(
436436
let data_size = file.metadata().map_err(map_error)?.len() as usize;
437437
let mut reader = std::io::BufReader::new(file);
438438

439-
// Create iterator that reads file in chunks
439+
// Track read errors so they aren't silently swallowed by the iterator
440+
let read_error: std::rc::Rc<std::cell::RefCell<Option<std::io::Error>>> =
441+
std::rc::Rc::new(std::cell::RefCell::new(None));
442+
let read_error_clone = read_error.clone();
443+
440444
let data_iter = std::iter::from_fn(move || {
441445
let mut buffer = vec![0u8; 8192];
442446
match reader.read(&mut buffer) {
@@ -445,7 +449,10 @@ pub fn streaming_encrypt_from_file(
445449
buffer.truncate(n);
446450
Some(Bytes::from(buffer))
447451
}
448-
Err(_) => None,
452+
Err(e) => {
453+
*read_error_clone.borrow_mut() = Some(e);
454+
None
455+
}
449456
}
450457
});
451458

@@ -474,10 +481,17 @@ pub fn streaming_encrypt_from_file(
474481
})?;
475482
}
476483

484+
if let Some(e) = read_error.borrow_mut().take() {
485+
return Err(napi::Error::new(
486+
Status::GenericFailure,
487+
format!("Failed to read input file: {e}"),
488+
));
489+
}
490+
477491
let datamap = stream.into_datamap().ok_or_else(|| {
478492
napi::Error::new(
479493
Status::GenericFailure,
480-
"Encryption did not produce a DataMap",
494+
"Encryption did not produce a DataMap — ensure chunks() iterator was fully consumed",
481495
)
482496
})?;
483497

@@ -499,6 +513,11 @@ pub fn encrypt_from_file(input_file: String, output_dir: String) -> Result<Encry
499513
let data_size = file.metadata().map_err(map_error)?.len() as usize;
500514
let mut reader = std::io::BufReader::new(file);
501515

516+
// Track read errors so they aren't silently swallowed by the iterator
517+
let read_error: std::rc::Rc<std::cell::RefCell<Option<std::io::Error>>> =
518+
std::rc::Rc::new(std::cell::RefCell::new(None));
519+
let read_error_clone = read_error.clone();
520+
502521
let data_iter = std::iter::from_fn(move || {
503522
let mut buffer = vec![0u8; 8192];
504523
match reader.read(&mut buffer) {
@@ -507,7 +526,10 @@ pub fn encrypt_from_file(input_file: String, output_dir: String) -> Result<Encry
507526
buffer.truncate(n);
508527
Some(Bytes::from(buffer))
509528
}
510-
Err(_) => None,
529+
Err(e) => {
530+
*read_error_clone.borrow_mut() = Some(e);
531+
None
532+
}
511533
}
512534
});
513535

@@ -533,10 +555,17 @@ pub fn encrypt_from_file(input_file: String, output_dir: String) -> Result<Encry
533555
})?;
534556
}
535557

558+
if let Some(e) = read_error.borrow_mut().take() {
559+
return Err(napi::Error::new(
560+
Status::GenericFailure,
561+
format!("Failed to read input file: {e}"),
562+
));
563+
}
564+
536565
let data_map = stream.into_datamap().ok_or_else(|| {
537566
napi::Error::new(
538567
Status::GenericFailure,
539-
"Encryption did not produce a DataMap",
568+
"Encryption did not produce a DataMap — ensure chunks() iterator was fully consumed",
540569
)
541570
})?;
542571

src/data_map.rs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -246,22 +246,17 @@ fn debug_bytes<V: AsRef<[u8]>>(input: V) -> String {
246246
}
247247
return ret;
248248
}
249-
match (input_ref.first(), input_ref.get(1), input_ref.get(2)) {
250-
(Some(a), Some(b), Some(c)) => {
251-
let len = input_ref.len();
252-
match (
253-
input_ref.get(len - 3),
254-
input_ref.get(len - 2),
255-
input_ref.get(len - 1),
256-
) {
257-
(Some(x), Some(y), Some(z)) => {
258-
format!("{a:02x}{b:02x}{c:02x}..{x:02x}{y:02x}{z:02x}")
259-
}
260-
_ => "<error>".to_owned(),
261-
}
262-
}
263-
_ => "<error>".to_owned(),
264-
}
249+
// Safety: len > 6 is guaranteed by the early return above, so all indices are valid.
250+
let len = input_ref.len();
251+
format!(
252+
"{:02x}{:02x}{:02x}..{:02x}{:02x}{:02x}",
253+
input_ref[0],
254+
input_ref[1],
255+
input_ref[2],
256+
input_ref[len - 3],
257+
input_ref[len - 2],
258+
input_ref[len - 1]
259+
)
265260
}
266261

267262
impl Debug for ChunkInfo {

src/python.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,11 @@ pub fn encrypt_from_file(input_file: &str, output_dir: &str) -> PyResult<(PyData
345345
.len() as usize;
346346
let mut reader = std::io::BufReader::new(file);
347347

348+
// Track read errors so they aren't silently swallowed by the iterator
349+
let read_error: std::rc::Rc<std::cell::RefCell<Option<std::io::Error>>> =
350+
std::rc::Rc::new(std::cell::RefCell::new(None));
351+
let read_error_clone = read_error.clone();
352+
348353
let data_iter = std::iter::from_fn(move || {
349354
let mut buffer = vec![0u8; 8192];
350355
match reader.read(&mut buffer) {
@@ -353,7 +358,10 @@ pub fn encrypt_from_file(input_file: &str, output_dir: &str) -> PyResult<(PyData
353358
buffer.truncate(n);
354359
Some(Bytes::from(buffer))
355360
}
356-
Err(_) => None,
361+
Err(e) => {
362+
*read_error_clone.borrow_mut() = Some(e);
363+
None
364+
}
357365
}
358366
});
359367

@@ -375,8 +383,16 @@ pub fn encrypt_from_file(input_file: &str, output_dir: &str) -> PyResult<(PyData
375383
})?;
376384
}
377385

386+
if let Some(e) = read_error.borrow_mut().take() {
387+
return Err(pyo3::exceptions::PyOSError::new_err(format!(
388+
"Failed to read input file: {e}"
389+
)));
390+
}
391+
378392
let data_map = stream.into_datamap().ok_or_else(|| {
379-
pyo3::exceptions::PyValueError::new_err("Encryption did not produce a DataMap")
393+
pyo3::exceptions::PyValueError::new_err(
394+
"Encryption did not produce a DataMap — ensure chunks() iterator was fully consumed",
395+
)
380396
})?;
381397

382398
Ok((PyDataMap { inner: data_map }, chunk_names))

src/stream_encrypt.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,8 @@ impl<I> EncryptionStream<I> {
602602
/// # Ok(())
603603
/// # }
604604
/// ```
605-
/// Returns the final DataMap, or `None` if encryption is not yet complete.
605+
/// Returns the final DataMap, or `None` if encryption is not yet complete
606+
/// (i.e., `chunks()` was not fully consumed).
606607
pub fn into_datamap(self) -> Option<DataMap> {
607608
self.final_datamap
608609
}

tests/integration_tests.rs

Lines changed: 21 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -873,16 +873,15 @@ fn make_parallel_fetcher(
873873
}
874874
}
875875

876-
// --- Task 5: stream_encrypt + streaming_decrypt roundtrip ---
877-
878-
#[test]
879-
fn test_stream_encrypt_decrypt_roundtrip() -> Result<()> {
880-
let data_size = 100_000;
876+
/// Helper: stream-encrypt data with a given iterator chunk size, then stream-decrypt and verify.
877+
fn assert_stream_roundtrip(data_size: usize, iter_chunk_size: usize) -> Result<()> {
881878
let original_data = random_bytes(data_size);
882879

883880
let mut stream = stream_encrypt(
884881
data_size,
885-
original_data.chunks(4096).map(|c| Bytes::from(c.to_vec())),
882+
original_data
883+
.chunks(iter_chunk_size)
884+
.map(|c| Bytes::from(c.to_vec())),
886885
)?;
887886

888887
let mut storage = HashMap::new();
@@ -893,82 +892,34 @@ fn test_stream_encrypt_decrypt_roundtrip() -> Result<()> {
893892

894893
let data_map = stream
895894
.datamap()
896-
.ok_or_else(|| Error::Generic("No DataMap after stream_encrypt".to_string()))?;
895+
.ok_or_else(|| Error::Generic("No DataMap after stream_encrypt".to_string()))?
896+
.clone();
897897

898898
let fetcher = make_parallel_fetcher(&storage);
899-
let decrypt_stream = streaming_decrypt(data_map, fetcher)?;
899+
let decrypt_stream = streaming_decrypt(&data_map, fetcher)?;
900900
let decrypted = decrypt_stream.range_full()?;
901901

902902
assert_eq!(decrypted.as_ref(), &original_data[..]);
903903
Ok(())
904904
}
905905

906-
// --- Task 6: stream_encrypt + streaming_decrypt roundtrip ---
906+
#[test]
907+
fn test_stream_encrypt_decrypt_roundtrip() -> Result<()> {
908+
assert_stream_roundtrip(100_000, 4096)
909+
}
907910

908911
#[test]
909912
fn test_file_stream_encrypt_decrypt_roundtrip() -> Result<()> {
910-
let data_size = 200_000;
911-
let original_data = random_bytes(data_size);
912-
913-
let mut stream = stream_encrypt(
914-
data_size,
915-
original_data.chunks(8192).map(|c| Bytes::from(c.to_vec())),
916-
)?;
917-
918-
let mut storage = HashMap::new();
919-
for chunk_result in stream.chunks() {
920-
let (hash, content) = chunk_result?;
921-
let _ = storage.insert(hash, content.to_vec());
922-
}
923-
924-
let data_map = stream
925-
.datamap()
926-
.ok_or_else(|| Error::Generic("No DataMap".to_string()))?
927-
.clone();
928-
929-
let fetcher = make_parallel_fetcher(&storage);
930-
let decrypt_stream = streaming_decrypt(&data_map, fetcher)?;
931-
let decrypted = decrypt_stream.range_full()?;
932-
933-
assert_eq!(decrypted.as_ref(), &original_data[..]);
934-
Ok(())
913+
assert_stream_roundtrip(200_000, 8192)
935914
}
936915

937-
// --- Task 7: Cross-compatibility between streaming APIs ---
938-
939916
#[test]
940917
fn test_stream_encrypt_file_decrypt_storage_cross() -> Result<()> {
941-
let data_size = 150_000;
942-
let original_data = random_bytes(data_size);
943-
944-
let mut stream = stream_encrypt(
945-
data_size,
946-
original_data.chunks(8192).map(|c| Bytes::from(c.to_vec())),
947-
)?;
948-
949-
let mut storage = HashMap::new();
950-
for chunk_result in stream.chunks() {
951-
let (hash, content) = chunk_result?;
952-
let _ = storage.insert(hash, content.to_vec());
953-
}
954-
955-
let data_map = stream
956-
.datamap()
957-
.ok_or_else(|| Error::Generic("No DataMap".to_string()))?
958-
.clone();
959-
960-
let fetcher = make_parallel_fetcher(&storage);
961-
let decrypt_stream = streaming_decrypt(&data_map, fetcher)?;
962-
let decrypted = decrypt_stream.range_full()?;
963-
964-
assert_eq!(decrypted.as_ref(), &original_data[..]);
965-
Ok(())
918+
assert_stream_roundtrip(150_000, 8192)
966919
}
967920

968-
#[test]
969-
fn test_file_encrypt_stream_decrypt_cross() -> Result<()> {
970-
// Use standard encrypt() then streaming decrypt — cross-compatibility test
971-
let data_size = 150_000;
921+
/// Helper: standard encrypt() then streaming_decrypt() cross-compatibility check.
922+
fn assert_encrypt_then_stream_decrypt(data_size: usize) -> Result<()> {
972923
let original_data = random_bytes(data_size);
973924

974925
let (data_map, encrypted_chunks) = encrypt(original_data.clone())?;
@@ -982,22 +933,14 @@ fn test_file_encrypt_stream_decrypt_cross() -> Result<()> {
982933
Ok(())
983934
}
984935

985-
// --- Task 8: In-memory encrypt ↔ streaming decrypt cross-compatibility ---
936+
#[test]
937+
fn test_file_encrypt_stream_decrypt_cross() -> Result<()> {
938+
assert_encrypt_then_stream_decrypt(150_000)
939+
}
986940

987941
#[test]
988942
fn test_memory_encrypt_stream_decrypt() -> Result<()> {
989-
let data_size = 100_000;
990-
let original_data = random_bytes(data_size);
991-
992-
let (data_map, encrypted_chunks) = encrypt(original_data.clone())?;
993-
let storage = build_chunk_storage(&encrypted_chunks);
994-
let fetcher = make_parallel_fetcher(&storage);
995-
996-
let decrypt_stream = streaming_decrypt(&data_map, fetcher)?;
997-
let decrypted = decrypt_stream.range_full()?;
998-
999-
assert_eq!(decrypted.as_ref(), &original_data[..]);
1000-
Ok(())
943+
assert_encrypt_then_stream_decrypt(100_000)
1001944
}
1002945

1003946
#[test]

0 commit comments

Comments
 (0)