condow_core/
download_range.rs

1//! Ranges for specifying downloads
2use std::{
3    fmt,
4    ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
5};
6
7use crate::errors::CondowError;
8
9/// An inclusive range which can not have a length of 0.
10///
11/// A replacement for [RangeInclusive] with some sugar.
12///
13///
14/// ## Examples
15///
16/// ```
17/// # use condow_core::InclusiveRange;
18///
19/// let range1: InclusiveRange = (10..=20).into();
20/// let range2 = InclusiveRange(10, 20);
21///
22/// assert_eq!(range1, range2);
23/// ```
24#[derive(Debug, Copy, Clone, PartialEq, Eq)]
25pub struct InclusiveRange(pub u64, pub u64);
26
27impl InclusiveRange {
28    /// Returns the index of the first item within the range
29    ///
30    /// ```
31    /// use condow_core::InclusiveRange;
32    ///
33    /// let range: InclusiveRange = (4..=9).into();
34    ///
35    /// assert_eq!(range.start(), 4);
36    /// ```
37    pub fn start(&self) -> u64 {
38        self.0
39    }
40
41    /// Returns the index of the last item within the range
42    ///
43    /// ```
44    /// use condow_core::InclusiveRange;
45    ///
46    /// let range: InclusiveRange = (4..=9).into();
47    ///
48    /// assert_eq!(range.end_incl(), 9);
49    /// ```
50    pub fn end_incl(&self) -> u64 {
51        self.1
52    }
53
54    pub fn validate(&self) -> Result<(), CondowError> {
55        if self.end_incl() < self.start() {
56            Err(CondowError::new_invalid_range(format!(
57                "End must not be smaller than start: {}",
58                self
59            )))
60        } else {
61            Ok(())
62        }
63    }
64
65    /// Returns the length of the range
66    ///
67    /// ```
68    /// use condow_core::InclusiveRange;
69    ///
70    /// let range: InclusiveRange = (4..=9).into();
71    ///
72    /// assert_eq!(range.len(), 6);
73    /// ```
74    #[allow(clippy::len_without_is_empty)]
75    #[inline]
76    pub fn len(&self) -> u64 {
77        if self.1 < self.0 {
78            return 0;
79        }
80
81        self.1 - self.0 + 1
82    }
83
84    pub fn to_std_range(self) -> RangeInclusive<u64> {
85        self.0..=self.1
86    }
87
88    #[cfg(test)]
89    pub fn to_std_range_usize(self) -> RangeInclusive<usize> {
90        self.0 as usize..=self.1 as usize
91    }
92
93    pub fn to_std_range_excl(self) -> Range<u64> {
94        self.0..self.1 + 1
95    }
96
97    #[inline]
98    pub fn advance(&mut self, by: u64) {
99        self.0 += by
100    }
101
102    /// Returns a value for an  `HTTP-Range` header with bytes as the unit
103    ///
104    /// ```
105    /// use condow_core::InclusiveRange;
106    ///
107    /// let range: InclusiveRange = (4..=9).into();
108    ///
109    /// assert_eq!(range.http_bytes_range_value(), "bytes=4-9");
110    /// ```
111    pub fn http_bytes_range_value(&self) -> String {
112        format!("bytes={}-{}", self.0, self.1)
113    }
114}
115
116impl fmt::Display for InclusiveRange {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        write!(f, "[{}..{}]", self.0, self.1)
119    }
120}
121
122impl From<RangeInclusive<u64>> for InclusiveRange {
123    fn from(ri: RangeInclusive<u64>) -> Self {
124        Self(*ri.start(), *ri.end())
125    }
126}
127
128impl From<RangeToInclusive<u64>> for InclusiveRange {
129    fn from(ri: RangeToInclusive<u64>) -> Self {
130        Self(0, ri.end)
131    }
132}
133impl From<InclusiveRange> for RangeInclusive<u64> {
134    fn from(ir: InclusiveRange) -> Self {
135        ir.to_std_range()
136    }
137}
138
139impl From<InclusiveRange> for Range<u64> {
140    fn from(ir: InclusiveRange) -> Self {
141        ir.to_std_range_excl()
142    }
143}
144
145/// A range defined by an offset and a length.
146#[derive(Debug, Copy, Clone, PartialEq, Eq)]
147pub struct OffsetRange(pub u64, pub u64);
148
149impl OffsetRange {
150    pub fn new(offset: u64, len: u64) -> Self {
151        Self(offset, len)
152    }
153
154    /// Returns the index of the first item within the range
155    ///
156    /// ```
157    /// use condow_core::OffsetRange;
158    ///
159    /// let range = OffsetRange::new(4, 6);
160    ///
161    /// assert_eq!(range.start(), 4);
162    /// ```
163    pub fn start(&self) -> u64 {
164        self.0
165    }
166
167    /// Returns the index of the first item after the range
168    ///
169    /// ```
170    /// use condow_core::OffsetRange;
171    ///
172    /// let range = OffsetRange::new(4, 6);
173    ///
174    /// assert_eq!(range.end_excl(), 10);
175    /// ```
176    pub fn end_excl(&self) -> u64 {
177        self.0 + self.1
178    }
179
180    #[allow(clippy::len_without_is_empty)]
181    /// Returns the length of the range
182    ///
183    /// ```
184    /// use condow_core::OffsetRange;
185    ///
186    /// let range = OffsetRange::new(4, 6);
187    ///
188    /// assert_eq!(range.len(), 6);
189    /// ```
190    pub fn len(&self) -> u64 {
191        self.1
192    }
193}
194
195impl fmt::Display for OffsetRange {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        write!(f, "{}({})", self.0, self.1)
198    }
199}
200
201/// A closed range
202///
203/// A closed range has a "defined end".
204/// This does not require [Condow](crate::Condow) to do a size request.
205/// [Condow](crate::Condow) can be configured to do a size request anyways
206/// which allows to adjust the end of the range so that the whole range
207/// is part of the file. This is the default behaviour. If this
208/// behaviour is disabled, it is up to the caller to ensure a valid
209/// range which does not exceed the end of the file is supplied.
210///
211/// Naming
212///
213/// variants where the last index is included in the range are suffixed with
214/// "Inclusive". Those missing the suffix do not include the last index which
215/// makes them exlusive. This is the same convention as with the stdlib.
216#[derive(Debug, Clone, Copy, PartialEq)]
217pub enum ClosedRange {
218    /// From including the first value to excluding the second value
219    FromTo(u64, u64),
220    /// From including the first value to including the second value
221    FromToInclusive(u64, u64),
222    /// From the beginning to excluding the value
223    To(u64),
224    /// From the beginning to including the value
225    ToInclusive(u64),
226}
227
228impl ClosedRange {
229    /// Validates the range.
230    ///
231    /// Fails with an error containig the reason if the range is invalid.
232    ///
233    /// Ranges with only 1 parameter are always valid.
234    ///
235    /// ## Examples
236    ///
237    /// [ClosedRange::FromTo]:
238    ///
239    /// ```
240    /// use condow_core::ClosedRange;
241    ///
242    /// let range = ClosedRange::FromTo(0,1);
243    /// assert!(range.validate().is_ok());
244    ///
245    /// let range = ClosedRange::FromTo(1,1);
246    /// assert!(range.validate().is_ok());
247    ///
248    /// let range = ClosedRange::FromTo(1,0);
249    /// assert!(range.validate().is_err());
250    /// ```
251    ///
252    /// [ClosedRange::FromToInclusive]:
253    ///
254    /// ```
255    /// use condow_core::ClosedRange;
256    ///
257    /// let range = ClosedRange::FromToInclusive(0,1);
258    /// assert!(range.validate().is_ok());
259    ///
260    /// let range = ClosedRange::FromToInclusive(1,1);
261    /// assert!(range.validate().is_ok());
262    ///
263    /// let range = ClosedRange::FromToInclusive(1,0);
264    /// assert!(range.validate().is_err());
265    /// ```
266    pub fn validate(&self) -> Result<(), CondowError> {
267        match self {
268            Self::FromTo(a, b) => {
269                if b < a {
270                    Err(CondowError::new_invalid_range(format!(
271                        "FromTo: 'to'({b}) must not be lesser than 'from'({a})"
272                    )))
273                } else {
274                    Ok(())
275                }
276            }
277            Self::FromToInclusive(a, b) => {
278                if b < a {
279                    Err(CondowError::new_invalid_range(format!(
280                        "FromToInclusive: 'to'({b}) must not be lesser than 'from'({a})"
281                    )))
282                } else {
283                    Ok(())
284                }
285            }
286            _ => Ok(()),
287        }
288    }
289
290    pub fn is_empty(&self) -> bool {
291        self.len() == 0 // efficient enough since not on the hot path
292    }
293
294    pub fn len(&self) -> u64 {
295        match self {
296            Self::FromTo(a, b) => b - a,
297            Self::FromToInclusive(a, b) => b - a + 1,
298            Self::To(last_excl) => *last_excl,
299            Self::ToInclusive(last_incl) => last_incl + 1,
300        }
301    }
302
303    pub fn sanitized(self) -> Option<Self> {
304        match self {
305            Self::FromTo(a, b) => {
306                if b <= a {
307                    return None;
308                }
309            }
310            Self::FromToInclusive(a, b) => {
311                if b < a {
312                    return None;
313                }
314            }
315            Self::To(0) => return None,
316            Self::To(_) => {}
317            Self::ToInclusive(_) => {}
318        }
319
320        Some(self)
321    }
322
323    pub fn incl_range(self) -> Option<InclusiveRange> {
324        let inclusive = match self {
325            Self::FromTo(a, b) => {
326                if a == b {
327                    return None;
328                }
329                InclusiveRange(a, b - 1)
330            }
331            Self::FromToInclusive(a, b) => InclusiveRange(a, b),
332            Self::To(b) => {
333                if b == 0 {
334                    return None;
335                }
336                InclusiveRange(0, b - 1)
337            }
338            Self::ToInclusive(b) => InclusiveRange(0, b),
339        };
340
341        Some(inclusive)
342    }
343}
344
345impl fmt::Display for ClosedRange {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        match self {
348            ClosedRange::To(to) => write!(f, "[0..{to}["),
349            ClosedRange::ToInclusive(to) => write!(f, "[0..{to}]"),
350            ClosedRange::FromTo(from, to) => write!(f, "[{from}..{to}["),
351            ClosedRange::FromToInclusive(from, to) => write!(f, "[{from}..{to}]"),
352        }
353    }
354}
355
356impl From<Range<u64>> for ClosedRange {
357    fn from(r: Range<u64>) -> Self {
358        ClosedRange::FromTo(r.start, r.end)
359    }
360}
361
362impl From<RangeInclusive<u64>> for ClosedRange {
363    fn from(r: RangeInclusive<u64>) -> Self {
364        ClosedRange::FromToInclusive(*r.start(), *r.end())
365    }
366}
367
368impl From<RangeTo<u64>> for ClosedRange {
369    fn from(r: RangeTo<u64>) -> Self {
370        ClosedRange::To(r.end)
371    }
372}
373
374impl From<RangeToInclusive<u64>> for ClosedRange {
375    fn from(r: RangeToInclusive<u64>) -> Self {
376        ClosedRange::ToInclusive(r.end)
377    }
378}
379
380impl From<InclusiveRange> for ClosedRange {
381    fn from(r: InclusiveRange) -> Self {
382        ClosedRange::FromToInclusive(r.0, r.1)
383    }
384}
385
386/// An open range
387///
388/// An open range has no "defined end".
389/// This always requires [Condow](crate::Condow) to do a size request
390/// so that the download can be split into parts of known size.
391#[derive(Debug, Clone, Copy, PartialEq)]
392pub enum OpenRange {
393    /// Download from the specified byte to the end
394    From(u64),
395    /// Download the whole file
396    Full,
397}
398
399impl OpenRange {
400    pub fn incl_range_from_size(self, size: u64) -> Result<Option<InclusiveRange>, CondowError> {
401        if size == 0 {
402            return Ok(None);
403        }
404
405        let max_inclusive = size - 1;
406        let inclusive = match self {
407            Self::From(a) => InclusiveRange(a, max_inclusive),
408            Self::Full => InclusiveRange(0, max_inclusive),
409        };
410
411        inclusive.validate()?;
412
413        Ok(Some(inclusive))
414    }
415}
416
417impl fmt::Display for OpenRange {
418    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419        match self {
420            OpenRange::From(from) => write!(f, "[{}..]", from),
421            OpenRange::Full => write!(f, "[0..]"),
422        }
423    }
424}
425
426/// A range which specifies a download
427///
428/// Conversions for the standard Rust range syntax exist.
429///
430/// # Examples
431///
432/// ```rust
433/// # use condow_core::*;
434/// let dl = DownloadRange::from(..);
435/// assert_eq!(dl, DownloadRange::Open(OpenRange::Full));
436/// ```
437///
438/// ```rust
439/// # use condow_core::*;
440/// let dl = DownloadRange::from(3..);
441/// assert_eq!(dl, DownloadRange::Open(OpenRange::From(3)));
442/// ```
443///
444/// ```rust
445/// # use condow_core::*;
446/// let dl = DownloadRange::from(5..10);
447/// assert_eq!(dl, DownloadRange::Closed(ClosedRange::FromTo(5,10)));
448/// ```
449///
450/// ```rust
451/// # use condow_core::*;
452/// let dl = DownloadRange::from(5..=10);
453/// assert_eq!(dl, DownloadRange::Closed(ClosedRange::FromToInclusive(5, 10)));
454/// ```
455///
456/// ```rust
457/// # use condow_core::*;
458/// let dl = DownloadRange::from(..7);
459/// assert_eq!(dl, DownloadRange::Closed(ClosedRange::To(7)));
460/// ```
461///
462/// ```rust
463/// # use condow_core::*;
464/// let dl = DownloadRange::from(..=7);
465/// assert_eq!(dl, DownloadRange::Closed(ClosedRange::ToInclusive(7)));
466/// ```
467///
468/// ```rust
469/// # use condow_core::*;
470/// let dl = DownloadRange::from(InclusiveRange(1, 7));
471/// assert_eq!(dl, DownloadRange::Closed(ClosedRange::FromToInclusive(1,7)));
472/// ```
473#[derive(Debug, Clone, Copy, PartialEq)]
474pub enum DownloadRange {
475    Open(OpenRange),
476    Closed(ClosedRange),
477}
478
479impl DownloadRange {
480    pub fn validate(&self) -> Result<(), CondowError> {
481        match self {
482            DownloadRange::Open(_) => Ok(()),
483            DownloadRange::Closed(r) => r.validate(),
484        }
485    }
486
487    pub fn sanitized(self) -> Option<Self> {
488        match self {
489            DownloadRange::Open(_) => Some(self),
490            DownloadRange::Closed(r) => r.sanitized().map(DownloadRange::Closed),
491        }
492    }
493
494    pub fn incl_range_from_size(self, size: u64) -> Result<Option<InclusiveRange>, CondowError> {
495        match self {
496            DownloadRange::Open(r) => r.incl_range_from_size(size),
497            DownloadRange::Closed(r) => {
498                let inclusive = r.incl_range();
499                if let Some(inclusive) = inclusive {
500                    inclusive.validate()?
501                }
502                Ok(inclusive)
503            }
504        }
505    }
506
507    /// Returns the length in bytes for ranges where an end is defined
508    pub fn len(&self) -> Option<u64> {
509        match self {
510            DownloadRange::Open(_) => None,
511            DownloadRange::Closed(r) => Some(r.len()),
512        }
513    }
514
515    pub fn is_empty(&self) -> Option<bool> {
516        match self {
517            DownloadRange::Open(_) => None,
518            DownloadRange::Closed(r) => Some(r.is_empty()),
519        }
520    }
521}
522
523impl fmt::Display for DownloadRange {
524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
525        match self {
526            DownloadRange::Open(open) => open.fmt(f),
527            DownloadRange::Closed(closed) => closed.fmt(f),
528        }
529    }
530}
531
532impl From<RangeFull> for DownloadRange {
533    fn from(_: RangeFull) -> Self {
534        Self::Open(OpenRange::Full)
535    }
536}
537
538impl From<Range<u64>> for DownloadRange {
539    fn from(r: Range<u64>) -> Self {
540        Self::Closed(r.into())
541    }
542}
543
544impl From<RangeInclusive<u64>> for DownloadRange {
545    fn from(r: RangeInclusive<u64>) -> Self {
546        Self::Closed(r.into())
547    }
548}
549
550impl From<RangeFrom<u64>> for DownloadRange {
551    fn from(r: RangeFrom<u64>) -> Self {
552        Self::Open(OpenRange::From(r.start))
553    }
554}
555
556impl From<RangeTo<u64>> for DownloadRange {
557    fn from(r: RangeTo<u64>) -> Self {
558        Self::Closed(r.into())
559    }
560}
561
562impl From<RangeToInclusive<u64>> for DownloadRange {
563    fn from(r: RangeToInclusive<u64>) -> Self {
564        Self::Closed(r.into())
565    }
566}
567
568impl From<InclusiveRange> for DownloadRange {
569    fn from(r: InclusiveRange) -> Self {
570        Self::Closed(r.into())
571    }
572}
573
574impl From<OffsetRange> for DownloadRange {
575    fn from(r: OffsetRange) -> Self {
576        Self::Closed(ClosedRange::FromTo(r.start(), r.end_excl()))
577    }
578}
579
580impl From<ClosedRange> for DownloadRange {
581    fn from(r: ClosedRange) -> Self {
582        Self::Closed(r)
583    }
584}
585
586#[test]
587fn range_full() {
588    let result: DownloadRange = (..).into();
589    assert_eq!(result, DownloadRange::Open(OpenRange::Full));
590}
591
592#[test]
593fn range() {
594    let result: DownloadRange = (3..10).into();
595    assert_eq!(result, DownloadRange::Closed(ClosedRange::FromTo(3, 10)));
596}
597
598#[test]
599fn range_inclusive() {
600    let result: DownloadRange = (3..=10).into();
601    assert_eq!(
602        result,
603        DownloadRange::Closed(ClosedRange::FromToInclusive(3, 10))
604    );
605}
606
607#[test]
608fn range_from() {
609    let result: DownloadRange = (3..).into();
610    assert_eq!(result, DownloadRange::Open(OpenRange::From(3)));
611}
612
613#[test]
614fn range_to() {
615    let result: DownloadRange = (..10).into();
616    assert_eq!(result, DownloadRange::Closed(ClosedRange::To(10)));
617}
618
619#[test]
620fn range_to_inclusive() {
621    let result: DownloadRange = (..=10).into();
622    assert_eq!(result, DownloadRange::Closed(ClosedRange::ToInclusive(10)));
623}