#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ChunkedUploadSplitter {
chunk_size: usize,
}
impl ChunkedUploadSplitter {
#[must_use]
pub fn new(chunk_size: usize) -> Self {
Self {
chunk_size: chunk_size.max(1),
}
}
#[must_use]
pub fn split(&self, data: &[u8]) -> Vec<Vec<u8>> {
if data.is_empty() {
return Vec::new();
}
data.chunks(self.chunk_size)
.map(|c| c.to_vec())
.collect()
}
#[must_use]
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
#[must_use]
pub fn chunk_count(&self, data_len: usize) -> usize {
if data_len == 0 {
return 0;
}
(data_len + self.chunk_size - 1) / self.chunk_size
}
}
pub struct ChunkedUploadReassembler;
impl ChunkedUploadReassembler {
#[must_use]
pub fn reassemble(parts: &[Vec<u8>]) -> Vec<u8> {
let total: usize = parts.iter().map(|p| p.len()).sum();
let mut out = Vec::with_capacity(total);
for part in parts {
out.extend_from_slice(part);
}
out
}
#[must_use]
pub fn validate_total(parts: &[Vec<u8>], expected_total: usize) -> bool {
parts.iter().map(|p| p.len()).sum::<usize>() == expected_total
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_empty_data() {
let s = ChunkedUploadSplitter::new(100);
assert!(s.split(&[]).is_empty());
}
#[test]
fn test_split_exact_multiple() {
let data: Vec<u8> = (0..9).collect(); let s = ChunkedUploadSplitter::new(3);
let chunks = s.split(&data);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0], &[0, 1, 2]);
assert_eq!(chunks[1], &[3, 4, 5]);
assert_eq!(chunks[2], &[6, 7, 8]);
}
#[test]
fn test_split_non_exact_multiple() {
let data: Vec<u8> = (0..10).collect(); let s = ChunkedUploadSplitter::new(3);
let chunks = s.split(&data);
assert_eq!(chunks.len(), 4); assert_eq!(chunks[3], &[9]);
}
#[test]
fn test_split_larger_than_data() {
let data = vec![1u8, 2, 3];
let s = ChunkedUploadSplitter::new(1000);
let chunks = s.split(&data);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], data);
}
#[test]
fn test_split_chunk_size_one() {
let data = vec![10u8, 20, 30];
let s = ChunkedUploadSplitter::new(1);
let chunks = s.split(&data);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0], &[10]);
assert_eq!(chunks[1], &[20]);
assert_eq!(chunks[2], &[30]);
}
#[test]
fn test_split_chunk_size_zero_clamped_to_one() {
let data = vec![0u8; 5];
let s = ChunkedUploadSplitter::new(0); assert_eq!(s.chunk_size(), 1);
let chunks = s.split(&data);
assert_eq!(chunks.len(), 5);
}
#[test]
fn test_chunk_count() {
let s = ChunkedUploadSplitter::new(10);
assert_eq!(s.chunk_count(0), 0);
assert_eq!(s.chunk_count(10), 1);
assert_eq!(s.chunk_count(11), 2);
assert_eq!(s.chunk_count(20), 2);
assert_eq!(s.chunk_count(21), 3);
}
#[test]
fn test_reassemble_empty() {
let parts: &[Vec<u8>] = &[];
assert!(ChunkedUploadReassembler::reassemble(parts).is_empty());
}
#[test]
fn test_reassemble_single_chunk() {
let parts = vec![vec![1u8, 2, 3]];
assert_eq!(
ChunkedUploadReassembler::reassemble(&parts),
vec![1u8, 2, 3]
);
}
#[test]
fn test_reassemble_multiple_chunks() {
let parts = vec![vec![1u8, 2], vec![3u8, 4], vec![5u8]];
assert_eq!(
ChunkedUploadReassembler::reassemble(&parts),
vec![1u8, 2, 3, 4, 5]
);
}
#[test]
fn test_split_then_reassemble_roundtrip() {
let original: Vec<u8> = (0u8..=255).collect();
let s = ChunkedUploadSplitter::new(64);
let chunks = s.split(&original);
let rebuilt = ChunkedUploadReassembler::reassemble(&chunks);
assert_eq!(rebuilt, original);
}
#[test]
fn test_validate_total_correct() {
let parts = vec![vec![0u8; 100], vec![0u8; 56]];
assert!(ChunkedUploadReassembler::validate_total(&parts, 156));
}
#[test]
fn test_validate_total_wrong() {
let parts = vec![vec![0u8; 100]];
assert!(!ChunkedUploadReassembler::validate_total(&parts, 200));
}
#[test]
fn test_large_roundtrip() {
let original: Vec<u8> = (0u8..=255).cycle().take(10_000).collect();
let s = ChunkedUploadSplitter::new(1024);
let chunks = s.split(&original);
let rebuilt = ChunkedUploadReassembler::reassemble(&chunks);
assert_eq!(rebuilt, original);
}
}