use bytes::{Bytes, BytesMut};
#[derive(Debug, Clone)]
pub struct UploadState {
pub name: String,
pub uuid: String,
pub buffer: BytesMut,
}
impl UploadState {
#[must_use]
pub fn new(name: impl Into<String>, uuid: impl Into<String>) -> Self {
Self {
name: name.into(),
uuid: uuid.into(),
buffer: BytesMut::new(),
}
}
#[must_use]
pub fn offset(&self) -> u64 {
self.buffer.len() as u64
}
pub fn append(&mut self, chunk: &Bytes) -> u64 {
self.buffer.extend_from_slice(chunk);
self.offset()
}
pub fn take_bytes(&mut self) -> Bytes {
std::mem::take(&mut self.buffer).freeze()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ContentRangeParseError {
#[error("malformed Content-Range")]
Malformed,
#[error("reversed range (start > end)")]
Reversed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentRange {
pub start: u64,
pub end: u64,
}
impl ContentRange {
pub fn parse(value: &str) -> Result<Self, ContentRangeParseError> {
let value = value.trim();
let payload = value.strip_prefix("bytes ").unwrap_or(value);
let payload = payload.split('/').next().unwrap_or(payload);
let (start, end) = payload
.split_once('-')
.ok_or(ContentRangeParseError::Malformed)?;
let start: u64 = start
.trim()
.parse()
.map_err(|_| ContentRangeParseError::Malformed)?;
let end: u64 = end
.trim()
.parse()
.map_err(|_| ContentRangeParseError::Malformed)?;
if start > end {
return Err(ContentRangeParseError::Reversed);
}
Ok(Self { start, end })
}
#[must_use]
pub const fn length(self) -> u64 {
self.end - self.start + 1
}
}
#[cfg(test)]
mod tests {
use super::{ContentRange, UploadState};
use bytes::Bytes;
#[test]
fn append_updates_offset() {
let mut s = UploadState::new("lib/alpine", "abc");
assert_eq!(s.offset(), 0);
let n1 = s.append(&Bytes::from_static(b"hello"));
assert_eq!(n1, 5);
let n2 = s.append(&Bytes::from_static(b"!"));
assert_eq!(n2, 6);
}
#[test]
fn take_bytes_returns_everything_and_resets() {
let mut s = UploadState::new("lib/alpine", "abc");
s.append(&Bytes::from_static(b"hello"));
let out = s.take_bytes();
assert_eq!(&out[..], b"hello");
assert_eq!(s.offset(), 0);
}
#[test]
fn content_range_parse_bare_form() {
let r = ContentRange::parse("0-1023").expect("parse");
assert_eq!(
r,
ContentRange {
start: 0,
end: 1023
}
);
assert_eq!(r.length(), 1024);
}
#[test]
fn content_range_parse_bytes_prefix() {
let r = ContentRange::parse("bytes 100-199").expect("parse");
assert_eq!(
r,
ContentRange {
start: 100,
end: 199
}
);
}
#[test]
fn content_range_parse_with_total() {
let r = ContentRange::parse("bytes 0-9/100").expect("parse");
assert_eq!(r, ContentRange { start: 0, end: 9 });
}
#[test]
fn content_range_rejects_reversed() {
assert!(ContentRange::parse("10-5").is_err());
}
#[test]
fn content_range_rejects_garbage() {
assert!(ContentRange::parse("not-a-range").is_err());
assert!(ContentRange::parse("").is_err());
}
}