streaming_http_range_client/
test_client.rs

1use crate::{HttpClient, Reader, ReaderSource, ReqStats, Result};
2use async_trait::async_trait;
3use std::fmt::{Debug, Formatter};
4use std::ops::{Range, RangeFrom};
5
6pub(crate) struct ByteFormatter<'a>(pub &'a [u8]);
7
8impl Debug for ByteFormatter<'_> {
9    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
10        write!(f, "0x{:02X?}", self.0)
11    }
12}
13
14/// Helpful for tests
15#[async_trait(?Send)]
16impl ReaderSource for LocalBytesClient {
17    async fn get_byte_range(&self, mut range: Range<u64>) -> Result<Reader> {
18        self.clamp_range(&mut range);
19        let usize_range = range.start as usize..range.end as usize;
20        let range_data = self.bytes[usize_range].to_owned();
21        Ok(Box::pin(std::io::Cursor::new(range_data)))
22    }
23
24    async fn get_byte_range_from(&self, mut range: RangeFrom<u64>) -> Result<Reader> {
25        self.clamp_range_from(&mut range);
26        let usize_range = range.start as usize..;
27        let range_data = self.bytes[usize_range].to_owned();
28        Ok(Box::pin(std::io::Cursor::new(range_data)))
29    }
30
31    fn boxed_clone(&self) -> Box<dyn ReaderSource> {
32        Box::new(self.clone())
33    }
34}
35
36#[derive(Clone)]
37struct LocalBytesClient {
38    bytes: Vec<u8>,
39}
40
41impl LocalBytesClient {
42    /// When requesting bytes _beyond_ a file's length, a web server typically ignores the extra
43    /// request and just returns up until the end of the file.
44    ///
45    /// This method does similarly for our test client, otherwise we'll get an array OOB panic.
46    fn clamp_range(&self, range: &mut Range<u64>) {
47        let len = self.bytes.len() as u64;
48        if range.start > len {
49            debug!(
50                "clamping request start to filesize {start} -> {len}",
51                start = range.start
52            );
53            range.start = len;
54        }
55        if range.end > len {
56            debug!(
57                "clamping request end to filesize {end} -> {len}",
58                end = range.end
59            );
60            range.end = len;
61        }
62    }
63    fn clamp_range_from(&self, range: &mut RangeFrom<u64>) {
64        let len = self.bytes.len() as u64;
65        if range.start > len {
66            debug!(
67                "clamping request start to filesize {start} -> {len}",
68                start = range.start
69            );
70            range.start = len;
71        }
72    }
73}
74
75impl Debug for LocalBytesClient {
76    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
77        f.debug_struct("LocalBytesClient")
78            .field("bytes", &ByteFormatter(&self.bytes))
79            .finish()
80    }
81}
82
83impl HttpClient {
84    pub fn test_client(data: &[u8]) -> Self {
85        Self {
86            client: Box::new(LocalBytesClient {
87                bytes: data.to_vec(),
88            }),
89            reader: crate::empty(),
90            pos: 0,
91            range: None,
92            stats: ReqStats::default(),
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use tokio::io::AsyncReadExt;
101
102    #[tokio::test]
103    async fn clamping() {
104        let bytes = [b'a', b'b', b'c'];
105        let mut client = HttpClient::test_client(&bytes);
106
107        client.set_range(1..3).await.unwrap();
108        let mut output = vec![];
109        client.read_to_end(&mut output).await.unwrap();
110        assert_eq!(output, [b'b', b'c']);
111
112        client.set_range(1..4).await.unwrap();
113        let mut output = vec![];
114        client.read_to_end(&mut output).await.unwrap();
115        assert_eq!(output, [b'b', b'c']);
116    }
117}